Catch copy/paste of empty text on Windows from AutoHotKey script - windows

By default on Windows, when copying text, it gets put in the clipboard. But when attempting to copy empty text, the clipboard is untouched. For example, selecting no text in your editor, then hitting ctrl+c, will cause no change in the clipboard.
Problem is, I need to catch this event with AutoHotKey. Since the clipboard is unchanged, I have no idea how to do this cleanly (without a timeout, that is).
Does anyone have any idea how to do this?
Edit: To clarify, I'm sending the ctrl+c from within AutoHotKey. I'm doing so to tell if any text is selected, i.e., I'm sending ctrl+c, then checking if any text was copied to the clipboard or not. Problem is, if no text is selected, the clipboard handlers for AutoHotKey never get called, forcing me to use a timeout, which isn't good practice.

Here is what I did. Since the clipboard is a variable in AutoHotkey, you can check to see if it is empty. I first cleared the clipboard, send control+c, then see if the clipboard is still empty. You can temporarily move the current clipboard to a temporary place first if you want.
ClipSaved := ClipboardAll
Clipboard = ; empties the clipboard
Send ^+{Left} ; I just used highlight left to select text, you can replace this with
; whatever your program uses to select an input.
Send ^c ; attempt to copy text
If Clipboard = ; checks to see if clipboard is empty
{
break ; Put what you want to do if the clipboard is empty, I used break to stop a loop
}
Clipboard := ClipSaved ; puts the original clipboard contents back
I was searching text from an open document in which the user can choose a forward or backward direction. When going backwards, it would get stuck in a loop at the beginning of the document. I set a loop limit to keep it from being an infinite loop, but it still wasted time having to wait for the loop to finish. I used the break function to end the loop if the clipboard was empty.
To give credit where credit is due, I got the inspiration from another post which had other tood tips. It posted you can check for a blank variable with this script.
http://www.autohotkey.net/~deleyd/xprxmp/autohotkey_expression_examples.htm#J
v := ""
If v =
MsgBox v = ""
If (v = "")
MsgBox v = ""
From the AutoHotkey documentation website I found out how to temporarily store and replace the clipboard content. http://www.autohotkey.com/docs/misc/Clipboard.htm
ClipSaved := ClipboardAll ; Save the entire clipboard to a variable of your choice.
;... here make temporary use of the clipboard, such as for pasting Unicode text via Transform Unicode ...
Clipboard := ClipSaved ; Restore the original clipboard. Note the use of Clipboard (not ClipboardAll).
ClipSaved = ; Free the memory in case the clipboard was very large.
Hope this helps.
Samuel

Here's the solution I currently use. Basically, it comes down to sending ctrl+c, waiting a certain timeout, then seeing if text was actually copied. If it wasn't, I know there is not selection.
There is no way, afaik, to avoid waiting a timeout, since Windows takes a certain time to perform the copy operation. I set the timeout to 0.15 seconds, so it isn't too bad.
Here's the function I use whenever I want to grab the contents of the clipboard, or check if it's empty. I always call this function first:
clipped_text :=
clip_empty := false
ClipSaved =
is_clipped := false
clip_speed := 0.15
Clip() {
global ClipSaved
global clip_empty
global clipped_text
global is_clipped
global clip_speed
if (!is_clipped) {
ClipSaved := ClipboardAll ; Save the entire clipboard to a variable of your choice.
; msgbox % ClipSaved
is_clipped := true
}
clipboard = ; Empty the clipboard
Send ^{c}
ClipWait clip_speed
if (ErrorLevel = 1)
{
clip_empty := false
}
else
{
clip_empty := true
clipped_text := clipboard
}
}
And I use this function to actually get the contents of the clipboard or check if it's empty:
IsTextSelected() {
global ClipSaved
global clip_empty
global clipped_text
if (clip_empty == true) {
return true
}
else {
return false
}
}
To get the contents of the clipboard I just look at the clipped_text variable.
After performing a "Clip()" operation, I always call the following function to restore the clipboard (this function is called once for multiple calls of Clip()):
UnClip() {
global ClipSaved
global clip_empty
global clipped_text
global is_clipped
is_clipped := false
Clipboard := ClipSaved
ClipSaved =
}

While not an actual answer to this question, a google search might lead you here if you are looking for a way to catch text on paste and modify it before pasting.
Here's the script which eliminates whitespace from text pasted from clipboard on CTRL + V:
~^v::
Trimmed := RegExReplace(Clipboard, "^\s+", "")
Trimmed := RegExReplace(Trimmed, "\s+$", "")
Clipboard = %Trimmed%
SendInput ^v
return

I think I have a solution. Set aside current clipboard, then copy. Compare what you have copied to an empty string.. if it's equal, then something was copied; otherwise, nothing was copied. At then, restore the clipboard to what you saved. Here is a code sample demonstrating the principle.
^#x::
ClipSaved := ClipboardAll ; Save the entire clipboard to a variable of your choice.
; ... here make temporary use of the clipboard, such as for pasting Unicode text via Transform Unicode ...
Clipboard := ; Clear the clipboard
Send, {CTRLDOWN}c{CTRLUP}
if (Clipboard = "") {
Send, you copied nothing
} else {
Send, you copied something
}
Clipboard := ClipSaved ; Restore the original clipboard. Note the use of Clipboard (not ClipboardAll).
ClipSaved = ; Free the memory in case the clipboard was very large.
return
Actually, I was hoping that there is another way to simply test if the cursor is currently selecting anything. I have asked this question on the AutoHotkey forums (http://www.autohotkey.com/forum/posting.php?mode=reply&t=69468), but until or if there is a better answer, I will use the above method.

Script for babylon (Middle Mouse Key for firefox):
MButton::
SetTitleMatchMode, 2
send {LButton}{LButton}
Send ^c
sleep, 100
send {F10}
sleep, 100
SendInput {Raw}%clipboard%
send {enter}
Return

I had the same problem - I would send the copy command, but it wouldn't copy anything. I tried working with timers to no avail.
Here is what I ended up doing (trying different modes):
thisclipboard := clipboard . a_now ;add NOW so that it won't possibly be the same as the contents of the clipboard
sendplay,^c
if(clipboard == thisclipboard){
sendinput,^c
}
if(clipboard == thisclipboard){
send,^c
}

Maybe you should hotkey the Ctrl + C instead, that way any time that hotkey is pressed you will know.
You might want to make sure to send the normal Ctrl + C action to windows so you can copy.
consider this example:
~^c::
msgbox, % "Clipboard Changed even if you didnt copy anything"
. "(...not really but you tried at least)"
return
That message will fire up every time you press Ctrl + C even if you didnt copy anything to the clipboard. At the same time you will be sending the native function of Ctrl + C to windows so your clipboard WILL change if you copied something.
From the help file:
~: When the hotkey fires, its key's native function will not be blocked (hidden from the system).
You might want to also have an onClipboardChange to check when the clipboard really changed.

Related

cmd converts em-dash to hyphen on pasting. Any workaround?

I want to be able to paste file paths into cmd with em dashes (—, alt 0151) in them.
cmd converts them to ones where the em dashes have been replaced by a hyphen.
Manual input:
(Keyboard) D:\—\image.png
(cmd) D:\—\image.png
Entering this would open the file as expected.
Pasted input:
(Clipboard) D:\—\image.png
(cmd) D:\-\image.png
Entering this would give me an error because a directory named hyphen doesn't exist.
This is baffling because the file system supports paths to have such a character - I can access this file if I type the path manually, and programs can open it just fine.
Why convert a character that is supported? If it wasn't supported when the conversion was added, why not remove the conversion when the support was added?
More importantly, how can I work around this while keeping the em dashes? I have programs that depend on such paths and it'd be inconvenient to change them in all of them.
Similar to:
How to deal with an em dash in a filename
Using “En Dash” in an input file to a batch file
Rename Files having EmDash using a Batch File
Changing the code page made no difference.
My workaround was to create an AutoHotKey script to parse the path being pasted and to send alt 0151 whenever it encounters an em dash.
It could be faster, but it works - which is miles better than receiving an error.
#SingleInstance, force
numpad7::
tooltip, exited!
Clipboard := stored
sleep, 300
exitapp
#IfWinActive ahk_exe cmd.exe
$^v::
cliptext := clipboard
stored := ClipboardAll
StringGetPos, garbage, cliptext, —
garbage =
if !ErrorLevel {
Loop, Parse, cliptext
{
char = %A_LoopField%
If (char == "—") {
clipboard := sentence
sendinput, ^v
SendInput {alt down}{numpad0}{numpad1}{numpad5}{numpad1}{alt up}
sentence := ""
} else if (char == "") {
char := " "
gosub define_sentence
} else {
gosub define_sentence
}
}
; send sentence when EOL
gosub define_sentence
Clipboard := sentence := SubStr(sentence, 1, -1)
sendinput, ^v
sleep 200
Clipboard := stored
stored =
sentence =
return
} else {
sendinput, ^v
}
return
define_sentence:
sentence := sentence . char
tool := "s= " . sentence . "`n" . "c= " . char
tooltip, %tool%
return
On another note, the highlight.js for autohotkey doesn't seem to work which is great.

How to handle optional windows in Autoit?

I am automating a software installation in Windows7 using AutoIt.
During the installation, in between if a error window appears. I want to click ENTER.
If the error window not appears then I should NOT do anything. Simply its should go to the next section.
I have tried "WinActive and WinWaitActive" But its waiting for the window to appear. If window not appears its not going to the next screen.
Any idea how to handle this situation?
Do a while loop:
$w = 0
While($w = 0)
If(WinActive("ERROR WINDOW"))Then
Send("{ENTER}")
$w = 1
ElseIf(ControlGetText("YOUR WINDOW", "", "[CLASS:Static; INSTANCE:2]") <> "SOME TEXT") Then
$w = 1
;and something else
EndIf
Sleep(1000)
WEnd
AdlibRegister() is the right choice. From the help file:
"... typically to check for unforeseen errors. For example, you could use adlib in a script which causes an error window to pop up unpredictably."
Each 100 ms (may be adjusted) the function is called to check the appearing of your error dialog:
Global $sErrorWindow = 'ErrorDialogName'
Global $iDelayHowOftenDoTheFunctionCall = 100
AdlibRegister('_isErrorWindowDisplayed', $iDelayHowOftenDoTheFunctionCall)
Func _isErrorWindowDisplayed()
If WinActive($sErrorWindow) <> 0 Then
WinActivate($sErrorWindow) ; just to be sure that the ENTER command is on the correct window/dialog
; either do
Send('{ENTER}')
; or
ControlClick('title', 'text', 'controlID')
EndIf
EndFunc
; do your software installation processing here
; ...
; ...
; don't forget to unregister the function at the end
AdlibUnRegister('_isErrorWindowDisplayed')

Navigate Shell command not working when the path includes an hash

I'm having problem using the Navigate Shell command when the path include an # sign.
; this will create 2 folders at the root of your C: drive
myPath1 := "C:\delete_me\"
myPath2 := "C:\delete#me\"
if !FileExist(myPath1)
FileCreateDir, %myPath1%
if !FileExist(myPath2)
FileCreateDir, %myPath2%
; make an Explorer active and press Alt-1 and Alt-2
return
!1::
strWinId := WinExist("A")
TrayTip, %myPath1%, %strWinId%
For pExp in ComObjCreate("Shell.Application").Windows
if (pExp.hwnd = strWinId)
try pExp.Navigate(myPath1)
return
!2::
strWinId := WinExist("A")
TrayTip, %myPath2%, %strWinId%
For pExp in ComObjCreate("Shell.Application").Windows
if (pExp.hwnd = strWinId)
try pExp.Navigate(myPath2)
return
Alt-1 works well. But, with Alt-2, the Navigate command returns "file:///C:/delete#me/ » not found.".
If there is no "/" after the "#" (eg myPath := "C:\delete#me"), it works. But this cannot be a solution because the destination path can be deeper in a subfolder (eg. "C:\delete#me\xyz").
I tried to encode the "#", replacing it with "%23", without success. Found nothing on the web or MSDN about that. Any idea?
[keywords: haskmark, hashtag, number sign or pound]
I have what looks to be a working solution for this, which I've also posted here:
4 options to change the current folder in Windows Explorer - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=526&p=153676#p153676
;links:
;Explorer Windows Manipulations - Page 5 - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/19039-explorer-windows-manipulations/page-5#entry297581
;Navigate2 Method (IWebBrowser2)
;https://msdn.microsoft.com/en-us/library/aa752134(v=vs.85).aspx
;4 options to change the current folder in Windows Explorer - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=526
;windows - Navigate Shell command not working when the path includes an hash - Stack Overflow
;https://stackoverflow.com/questions/22868546/navigate-shell-command-not-working-when-the-path-includes-an-hash
;an AutoHotkey v1.1 script
;note: will create folder: %A_Desktop%\abc#def\abc#def
;q:: ;explorer - navigate to folder (tested on Windows 7)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if !(vWinClass = "CabinetWClass") && !(vWinClass = "ExploreWClass")
return
vDir = %A_Desktop%\abc#def\abc#def
;vDir = %A_Desktop%\abc def\abc def
if !FileExist(vDir)
FileCreateDir, % vDir
DllCall("shell32\SHParseDisplayName", WStr,vDir, Ptr,0, PtrP,vPIDL, UInt,0, Ptr,0)
for oWin in ComObjCreate("Shell.Application").Windows
if (oWin.HWND = hWnd)
{
if !InStr(vDir, "#")
oWin.Navigate(vDir)
else
{
VarSetCapacity(SAFEARRAY, A_PtrSize=8?32:24, 0)
NumPut(1, SAFEARRAY, 0, "UShort")
NumPut(1, SAFEARRAY, 4, "UShort")
NumPut(vPIDL, SAFEARRAY, A_PtrSize=8?16:12, "Ptr")
NumPut(DllCall("shell32\ILGetSize", Ptr,vPIDL, UInt), SAFEARRAY, A_PtrSize=8?24:16, "Int")
oWin.Navigate2(ComObject(0x2011,&SAFEARRAY))
DllCall("shell32\ILFree", Ptr,vPIDL)
}
break
}
return
If you want to open a new window, there's no need for COM or unreliable workarounds: just run the folder.
Run C:\delete#me
If you want to open the path in an existing window which is already active, the simplest and most effective workaround is this:
SendInput {F4}{Esc}{Raw}C:\delete#me`n
So in the context of your script, you could use the following function to work around the # when it is present:
Navigate(pExp, myPath2)
;...
Navigate(Exp, Path)
{
if RegExMatch(Path, "#.*\\")
SendInput {F4}{Esc}{Raw}%Path%`n
else
Exp.Navigate(Path)
}
Unfortunately, there does not seem to be a solution to this. Shell.Application Navigate command fails if the path includes a hash (# as in C:\C#Projects).
Using AutoHotkey, the workaround would be to rely on the "second best" approach as identified by the tests in this thread: http://ahkscript.org/boards/viewtopic.php?f=5&t=526.
run, Explorer.exe
Sleep, 500
strFolder := A_ScriptDir
Send, {F4}{Esc}
Sleep, 500
ControlSetText, Edit1, C:\delete#me, A
ControlSend, Edit1, {Enter}, A
When I saw that Navigate couldn't handle hash, I was shocked,
but sure enough I replicated the error.
I thought I'd try the short form path just in case. It works!
if vDir contains #
Loop, %vDir%, 2, 0 ;(0/1/2=files/both/folders, 0/1=recurse no/yes)
vDir := A_LoopFileShortPath
The following approach doesn't require a visible address bar, or SendInput,
also the previous navigation history is maintained.
In the worst-case scenario of a hash in the short-form path of the dir above the target dir,
a go-between folder is used which is navigated to.
A link is created there, invoked, and deleted.
Below, the workaround code is indented, to separate it from the standard code.
A hotkey of ctrl+q, when an Explorer window is active, launches the script.
-
^q:: ;explorer - navigate to directory (use go-between dir if short-form path of dir above target contains #)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, ahk_id %hWnd%
if vWinClass not in CabinetWClass,ExploreWClass
Return
vDir2 = %A_Desktop%\Go-Between ;go-between dir
vDir3 = C:\delete#me ;target dir
if (SubStr(vDir3, 1-1) = "\")
vDir3 := SubStr(vDir3, 1, -1)
if !InStr(FileExist(vDir3), "D")
Return
vPathLnk := ""
if vDir3 contains #
Loop, %vDir3%, 2, 0 ;(0/1/2=files/both/folders, 0/1=recurse no/yes)
vDir3 := A_LoopFileShortPath
;vDir4 is the short-form path of the dir above the target
;paths of problem target dirs are of the form: *#*\*
;where there is at least one hash with a backslash to its right
SplitPath, vDir3, , vDir4
if vDir4 contains #
{
if !InStr(FileExist(vDir2), "D")
FileCreateDir, %vDir2%
if !InStr(FileExist(vDir2), "D")
{
MsgBox error`, go-between dir not found:`r`n%vDir2%
Return
}
vNameLnk = Go-Between.lnk
vPathLnk = %vDir2%\%vNameLnk%
FileCreateShortcut, %vDir3%, %vPathLnk%
}
for oWin in ComObjCreate("Shell.Application").Windows
if (hWnd = oWin.Hwnd)
{
vDir1 := oWin.Document.Folder.Self.Path
if (vDir1 = vDir3)
break
if vDir3 contains #
{
if !(vDir1 = vDir2)
oWin.Navigate(vDir2)
while !(oWin.ReadyState = 4)
Sleep 10
oItem := oWin.Document.Folder.Items.Item(vNameLnk)
oItem.InvokeVerbEx("open")
break
}
oWin.Navigate(vDir3)
break
}
oWin := ""
if !(vPathLnk = "")
FileRecycle, %vPathLnk% ;send to recycle bin
;if !(vPathLnk = "")
;FileDelete, %vPathLnk% ;delete
Return

AutoHotkey able to capture firefox tab?

It is a bit beyond of my knowledge, so I copy&pasted the whole script.
But I was rejected with a message, which reads, (Firefox version is 28.0) Anybody please help me.
Error: Call to nonexistent function.
Specifically: Acc_Get( ... ...
SetTitleMatchMode 2
WinGet, windows, List, Mozilla Firefox
Loop %windows% {
hwnd := windows%A_Index%
;// Acc_Get(Cmd, ChildPath="", ChildID=0, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="")
page_tab_list := Acc_Get("object", "application.grouping2.property_page.tool_bar3.page_tab_list", "", "ahk_id" hwnd)
For Each, tab in Acc_Children(page_tab_list)
if tab.accName(0) = "https://www.apple.com/" {
tab.accDoDefaultAction(0) ;// remove line to NOT activate tab
WinActivate ahk_id %hwnd%
break 2
}
}
This script simply walks through all tabs until it finds the correct one, via the page title in the window title. Sleep timer can be adjusted.
SetTitleMatchMode 2
needle := "Stack Overflow"
WinActivate, Firefox
Loop {
WinGetTitle, title
IfWinNotActive, Firefox
break
if (InStr(title,needle))
Break
Else
send ^{PgUp}
sleep 50
}
You didn't take all the dependencies from the source. You are missing the required Acc Library.
; Acc.ahk https://github.com/sancarn/ACC.AHK/blob/master/AccV2.ahk
; ACC Tutorial: https://www.autohotkey.com/boards/viewtopic.php?f=7&t=40590

How to debug variables in Autohotkey without using MsgBox?

When debugging scripts, you often need to know the value of your variables to know exactly what is going on. Outputting variables with MsgBox to tackle this problem is annoying and inefficient.
Is there a function that can help me with debugging variables?
I want it to write down all my local variables, their names and corresponding values as well as the name of the current procedure being executed to an ini file. Are there debugging tools that can provide this functionality, possibly in real time?
The name of the procedure may be challenging to do, but is there a way that at least all the local variables can get enumerated and written away?
Take a look at the ListVars command. It lists all of the variables.
You can also get more extensive information using a method like this:
MsgBox, % GetAhkStats("lines")
MsgBox, % GetAhkStats("variables")
MsgBox, % GetAhkStats("hotkeys")
Stat1 := GetAhkStats("history")
MsgBox, %Stat1%
Return
a::a
b::b
c::c
d::d
GetAhkStats(xxSection="", xxUseWindow=99, xxDestroyAfter=1)
{
xxSectionN = Lines|Variables|Hotkeys|History
If xxSection=
xxSection = History
Loop, Parse, xxSectionN, |
IfInString, A_LoopField, %xxSection%
xxSection = %A_Index%
DetectHiddenWindows, On
SetTitleMatchMode, 2
Gui, %xxUseWindow%:Show, Hide
xxHidWin := WinExist(A_ScriptFullPath " - AutoHotkey v")
xxOldpar := DllCall("GetParent", "UInt", xxHidWin)
DllCall("SetParent", "UInt", xxHidWin, "UInt", (GuiGetHWND("", xxUseWindow)))
WinMenuSelectItem, ahk_id %xxHidWin%,, View, %xxSection%&
Loop {
Sleep, 50
ControlGetText, xxOut1, Edit1, ahk_id %xxHidWin%
If xxOut1<>
break
}
WinHide, ahk_id %xxHidWin%
DllCall("SetParent", "UInt", xxHidWin, "UInt", xxOldpar)
If (xxDestroyAfter)
Gui, %xxUseWindow%:Destroy
Return, xxOut1
}
GuiGetHWND(xxClassNN="", xxGUI=1)
{
If (xxGUI)
Gui, %xxGUI%:+LastFound
xxGui_hwnd := WinExist()
If xxClassNN=
Return, xxGui_hwnd
ControlGet, xxOutputVar, Hwnd,, %xxClassNN%, ahk_id %xxGui_hwnd%
Return, xxOutputVar
}
Source
listvars does the trick for me.

Resources