Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

A simple clipboard stack/ kill ring,


  • Please log in to reply
22 replies to this topic
bjork
  • Members
  • 1 posts
  • Last active: Aug 10 2006 05:47 AM
  • Joined: 07 Aug 2006
Hi everyone,

My AHK scripting is very rudimentary, and I am sure that this script (or similar) has been written before in much better style. I would welcome any comments. I would especially like to know if there is some way to declare a static global array, without having to declare multiple elements individually.

The script copies successive clipboards onto a rotating stack, and then lets you pull them back one at a time, or cycle through to find the one you want. It's meant to vaguely emulate Emacs kill ring, though the resemblance is very sketchy.

At the moment, I am using the following hotkeys:
#[ Copy
#] Paste
#\ RollBack
#^\ RollForward
#0 Clear Stack

Note that this only works for pure text, as I found that I couldn't store multiple ClipboardAll (looks like ClipboardAll can only be stored as a pointer to a single Clipboard object? Is that right?).

Anyway, I'd love it if anyone would take a look, see if it works, see if it could be implemented better, and tell me about any similar kinds of scripts that already exist. I have looked through the forum, and find lots of stuff on clipboard enhancements, but nothing I have seen resembles this. More than likely I just haven't looked hard enough.

thanks heaps,
LBB


handleClip(action)
{
	global static AddNextNum
	global static GetNextNum
	global static HighestNum
	global static ClipArray
	global static ClipArray1
	global static ClipArray2
	global static ClipArray3
	global static ClipArray4
	global static ClipArray5
	global static ClipArray6
	global static ClipArray7
	global static ClipArray8
	global static ClipArray9
	global static ClipArray10
	global static ClipArray11
	global static ClipArray12
	global static ClipArray13
	global static ClipArray14
	global static ClipArray15
	global static ClipArray16
	global static ClipArray17
	global static ClipArray18
	global static ClipArray19
	global static ClipArray20
	global static ClipArray21
	global static ClipArray22
	global static ClipArray23
	global static ClipArray24
	global static ClipArray25
	global static ClipArray26
	global static ClipArray27
	global static ClipArray28
	global static ClipArray29
	global static ClipArray30


	
	if (action = "save")
	{
		if (AddNextNum < 30)
		{
			AddNextNum += 1 ;
		}
		else
		{
			AddNextNum := 1 ;
		}


		if (HighestNum < 30)
		{
			HighestNum += 1 ;
		}


		GetNextNum := AddNextNum ;	
		ClipArray%AddNextNum% := Clipboard
	}
	else if ((action = "get") OR (action = "roll"))
	{
		if (GetNextNum != 0)
		{
			if (action = "roll")
			{
				Send, ^z
			}
			Clipboard := ClipArray%GetNextNum%
			if (GetNextNum > 1)
			{
				GetNextNum -= 1 ;
			}
			else
			{
				GetNextNum := HighestNum
			}
			Send, ^v
		}
	}
	else if (action = "rollforward")
	{
		if (GetNextNum != 0)
		{
			Send, ^z
			if (GetNextNum < HighestNum)
			{
				GetNextNum += 1 ;
			}
			else
			{
				GetNextNum := 1
			}
			Clipboard := ClipArray%GetNextNum%
			Send, ^v
		}
	}
	else if (action = "clear")
	{
		
		GetNextNum := 0
		AddNextNum := 0
		HighestNum := 0
	}
}


#0::
	handleClip("clear")
return

#[::
	Send, ^c
	handleClip("save")
	
return


#]::
	handleClip("get")
return

#\::
	handleClip("roll")
return

#^\::
	handleClip("rollforward")
return



ohadsc
  • Guests
  • Last active:
  • Joined: --
I was looking for something like this for AGES UPON AGES
Your script does the job superbly, though I modified it to replace ctrl+c altogether (and changed the paste shortcut to win+v)

Basically all it is is changing the hotkey to ^c and suspending events before sending ^c to the application (restoring it after) so that you don't get caught in an endless loop

so now you can copy copy copy whatever you like normally, and at any point start cycling back your recent copies.

I've tried every existing clipboard manager there is, very few allow this basic, almost mandatory, functionality and those who do implement it horribly. Hooray for AHK !

For your convenience, my small modification:

handleClip(action)
{
   global static AddNextNum
   global static GetNextNum
   global static HighestNum
   global static ClipArray
   global static ClipArray1
   global static ClipArray2
   global static ClipArray3
   global static ClipArray4
   global static ClipArray5
   global static ClipArray6
   global static ClipArray7
   global static ClipArray8
   global static ClipArray9
   global static ClipArray10
   global static ClipArray11
   global static ClipArray12
   global static ClipArray13
   global static ClipArray14
   global static ClipArray15
   global static ClipArray16
   global static ClipArray17
   global static ClipArray18
   global static ClipArray19
   global static ClipArray20
   global static ClipArray21
   global static ClipArray22
   global static ClipArray23
   global static ClipArray24
   global static ClipArray25
   global static ClipArray26
   global static ClipArray27
   global static ClipArray28
   global static ClipArray29
   global static ClipArray30
   
   if (action = "save")
   {
      if (AddNextNum < 30)
      {
         AddNextNum += 1 ;
      }
      else
      {
         AddNextNum := 1 ;
      }


      if (HighestNum < 30)
      {
         HighestNum += 1 ;
      }

      GetNextNum := AddNextNum ;   
      ClipArray%AddNextNum% := Clipboard
   }
   else if ((action = "get") OR (action = "roll"))
   {
      if (GetNextNum != 0)
      {
         if (action = "roll")
         {
            Send, ^z
         }
         Clipboard := ClipArray%GetNextNum%
         if (GetNextNum > 1)
         {
            GetNextNum -= 1 ;
         }
         else
         {
            GetNextNum := HighestNum
         }
         Send, ^v
      }
   }
   else if (action = "rollforward")
   {
      if (GetNextNum != 0)
      {
         Send, ^z
         if (GetNextNum < HighestNum)
         {
            GetNextNum += 1 ;
         }
         else
         {
            GetNextNum := 1
         }
         Clipboard := ClipArray%GetNextNum%
         Send, ^v
      }
   }
   else if (action = "clear")
   {
      
      GetNextNum := 0
      AddNextNum := 0
      HighestNum := 0
   }
}

#0::
   handleClip("clear")
return

^c::
	suspend on
	Send, ^c
	suspend off
	handleClip("save")
   
return

#v::
   handleClip("get")
return

#\::
   handleClip("roll")
return

#^\::
   handleClip("rollforward")
return


RocknRoller
  • Guests
  • Last active:
  • Joined: --
I must say that I also probably tried all existing clipboard managers and this one looks perfect for my needs!

Thanks for sharing!

tonne
  • Members
  • 1654 posts
  • Last active: Apr 10 2014 04:12 PM
  • Joined: 06 Jun 2006
v1.0.48+ supports assume static mode, and the global static var section can be replaced with:
handleClip(action)
{
   static ; assume all variables static (v1.0.48+)
   
   if (action = "save")
   {
...


ohadsc
  • Guests
  • Last active:
  • Joined: --

v1.0.48+ supports assume static mode, and the global static var section can be replaced with:

handleClip(action)
{
   static ; assume all variables static (v1.0.48+)
   
   if (action = "save")
   {
...


I too agree there are quite a few things that could be written better there (like, using an *array* instead of 32 variables :), but I am unfamiliar with the AHK language (C++ programmer myself) and I saw the job got done exactly as I wanted it so I left it alone...

None of this is to take anything away from bjork - The guy is my personal hero :)

Admins - perhaps add this to the wiki ? I haven't tried all clipboard scripts there, but even if some of them have this exact functionality, I think this one is worth adding there - I know I for one have scoured the internet for something like this

Actually I installed AHK specifically for it, of course then I realized what a great platform it is and installed some more scripts :)

Cheers!

ohadsc
  • Guests
  • Last active:
  • Joined: --
BTW, I am using this in conjunction with ClipMagic - http://www.clipmagic.com/ which is, IMHO, the best free clipboard manager

It even has functionality similar to this script (with the "ring" being every clip you ever copied) but its hotkey is not configurable - that's when this script comes in

Cheers

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

only works for pure text, as I found that I couldn't store multiple ClipboardAll (looks like ClipboardAll can only be stored as a pointer to a single Clipboard object? Is that right?).

In the 4 years old Deluxe Clipboard the multiple binary clipboard problem was solved with arrays. Each array entry stores a ClipboardAll instance, to be pasted one-by-one in a loop.

t0xin
  • Members
  • 1 posts
  • Last active: Nov 20 2012 01:47 PM
  • Joined: 30 Mar 2009
I made another small modification
Turns out sending Ctrl+c to the application might not work perfectly, for example in Lyx the Ctrl doesn't get through and it sends a 'c', overwriting your selection. Also, I added cut items (Ctrl+x) to the clipboard ring.

To fix the sending bug, I changed the hotkey to be sent natively to the application (via the '~' prefix) and added the UP suffix so that the script will kick in after the application has already placed the item in the clipboard

Instead of ctrl+v I now send shift+insert (seems to work with Lyx as well)

handleClip(action)
{
   global static AddNextNum
   global static GetNextNum
   global static HighestNum
   global static ClipArray
   global static ClipArray1
   global static ClipArray2
   global static ClipArray3
   global static ClipArray4
   global static ClipArray5
   global static ClipArray6
   global static ClipArray7
   global static ClipArray8
   global static ClipArray9
   global static ClipArray10
   global static ClipArray11
   global static ClipArray12
   global static ClipArray13
   global static ClipArray14
   global static ClipArray15
   global static ClipArray16
   global static ClipArray17
   global static ClipArray18
   global static ClipArray19
   global static ClipArray20
   global static ClipArray21
   global static ClipArray22
   global static ClipArray23
   global static ClipArray24
   global static ClipArray25
   global static ClipArray26
   global static ClipArray27
   global static ClipArray28
   global static ClipArray29
   global static ClipArray30
   
   if (action = "save")
   {
      if (AddNextNum < 30)
      {
         AddNextNum += 1 ;
      }
      else
      {
         AddNextNum := 1 ;
      }


      if (HighestNum < 30)
      {
         HighestNum += 1 ;
      }

      GetNextNum := AddNextNum ;   
      ClipArray%AddNextNum% := Clipboard
   }
   else if ((action = "get") OR (action = "roll"))
   {
      if (GetNextNum != 0)
      {
         if (action = "roll")
         {
            Send, ^z
         }
         Clipboard := ClipArray%GetNextNum%
         if (GetNextNum > 1)
         {
            GetNextNum -= 1 ;
         }
         else
         {
            GetNextNum := HighestNum
         }
         ;Send, ^v
	 Send, +{insert}
      }
   }
   else if (action = "rollforward")
   {
      if (GetNextNum != 0)
      {
         Send, ^z
         if (GetNextNum < HighestNum)
         {
            GetNextNum += 1 ;
         }
         else
         {
            GetNextNum := 1
         }
         Clipboard := ClipArray%GetNextNum%
         Send, ^v
      }
   }
   else if (action = "clear")
   {
      
      GetNextNum := 0
      AddNextNum := 0
      HighestNum := 0
   }
}

#0::
   handleClip("clear")
return

~^c Up::
	;suspend on
	;Send, ^c
	;suspend off
	handleClip("save")
   return
   
~^x Up::
	;suspend on
	;Send, ^c
	;suspend off
	handleClip("save")
	
return

#v::
   handleClip("get")
return

; #\::
   ; handleClip("roll")
; return

; #^\::
   ; handleClip("rollforward")
; return

@Laszlo - Thanks for the insight! Perhaps someone more skilled than I in AHK will make that modification.

pajenn
  • Members
  • 391 posts
  • Last active: Jan 28 2014 03:01 PM
  • Joined: 07 Feb 2009
I condensed the code for my own use, otherwise it's the same as ohadsc's version. I'm using AHK Version 1.0.48.03, so not sure if it works on older ones:

#SingleInstance force
SendMode Input
handleClip("clear")

handleClip(action)
{
   static
   
   if (action = "save")
   {
      AddNextNum:= AddNextNum < 30 ? AddNextNum+1 : 1 
      HighestNum+= HighestNum < 30 ? 1 : 0
      GetNextNum:= AddNextNum , ClipArray%AddNextNum% := Clipboard
   }
   else if ((action = "get") || (action = "roll")) && (GetNextNum != 0)
   {
      if (action = "roll")
         Send ^z
      Clipboard := ClipArray%GetNextNum%
      GetNextNum:= GetNextNum > 1 ? GetNextNum-1 : HighestNum   
      Send ^v
   }
   else if ((action = "rollforward") && (GetNextNum != 0))
   {
      Send, ^z
      GetNextNum:= GetNextNum < HighestNum ? GetNextNum+1 : 1
      Clipboard:= ClipArray%GetNextNum%
      Send, ^v
   }
   else if (action = "clear")
      GetNextNum:=0, AddNextNum=HighestNum=0
}

^c::
   suspend on
   Send ^c
   suspend off
   handleClip("save")
return

#0::handleClip("clear")
#v::handleClip("get")
#\::handleClip("roll")
#^\::handleClip("rollforward")

note: I SendMode to Input and added handleClip("Clear") at load-up, because it appeared to require a clearing to initialize (ymmv).

ribbet.1
  • Members
  • 198 posts
  • Last active: Feb 07 2012 01:21 AM
  • Joined: 20 Feb 2007
Unfortunately neither of these are working for me. And the error code I see with Pajenn's script is:


Error at line 34.
Line Text: ^c::
Error: Hotkeys/hotstrings are not allowed inside functions.

The program will exit.
---------------------------
OK   

So I think you forgot to close a bracket or two. I just tried Lazlo's also, and I'm having really bad luck with these. I have MSWord installed and several little programs running that do clipboard tricks, such as Textpad and Xplorer2. I wonder if there's any relation?

pajenn
  • Members
  • 391 posts
  • Last active: Jan 28 2014 03:01 PM
  • Joined: 07 Feb 2009

Unfortunately neither of these are working for me. And the error code I see with Pajenn's script is:


Error at line 34.
Line Text: ^c::
Error: Hotkeys/hotstrings are not allowed inside functions.

The program will exit.
---------------------------
OK   

So I think you forgot to close a bracket or two. I just tried Lazlo's also, and I'm having really bad luck with these. I have MSWord installed and several little programs running that do clipboard tricks, such as Textpad and Xplorer2. I wonder if there's any relation?



fixed -- i had accidentally deleted a curly bracket before posting.

just copy bunch of simple text with ^c. then press #v repeatedly to paste them from newest to oldest. #\ and ^#\ let you change the last pasting to adjacent ones in the ring.

it's a very basic script so people probably need to customize the keys for their needs the way t0xin did to use it with Lyx. i only tried the one i posted in notepad.

ribbet.1
  • Members
  • 198 posts
  • Last active: Feb 07 2012 01:21 AM
  • Joined: 20 Feb 2007
Appears to work now Pajenn. Tested in TextPad. When it rolls, it repeats the same clipboard item sometimes and I don't see exactly why, but it does it exactly the same way until I clear the ring and start over. But that notwithstanding it is performing now. This may have something to do with how Textpad handles clipboard too, because it has some features built in. And yes, I would want to reconfigure the keys. Thanks for your quick reply and for your addition.

;-----------------------------------------------------

Added later. It was appearing to replay certain items twice yesterday, but I'm not seeing that now. Just my brain hicoughing apparently. It works well for me.

Chairs, ribbet

pajenn
  • Members
  • 391 posts
  • Last active: Jan 28 2014 03:01 PM
  • Joined: 07 Feb 2009
I added a few things that others may or may not find useful. If you see a better implementation, let me know:

1. RButton as a trigger: When user right-clicks, the scripts starts a keywait loop for the subsequent left-click. If detected, it waits 1s to see if anything appears on clipboard. If yes, it adds the new clipboard contents to the ring. Since I sometimes take a while to choose an option from the right-click context menu, I have the loop check every 2s if context menu class window is still present, if not the loop breaks. It's truncated at 6 cycles in case there are multiple ahk_class #32768 windows. (note: may require DetectHiddenWindows, On - not sure since the larger script I added this to has that on anyway...)

If there's a better way to do this using OnMessage or such, please let me know.

sidenote: I also decided to go with the ~^c and ~^x hotkey format instead of using Suspend on-off. If I experience the endless loop problem mentioned earlier, I'll reconsider.

~RButton::
  loop, 4
  {
    oCB:= ClipboardAll
    Clipboard=
    KeyWait, LButton, D T3
    If !ErrorLevel
    {
      ClipWait, 1
      If !ErrorLevel
        handleClip("save")
      Else Clipboard:= oCB
      Break
    }
    Else If !WinExist("ahk_class #32768")
    {
      Clipboard:= oCB
      Break
    }
  }
return

~^x::
~^c::
   Sleep, 100
   handleClip("save")
return

2. I added a sixth action to clipHandle (action = "write"), which loops through the clipArray and logs the contents. I only call it as part of the larger script's OnExit routine so that I don't lose something useful if I have to close or restart the script:

else if (action = "write")
    loop, 15
    {
      If A_Index = 1
        FileAppend, `n%A_Now%`n, %A_ScriptDir%\ClipRing.txt
      If content:= ClipArray%A_Index%
        FileAppend, %content%`n, %A_ScriptDir%\ClipRing.txt
    }

On a somewhat related note, if anyone wants to also save image content, I found Sean's convert function (from ScreenCapture) to work great for that. My original PrntSc key is an incovient fn-ins key combination, so I switched it to scroll key (sc046 for me), and convert it as follows:

sc046::
{
  FormatTime, myTime, , dMMMyy_HHmmss
  Send {PrintScreen}
  ClipWait,1,1
  If !ErrorLevel
    Convert(0, "Z:\My Pics\PrintScreens\shot_" . myTime . ".png")
}
Return

note: I append a time stamp to the pic names the differentiate Printscreen shots, and my OnExit routine keeps Z:\My Pics\PrintScreens folder down to the 20 most recent files.

H4ck3rs
  • Members
  • 1 posts
  • Last active: Jun 10 2009 03:26 AM
  • Joined: 10 Jun 2009
lol

  • Guests
  • Last active:
  • Joined: --
I just noticed that AHK has a built-in "OnClipboardChange" label so there's no need link ^c or ^x type keys to it. I updated the script as follows:

1. I'm using #Right for paste from clipring, and #Up/#Down change clip, #Del to clear it. #Left might have been the more consistent choice, but I thought I might hit it accidentally when pressing the other keys. I also change the cliparray to 15 clips (from 30).

2. I added the OnClipboardChange label so it when new text enters clipboard it's added to the clipring with clipHandle("Save"). However, if you use this script with different hotkeys, you need to add them to the OnClipboardChange label's 'ignore list', as well as other hotkeys that change clipboard content.

3. I included the OnExit label to save clip content before exit, but most people probably don't want those so just remove it, and the ClipSave subroutine, and also the "else if (action = "write")" if you don't need it.

4. I added an "Else If (A_EventInfo = 2)" to the OnClipboardChange label, though it's currently disable. Type 2 events are things like images (non-text), so if you want those you can download the "convert" function (link in script) and add it to your library. It's only meant for images, and I think type 2 clipboard events other things too (not sure), so it might be better to add further restriction to it, for example, only execute convert if printscreen was pressed. Also, I have it set to save type 2 content to 'My Documents' with [time stamp].png title.

#NoEnv
#SingleInstance, Force
SetBatchLines, -1
SendMode, Input
OnExit, ClipSave
handleClip("clear")
Return

#Right::handleClip("get") ;paste
#Up::handleClip("rollforward")
#Down::handleClip("roll")
#Del::handleClip("clear")

OnClipboardChange:
If A_ThisHotkey In #Right,#Up,#Down  ;add your other hotkeys that use clipboard
  Return
If (A_EventInfo = 1)
  handleClip("save")
;Type 2 events include images, you can get them with the convert function from
;Sean's ScreenCapture.ahk: http://www.autohotkey.com/forum/viewtopic.php?t=18146
;Else If (A_EventInfo = 2) ;add further restriction (e.g. only if PrtSc was pressed)
;  Convert(0, A_MyDocuments . "\" . A_Now . ".png") ;customize save location and pic name
Return

handleClip(action)
{
  static
  if (action = "save")
  {
    AddNextNum:= AddNextNum < 15 ? AddNextNum+1 : 1 
    HighestNum+= HighestNum < 15 ? 1 : 0
    GetNextNum:= AddNextNum, ClipArray%AddNextNum% := Clipboard
  }
  else if ((action = "get") || (action = "roll")) && (GetNextNum != 0)
  {
    if (action = "roll")
      Send ^z
    Clipboard := ClipArray%GetNextNum%
    GetNextNum:= GetNextNum > 1 ? GetNextNum-1 : HighestNum   
    Send ^v
  }
  else if ((action = "rollforward") && (GetNextNum != 0))
  {
    Send ^z
    GetNextNum:= GetNextNum < HighestNum ? GetNextNum+1 : 1
    Clipboard:= ClipArray%GetNextNum%
    Send ^v
  }
  else if (action = "clear")
    GetNextNum:=0, AddNextNum=HighestNum=0
  else if (action = "write")
    loop, 15
    {
      If A_Index = 1
        FileAppend, `n%A_Now%`n, %A_ScriptDir%\ClipRing.txt
      If content:= ClipArray%A_Index%
        FileAppend, %content%`n, %A_ScriptDir%\ClipRing.txt
    }
}

ClipSave:
handleClip("write")
ExitApp

Problems:
The rolling uses ^z to undo the previous action, then pastes a new clip. The problem is that ^z often undoes more than just the last thing you pasted. At least in Notepad it takes back everything you did in the last 30 sec or so...

I considered alternative for selecting only the last item to have been pasted, but thought the simplicity of ^z outweighed its shortcomings.

I tried cutting out use of clipboard completely, and just send the array content directly to the application with
SendRaw % ClipArray%GetNextNum%
but the problem is it sends array contents one as a string of characters, which caused SciTE to inject auto-formating (indents, etc) into the process.

I also thought about sending the windows X=(StringLength of last paste) steps left with shiftdown, but it's not exact because of special characters (`n`r etc), so you'd need to return a few steps, but that was getting too complicated when I liked this script in the first place due to its simplicity.

fwiw, here's the type of loop i tried, but it's very rough:

else if ((action = "rollforward") && (GetNextNum != 0))
  {
    temp1:= Clipboard, steps:= StrLen(temp1)
    Send {ShiftDown}{Left %steps%}{ShiftUp}
    loop, % steps
    {
      Clipboard =
      Send ^c
      ClipWait, 1
      If (temp1 = Clipboard)
        Break
      Else Send {ShiftDown}{Right}{ShiftUp}
    }
    Send {Del}
    GetNextNum:= GetNextNum < HighestNum ? GetNextNum+1 : 1
    Clipboard:= ClipArray%GetNextNum%
    Send ^v
  }

if anyone knows of an elegant way to walk back one paste only, please post it.