 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
bjork
Joined: 07 Aug 2006 Posts: 1 Location: australia
|
Posted: Mon Aug 07, 2006 2:53 am Post subject: A simple clipboard stack/ kill ring, |
|
|
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
| Code: |
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
|
|
|
| Back to top |
|
 |
ohadsc Guest
|
Posted: Tue Mar 24, 2009 4:53 am Post subject: Beautiful ! |
|
|
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:
| Code: | 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 |
|
|
| Back to top |
|
 |
RocknRoller Guest
|
Posted: Wed Mar 25, 2009 1:27 pm Post subject: |
|
|
I must say that I also probably tried all existing clipboard managers and this one looks perfect for my needs!
Thanks for sharing! |
|
| Back to top |
|
 |
tonne
Joined: 06 Jun 2006 Posts: 1651 Location: Denmark
|
Posted: Wed Mar 25, 2009 1:50 pm Post subject: |
|
|
v1.0.48+ supports assume static mode, and the global static var section can be replaced with:
| Code: | handleClip(action)
{
static ; assume all variables static (v1.0.48+)
if (action = "save")
{
... |
_________________ RegEx Powered Dynamic Hotstrings
COM
AutoHotkey 2 |
|
| Back to top |
|
 |
ohadsc Guest
|
Posted: Wed Mar 25, 2009 2:17 pm Post subject: Perhaps add to Wiki ? |
|
|
| tonne wrote: | v1.0.48+ supports assume static mode, and the global static var section can be replaced with:
| Code: | 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! |
|
| Back to top |
|
 |
ohadsc Guest
|
Posted: Thu Mar 26, 2009 10:19 am Post subject: |
|
|
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 |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4710 Location: Boulder, CO
|
Posted: Thu Mar 26, 2009 10:00 pm Post subject: Re: A simple clipboard stack/ kill ring, |
|
|
| bjork wrote: | | 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. |
|
| Back to top |
|
 |
t0xin
Joined: 30 Mar 2009 Posts: 1
|
Posted: Mon Mar 30, 2009 8:56 pm Post subject: Another update |
|
|
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)
| Code: | 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. |
|
| Back to top |
|
 |
pajenn
Joined: 07 Feb 2009 Posts: 384
|
Posted: Thu Jun 04, 2009 2:54 pm Post subject: |
|
|
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:
| Code: | #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). _________________ Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.
Last edited by pajenn on Thu Jun 04, 2009 7:51 pm; edited 1 time in total |
|
| Back to top |
|
 |
ribbet.1
Joined: 20 Feb 2007 Posts: 197 Location: D.C.
|
Posted: Thu Jun 04, 2009 7:20 pm Post subject: |
|
|
Unfortunately neither of these are working for me. And the error code I see with Pajenn's script is:
| Code: | 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? |
|
| Back to top |
|
 |
pajenn
Joined: 07 Feb 2009 Posts: 384
|
Posted: Thu Jun 04, 2009 7:59 pm Post subject: |
|
|
| ribbet.1 wrote: | Unfortunately neither of these are working for me. And the error code I see with Pajenn's script is:
| Code: | 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. _________________ Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler. |
|
| Back to top |
|
 |
ribbet.1
Joined: 20 Feb 2007 Posts: 197 Location: D.C.
|
Posted: Thu Jun 04, 2009 8:55 pm Post subject: |
|
|
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 |
|
| Back to top |
|
 |
pajenn
Joined: 07 Feb 2009 Posts: 384
|
Posted: Tue Jun 09, 2009 5:43 pm Post subject: |
|
|
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.
| Code: | ~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:
| Code: | 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:
| Code: | 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. _________________ Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.
Last edited by pajenn on Wed Jun 10, 2009 11:34 am; edited 2 times in total |
|
| Back to top |
|
 |
H4ck3rs
Joined: 10 Jun 2009 Posts: 1
|
Posted: Wed Jun 10, 2009 3:25 am Post subject: |
|
|
lol _________________
 |
|
| Back to top |
|
 |
Guest
|
Posted: Thu Jun 11, 2009 6:56 pm Post subject: |
|
|
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.
| Code: | #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
| Code: | | 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:
| Code: | 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. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|