2014年8月16日 星期六

Autohotkey: 基本面

前言

  • 講了幾篇後,朋友試寫script一直遇到問題,我才發現最基本的東西沒講 orz
  • 其實最穩的方式還是讀autohotkey的說明檔,尤其autohotkey_L有簡體中文版本的說明,英文苦手可以考慮。
  • 以下列舉一些要點,但如果沒有詳細看說明檔還是可能會遇到問題,總之見招拆招!

環境設定

  • 之前這篇有提到過,目前所用的免安裝版主程式是Autohotkey_L Unicode 32bit,編輯器是SciTE4AutoHotkeySciTE的資料夾應該要放在Autohotkey內。如果都是安裝版,要先安裝autohotkey再裝SciTE,SciTE會自動裝到適當的地方。
  • 副檔名ahk的檔案本質上就是純文字檔,滑鼠點兩下可能會發生幾種事情:
    • 如果有安裝autohotkey、SciTE,可以直接執行這個script,或者直接打開SciTE編輯ahk檔案。
    • 如果用免安裝版,剛下載下來時電腦並不認得ahk檔,要選擇開啟ahk檔的程式(autohotkey、SciTE或者任何一種文字編輯程式)。
    • 如果是像我一樣裝在隨身碟裡到處跑,通常的流程是打開SciTE,然後直接把ahk檔拖曳到SciTE裡開啟,debug或執行都可以直接用SciTE完成(所以才說SciTE資料夾和autohotkey.exe要放一起)。
  • 在沒有裝autohotkey的電腦要能直接執行script,就要把ahk檔「編譯」(compile)成exe檔,其實本質上就是把autohotkey主程式和純文字script綁在一起。
    • 在SciTE裡按Ctrl + F7可以compile(其實是用ahk2exe.exe來完成的),產生的exe檔和ahk檔在同一資料夾。
    • Compile有時候會出現錯誤,常見的原因包括原本的exe還在執行中(要先關閉),或是電腦中執行的程式衝到,後者就不容易解決了…
    • 理論上exe檔並沒有什麼加密的功能,原始的程式碼其實還是能復原,ahk2exe.exe加入參數能讓compile時加入密碼,保密性會比較好一些。
    • EXE檔可以附加客製化的圖示(ico檔,128*128 pixels以下),通常可以用icon編輯軟體,搭配這個網站,然後用這個把icon加進exe檔。

Script結構

  • 通常最開頭會用#include來引用前人的智慧結晶,例如同個資料夾func.ahk裡面有一些函數定義,#include func.ahk就相當於func.ahk裡面的內容複製貼上到#include這一行。
    • 也較好的方法是在ahk檔同一層或者主程式Autohotkey.exe同一層新增資料夾Lib,然後把func.ahk放進去,這樣的話可以用語法#include <func>
  • 其他#開頭的指令也都會優先處理,常用的有
    • #NoTrayIcon:程式執行時不會在螢幕右下角那區出現小圖示,所以TrayTip這個指令也會無效。
    • #Persistent:如果script沒有hotkey、hotstring、GUI,程式執行完就會自動結束,加了這指令,程式不會直接結束變成idle狀態。
    • #SingleInstance:如果程式執行中,又再滑鼠點兩下同一個exe檔,會出現message box問是否取代原本執行中的程式,有這個指令則可以讓message box不出現,參數force、ignore、off分別對應「新的取代舊的」、「繼續執行舊的,不執行新的」、「新的執行新的,舊的執行舊的」。
  • Hotkey、hotstring、function定義的block可以放在script裡任何地方皆有作用,但放的地方不對可能讓其他非hotkey/hotstring的script沒有執行到(下面會說明),建議把同類型的放在一起,而且都放在script底端的位置。
    • SciTE中,在某個label或function名稱按滑鼠中鍵或Shift + Enter,可以直接跳到label或function定義的地方;或者在空白處按,會出現查詢框。
  • 想像script中把hotkey、hotstring、function定義的block都拿掉後,留下的script(auto-execution section)會從最上面開始執行,直到遇到return、exit或是hotkey/hotstring
    • 如果#include裡的檔案有空閒的return(不是hotkey、hotstring、function內的),那自己的script就等於不會執行到。
    • 如果是先用gosub跳到subroutine,subroutine的return會回到剛才gosub的那行,script不會停下來。

Threads

  • 理論上同一個process中可以有一個以上的thread(執行緒),每個執行緒可以各自執行自己的任務,但也有相對應的缺點。
  • Autohotkey_L只有單一執行緒,在auto-execution section執行中跳出message box、input box,程式就相當於被暫停,或者執行中遇到一個耗費時間的loop(甚至根本是無窮迴圈),那程式就沒辦法做其他事。
  • 事實上Autohotkey_L會模擬多執行緒,有一些block會以新的thread來執行,包括hotkey/hotstring、GUI events、SetTimer引發的subroutine、menu引發的subroutine。所以說如果程式卡在loop或是message box,hotkey還是能被trigger,SetTimer反覆執行的subroutine還是繼續。
  • 有些外掛可以更模擬多執行緒,例如WorkerThread(事實上是藉由重覆執行exe檔)。只有Autohotkey_H才能使用真正的執行緒。

語法速成

var = abc
    • abc這個字串儲存到var變數。
var = %var2%
    • var2這個變數代表的值取出,然後儲存到var變數。
if a = 2
{
    …
}
    • 比對變數a的值是否等於2,大括號不能和if在同一行。
MsgBox test
    • 跳出message box,內容是test。
InputBox,OutputVar,%title%
    • 出現input box,標題為變數title的值,注意OutputVar這個參數是一個變數的名稱,通常不用%,除非要用的變數的名稱是存在某一個變數。
  • Non-expression:
var := "abc"
    • abc這個字串儲存到var變數,要用運算元 := 而且字串前後要加雙引號。
var := var2
    • var2這個變數代表的值取出,然後儲存到var變數。
if (a = b) {
    …
}
    • 比對變數a的值是否等於變數b的值,整個條件式要有小括號圍起來才視為expression,大括號可以和if在同一行。
MsgBox % test
    • %後面加空格,會視為expression。
MouseClick, L, % x, % y, 1
    • %後面加空格,作用的範圍只有那一個參數。
  • Autohotkey官方版的array純粹是單純的變數,只不過變數名稱最後一個字元是數字,autohotkey_L支援object(物件)的array,使用起來比較有效率。主要有幾個重點:
    • 一般的array以 [ ] 括起來,例如arr := [ 5, 4, "3" , "abc" ],index從1開始遞增,所以arr[2]的值是4,而arr[4] := "bcd"會蓋過本來的"abc"。
    • Associative array有點像dictionary,指定一個key對應一個value,例如 arr := { 1: "a", k: v },以大括號括起來,key和value間以冒號隔開,key的部分是non-expression,預設視為字串,value的部分是expression,arr["k"]儲存的是變數v的值。

2014年8月13日 星期三

Autohotkey: Hotkey

前言

  • 從名稱可以知道Autohotkey的強項就是hot key,所謂熱鍵、快速鍵。控制滑鼠、視窗也是autohotkey的常用功能,用簡單的語法就能組合完成複雜的動作。
  • 百般task皆可由一個hot key引發,hot key可以是一個按鍵、多個按鍵、一串特定按鍵,可以是某個滑鼠鍵、多個滑鼠鍵,或是鍵盤搭配滑鼠鍵。可以指定在某些視窗才有快速鍵,或是某些視窗沒有快速鍵。

Hotkey

  • 之前的post提過Send可以模擬按出某個鍵(鍵盤、滑鼠),hotkey就是以這些鍵當作trigger。
  • Hotkey的第一行定義trigger的按鍵,send指令需要的大括號 { } 在hotkey要省略,然後後面加兩個冒號 :: 
  • 如果指令只有一行(包括以小括號包圍的段落文字),則可以寫在冒號右邊;如果是多行則最後要用return表示hotkey的結尾,開頭到結尾間可以有其他return,只要一遇到return這個hotkey就會結束。
  • Hotkey定義之後原本的功能會被覆蓋,例如LButton::,會讓autohotkey抓住滑鼠左鍵,但不做任何事,原本的滑鼠左鍵功能就消失了(請勿輕易嘗試XD)。
  • Hotkey之前可以有修飾符,調整hotkey的行為
    • *:例如^c::的hotkey只有ctrl + c才會trigger,多按一個鍵shift + ctrl + c則不會,但*^c::則只要ctrl + c有按到,多按shift或其他按鍵都還能trigger。
    • ~:原本hotkey的功能保留,不會被覆蓋
    • $:如果是^c::send ^c會出現無限迴圈,因為send出去的按鍵又trigger自己,$則避免出現迴圈。
    • UP:一般hotkey的trigger是在「按下」的階段發生(某些功能鍵除外),如果希望hotkey是在按鍵「放開」時發生,則在後面加一個up (不分大小寫)
      • ^c up::會在c放開時才執行。
      • 雖然只指定了^c up::,原本的^c還是會被覆蓋,所以如果要保留原本功能則用~^c up::或是另外再設定^c::send ^c(這時不需要加$)。
  • 組合鍵:只能組合兩個,舉例如下
    • a & b:::先按住a不要放掉,再按b可以觸發,有點像是按ctrl + c之類快速鍵的順序
    • 前面的按鍵a功能會被覆蓋,只按a不會做任何事,彌補的方法一樣是加一個~或是設定a::send a(兩者的行為不一樣,比較建議後者)
  • Suspend指令可以暫時停止所有的hotkey及hotstring(除了造成suspend的那個hotkey或hotstring),再一次suspend指令則會回復
  • Context-sensitive hotkey:以#IfWinActive包圍的hotkey,只有在指定的視窗會有作用
  • Hotkey本身就是一個label(不含兩個冒號::),可以用gosubgosub直接執行一個hotkey。例如$^c::可以用gosub $^c來執行,不用去按ctrl + c。

KeyWait

  • KeyWait可以設定微調hotkey,功能是等待某個按鍵放開或按下。
  • KeyWait, a 會等待按鍵a被放開
  • KeyWait, LButton, D 會等待滑鼠左鍵按下
  • KeyWait, Ctrl, T3 會等待Ctrl鍵放開,但只會等待3秒鐘,如果超過時間沒放開,ErrorLevel這個變數會是1;如果時間內放開,ErrorLevel會是0而且繼續執行script,不會等到3秒鐘。

GetKeyState

  • KeyWait一樣,GetKeyState也是輔助hotkey的小函數。有兩種形式的用法。
GetKeyState, OutputVar, KeyName [, Mode] 
  • 針對KeyName是按下還是放開,將OutputVar設定成 D U Mode中可以設定用哪一種方式判斷按鍵的狀態
    • Logical state:預設值,windows所認為的這個按鍵的狀態
    • Physical state:使用者是否按著這個按鍵,可能會和logical state不相同。如果script有mouse hook或keyboard hook,就能完全監測滑鼠/鍵盤,physical state可以當成等於logical state。
    • Toggle state:像是Caps Lock或Num Lock,如果用toggle state則是傳回目前的燈號,燈號亮的時候傳回 D ,反之傳回 U 。
KeyIsDown := GetKeyState("KeyName" [, "Mode"])
  • 用法類似,唯一的不同是剛才傳回 D 的地方現在會傳回 1 ,傳回 U 變成傳回 0

Double Click/Keyboard Event

  • 前面的hotkey都是按一下就會trigger,如果想要的hotkey是滑鼠左鍵「按兩下」,或是Esc鍵「按兩下」,可以加一行判斷式:
if (A_PriorHotkey = A_ThisHotkeky && A_TimeSincePriorHotkey < 500)
  • 白話翻譯就是「上一個trigger的hotkey和現在trigger的hotkey是同一個」而且「從上次trigger到現在的時間差小於500毫秒」,所以只要同一個鍵按兩下,時間差小於500毫秒,就會通過判斷式,如果不成立,就return回去。當然,500毫秒可以改成其他時間。
  • 如果hotkey需要按三次以上,建議可以用RapidHotkey()

Dynamic Hotkey

  • 之前的hotkey都是直接寫好在code裡,程式執行後就一直存在,如果要在程式執行時改變hotkey,就要用Hotkey指令。
Hotkey, KeyName [, Label, Options]
Hotkey, IfWinActive/Exist [, WinTitle, WinText]
  • KeyName是按鍵組合,就是上面提到的那些,Label則是要執行的區塊名稱(所以這部分還是要先寫在code裡)。
  • 多個hotkey可以共用同一個label,在區塊中可以用A_ThisHotkey變數區分是誰trigger的。
  • 按鍵組合一樣,但修飾符不一樣,或者在不同IfWinActive條件下的同樣按鍵組合,視為不同的hotkey。如果設定了重覆的hotkey,新的功能會覆蓋舊的功能。

2014年8月8日 星期五

Regular Expression入門

前言

  • Regular expression一般翻譯做「正規表達式」之類的,但其實翻譯不重要,因為看了翻譯還是不知道他的用途。
  • 在字串裡搜尋字串是大家很常用的功能,例如在硬碟內找某個檔案,或者是在文章內找某個字。
  • 但更常用的是搜尋「某個pattern」,例如想要找「連續7個數字」或是「三個字母接一個特殊字元,後面又有1或2個數字」,就需要用到regular expression!
  • 很多程式語言(或者script)都會有regular expression,使用上大同小異,花點時間學習這些火星文,一生受用無窮。
  • 以下以autohotkey環境來講解,其實大部分也是翻譯自autohotkey的官方文件
  • 測試regular expression的工具

RegExMatch

  • 在開頭先簡介autohotkey內所用的函數RegExMatch,用法是
FoundPos := RegExMatch(Haystack, NeedleRegEx [, UnquotedOutputVar = "", StartingPosition = 1])
    • 一定要有的參數:Haystack是被搜尋的文字,Needle則是所搜尋的pattern,也就是底下要學的東西。
    • 選擇性參數:match後的結果會儲存在OutputVar變數裡,StartingPosition可以指定從Haystack的第幾個字元開始搜尋。
    • 這個函數會傳回第一個找到pattern的字元位置,如果沒找到的話是 0 。
    • 相較於autohotkey很多函數/變數是大小寫不區分,regular expression預設是區分大小寫的。

Regular Expression

  • 一般字元
    • 大部分的字元都可以直接用在regular expression內,搜尋起來就是和傳統的搜尋文字一樣,例如 RegExMatch("abcde","bcd") 會傳回 2 。
  • 特殊字元
    • 在regular expression裡有特殊意義,要用 \ 去escape,才會是原本的字元:\ . * ? + [ { | ( ) ^ $ 
    • 例如要找中括號,不能用 RegExMatch("()[]{}","[") ,而是要用 RegExMatch("()[]{}","\[") 
  • Symbols
    • .:一個點表示除了斷行以外的任何一個字元。
    • \d:表示任何一個數字,也就是0到9。如果要表達「一個非數字」,則用\D (大寫)。
    • \w:表示任何一個文字,包括數字、大小寫的a到z,還有底線 _ 。如果要表達「一個非文字」,則用\W (大寫)。
    • \s:表示某種空白字元,包括空白、tab (\t),斷行 (\r\n)等。如果要表達「一個非空白」,則用\S (大寫)。
    • [...]:一對中括號內為候選字,正面表列
      • [abc]:找某一個字元,可以是a,也可以是b或是c。
      • [a-z]:找某一個字元,可以是a到z。同理可以用在[A-Z][0-9],或是組合用[b-s4-7]
      • 候選字中某些特殊字元可以不用escape,例如 * ? + 都不用加 \ ,當然如果怕搞混加上去也沒關係。
      • [^...]:最前面標一個^表示是黑名單,找一個字元且這個字元不在黑名單中。
  • 數量詞
    • 前面的Symbol都是指單一字元,數量詞一定接在某個symbol右邊,也就是說數量詞作用在左邊那一個symbol。
    • *:表示0個或是0個以上。
    • ?:表示0個或是1個。
    • +:表示1個或是1個以上。
    • {min,max}:表示min個以上、max個以下。例如a{2,3}表示連在一起的兩個或三個a。
  • Syntax
    • \b:表示一個「文字邊界」,例如 cat\b 不會match到 catfish ,因為cat是fish連在一起的。\B(大寫)則表示「非文字邊界」。
    • ^ $:把^放在整個pattern的最前面表示「開頭」,把$放在整個pattern的最後面表示「結尾」,例如 ^bcd abc$ 都無法match到 abcd
    • |:表是「或」,例如 (Sun|Mon|Tues|Wednes|Thurs|Fri|Satur)day 可以match到 SundayMonday 或 ... 或 Saturday
    • ( ):小括號一方面可以讓pattern比較容易閱讀,一方面被括起來的部分會成為一個submatch。
  • Options
    • 在pattern的左邊,以一個右括號)隔開option和pattern,各個options可以連起來寫,或是用空白、tab分開。
    • i:表示case insensitive,例如 i)abc 可以match到 ABC
    • m:表示multiline模式,影響的是^$,在多行文字時本來表示「全部文字」的頭尾,會變成「一行文字」的頭尾。
    • s:讓一個點.表示「所有」字元,包括斷行。
    • U:ungreedy
      • 預設情況下如果是不定數量的數量詞* + ? { },regular expression會符合「盡量多」的字元,所以 1\d+1 會match到全部的 1000010000101,稱作greedy
      • 如果在數量詞後加一個問號?,則變成ungreedy1\d+?1 只會match到 100001。
      • 如果有U設定,則預設為ungreedy,加問號變greedy。
  • Submatch (subpattern)
    • 剛才提到小括號可以形成submatch,例如 RegExMatch("zzabcdefzz","ab(.*)e(f)",match) match變數會儲存 abcdefmatch1變數會儲存 cdmatch2變數會儲存 f
    • 如果小括號只是為了語法,而不是為了submatch,可以用(?:  )表示這個括號不是submatch,例如 RegExMatch("zzabcdefzz","ab(?:.*)e(f)",match) match變數會儲存 abcdefmatch1變數會儲存 f
    • (<?P<...>   ):Named subpattern,例如 RegExMatch("20140102","(?P<Year>\d{4})(?P<Month>\d{2})(?P<Day>\d{2})",match) 執行後,match20140101,而matchYear變數是2014matchMonth變數是01matchDay變數是02
  • Assertion
    • 雖然是用小括號,但不是submatch也不包含在match裡,僅僅是當成一個條件。
    • (?=   ):positive look ahead,例如 a(?=b) 會match一個a,這個a要接著b。
    • (?!   ):negative look ahead,例如 a(?!b) 會match一個a,這個a後面不能接著b。
    • (?<=  ):positive look behind,例如 (?<=a)b 會match一個b,這b左邊接著a。
    • (?<!  ):negative look behind,例如 (?<!a)b 會match一個b,這b左邊不能接著a。

Further Reading

2014年7月9日 星期三

自己寫武功祕笈:Radiologist的第一個Autohotkey

開發環境

主程式

  • 官方版autohotkey
  • Autohotkey_L:根據官方版加入更多功能,例如unicode支援、64位元支援、物件導向,而且有提供簡體中文的說明檔
  • 以上都有提供portable版本,免安裝,解壓縮後就可以直接用

Script File

  •  只有主程式沒用,還需要script才能做事,script的副檔名是ahk,但本質上就是文字檔,可以用任何文字編輯軟體打開撰打
  • 同樣是文字檔會有不同的編碼方式,原PO儲存時都使用UTF-8 (with BOM),對於中文字或是其實特殊文字比較不會變亂碼

編輯器

  • 工欲善其事,必先利其器,好的編輯器可以讓寫程式更有效率,包括偵測錯誤、函數提示、顏色標示
  • 原PO使用的是SciTE4Autohotkey,一樣有分安裝版和portable版

寫程式

前言

  • 所有程式都能做很簡單或是很複雜的事,autohotkey也是,尤其autohotkey特別針對日常使用電腦需要用到的功能,寫起來非常有效率,用最少的程式碼做最多的事
  • 本篇主要介紹血汗國家(地區?)的放射科醫師,靠著autohotkey在夾縫中求生存的一段故事

模擬鍵盤、滑鼠

  • 基本上打報告的所有動作,包括鍵盤上任何按鍵、滑鼠上任何按鍵和動作,都是可以模擬出來的,所以寫程式的第一步是思考、設計,「我要我的程式做什麼事情」,而且要step by step,分析每一個步驟
send
  • 官方詳細說明
  • 送出一個按鍵,例如:send blabla
  • 英文字母、數字、特殊符號可以直接送出,例如:send abc123&*?
    • 例外的是 ` ,要在字元前加一個`,也就是 send ``
    • ! # + ^ { } ,這些字元有特殊用途,要用大括號包圍起來,表示送出原本的字元而不是特殊用途,例如 send {!}{#}
  • 功能鍵(會自動和右邊一個按鍵變組合鍵)
    •  ^ 表示ctrl,例如 send ^c 就是按 ctrl + c
    •  ! 表示alt,例如 send !{F4} 就是按 alt + F4
    •  + 表示shift,例如 send ^+{esc} 就是按 ctrl + shift + esc
    •  # 表示視窗鍵(ctrl和alt中間那個),例如 send #e 就是按 win + e
  • 特殊字元
    • { } 包圍起來,族繁不及備載,可參考說明檔,大部分都很直覺,常用如下
    • {enter} {space} {tab}
    • {backspace} 或 {bs}、{delete} 或 {del}
    • {up} {down} {left} {right} {home} {end} {pgup} {pgdn}
    • {F1} ~ {F12}
    • 在數字盤的按鍵都是這個形式:{Numpad___}
  • 滑鼠按鍵
    • {LButton} {RButton} {MButton}
    • {WheelUp} {WheelDown}:滾輪
  • 重複同一個按鍵:send {space 10}會送出10個空白鍵

Hotstring & Hotkey & Settings


::cct::CT of the chest without and with contrast enhancement is read and compared with prior study dated . This CT study shows:{left 22}
  •  送出了這段文字,通常會將游標移到前面填入日期,所以在後面加上{left 22},將游標往左移動22次
  •  或者可以用^{left 5},因為 ctrl + left 會往左移動一個「字」而不是「字元」,但使用後的效果不如前一個方法精準
SendMode Input
  • 將send mode改成input模式,比較不會發生打報告打太快時,autohotkey送出的模擬按鍵和自己實際按的按鍵交錯送出
^!x::suspend
  • 打報告時還是常需要google查資料(&上facebook?),這時可以用suspend指令將這個script的所有hotstring和hotkey停用,繼續打報告時再按同一個快速鍵(也只有這個快速鍵不會被停用)回復
^!r::reload
  • 如果script在執行中,修改程式碼後要重新執行,可以直接用reload指令
^!e::
inputbox,addahk
if addahk!=
    fileappend,% addahk,% A_ScriptFullPath
reload
return

  • 跳出一個輸入框,輸入的文字(例如新的hotstring)會加到原本的script最底下,然後reload整個script,就能使用剛加入的hotstring
^!q::exitapp
  • exitapp指令可以離開整個script
~+enter::
~+NumpadEnter::
send -{space}

return
  •  bullet是項目符號,每個人用的都不一樣,原PO是用「- 」,設定快速鍵後按 ctrl + enter 可以跳到下一行並輸出項目符號

Dynamic Hotstring

  • 進階版的hotstring,見AHK論壇
  • 原始的hotstring是一對一的關係,打出一些特定的字跳出一些特定的字,dynamic  hotstring則是多對多的,打出某些pattern的字跳出某些pattern的字
  • 常見在一些列舉項目,例如left/right/bilateral、head/neck/chest/abdomen/pelvis,或是需要輸入某些數字,例如SE3 IM20,都適合用dynamic hotstring,舉一個例子…
#include hotstring.ahk

hotstring("([.\W])?(\d+)\.\.(\d+)(\W)","specifyImage",3)
return

specifyImage:
if $.value(1)="."
    send % "(SE" $.value(2) " IM" $.value(3) ")" $.value(4)
else
    send % $.value(1) "SE" $.value(2) " IM" $.value(3) $.value(4)
return

  • 下載Hotstring.ahk後放到自己的script的資料夾;自己的script(通常在最前面)要有一行#include hotstring.ahk載入函數
  • hotstring("([.\W])?(\d+)..(\d+)(\W)","specifyImage",3)要放在「第一個return」之前,因為return之後的程式碼不會被直接執行
    • ([.\W])?(\d+)..(\d+)(\W)表示要trigger的pattern
    • specifyImage表示trigger後要執行的段落標籤
    • 3表示pattern是用regular expression
  • 總之最後的效果是
    • 輸入 .3..20 ,輸出 (SE3 IM20) 
    • 輸入 3..20 ,輸出 SE3 IM20