# Quickstart ###### tags: `python` `autohotkey` 這邊假設你已經安裝好AutoHotkey.py。如果還沒有安裝,那就回頭看一下[Installation](https://hackmd.io/@shaoeChen/H1rwTGZoq)這一章。如果你需要復習一下Python,那就拜託你自己去看一下[The Python Tutorial](https://docs.python.org/3/tutorial/index.html)。 ## Command Line Interface 當你調用AutoHotkey.py的時候,你可以指定下面這些選項的隨便一個: ```shell ahkpy [-h] [-V] [-q] [--no-tray] [-c CMD | -m MOD | FILE | -] [args] ``` 當然,最常用的通常就是單純的調用一個腳本: ```shell ahkpy myscript.py ``` 當你用python可執行文件來調用[REPL](https://zh.wikipedia.org/zh-tw/%E8%AF%BB%E5%8F%96%EF%B9%A3%E6%B1%82%E5%80%BC%EF%B9%A3%E8%BE%93%E5%87%BA%E5%BE%AA%E7%8E%AF)接口的時候它的效果是最好的,這讓你可以用上下鍵來瀏覽你的命令的歷史記錄。 ``` python -m ahkpy ``` 如果你要在一個沒有終端視窗(terminal window)的情況下啟動AutoHotkey.py,那就要使用pyw: ``` pyw -m ahkpy myscript.py ``` AutoHotkey.py的接口類似於Python解釋器的接口。如果你想學習很多關於你的接口選項(interface options),那就請參考[Python文檔](https://docs.python.org/3/using/cmdline.html#interface-options)。 下面的CLI options是專門給AutoHotkey.py用的: * -q 抑制帶有錯誤的[訊息盒](http://terms.naer.edu.tw/detail/19384963/)(message box)。如果你沒有指定這個選項(option),那AutoHotkey.py把沒有處理的Python異常直接顯示在訊息盒裡面。 * --no-tray 不要在[系統拖盤](http://terms.naer.edu.tw/detail/19428209/)(system tray)中顯示AutoHotkey的icon。 在你啟動之後,AutoHotkey.py會依下面順序來尋找AutoHotkey可以執行的文件: 1. 首先,確定`AUTOHOTKEY`這個環境變數是否已經設置。如果設置了,那AutoHotkey.py就會用這個環境變數來做為AutoHotkey的可執行路徑。 2. 如果還沒有設置,那就會檢查Windows的註冊表,看看程序是不是設定在使用者開啟AHK文件的時候執行。如果程序的名稱是以autohotkey做為開頭(不分大小寫),那AutoHotkey.py就會用它來做為AutoHotkey的可執行路徑。 3. 否則,就預設路徑為`C:\Program Files\AutoHotkey\AutoHotkey.exe`。 :::info 注意: 跟AutoHotkey相比,整個腳本是在載入之後才會執行。也就是,沒有單獨的[auto-execute](https://www.autohotkey.com/docs/Language.htm#auto-execute-section)與hotkey/hotstring sections。熱鍵的註冊是隨著腳本逐行執行的。 ::: ## Hotkeys 函數`hotkey()`註冊著Hotkeys。在下面的範例中,我們把`Win`+`N`配置來啟動Notepad。井字號的話代表著`Win`,為熱鍵[修飾符](http://terms.naer.edu.tw/detail/19387424/)之一: ```python import subprocess import ahkpy @ahkpy.hotkey("#n") def run_notepad(): subprocess.Popen(["notepad"]) ``` 如果你想要把一個已存在的函數綁定熱鍵,就把它做為參數傳遞給`hotkey()`: ```python ahkpy.hotkey("#n", subprocess.Popen, ["notepad"]) ``` 上面的範例來看,當使用者按下`Win` + `N`,就會建立一個帶有參數`["notepad"]`的`subprocess.Popen`的物件 如果要把整個系統禁用一個鍵或是一個組合鍵的話,那就可以使用`lambda: None`。舉例來說,下面範例就禁用右邊的`Win`鍵: ```python ahkpy.hotkey("RWin", lambda: None) ``` 如果你想要指定熱鍵執行不同操作(或根本不執行任何操作)的時候指定某些條件,可以使用`Windows.active_window_context()`與`Windows.window_context()`,或是`HotkeyContext`這個class。舉例來說: ```python notepad_ctx = ahkpy.windows.active_window_context(class_name="Notepad") notepad_ctx.hotkey( "^a", ahkpy.message_box, "You pressed Ctrl-A while Notepad is active. Pressing Ctrl-A in any " "other window will pass the Ctrl-A keystroke to that window.", ) notepad_ctx.hotkey( "#c", ahkpy.message_box, "You pressed Win-C while Notepad is active.", ) ctx = ahkpy.windows.active_window_context() ctx.hotkey( "#c", ahkpy.message_box, "You pressed Win-C while any window except Notepad is active.", ) ``` ```python def is_mouse_over_taskbar(): win = ahkpy.get_window_under_mouse() return win.class_name == "Shell_TrayWnd" # Wheel over taskbar: increase/decrease volume. taskbar_ctx = ahkpy.HotkeyContext(is_mouse_over_taskbar) taskbar_ctx.hotkey("WheelUp", ahkpy.send, "{Volume_Up}") taskbar_ctx.hotkey("WheelDown", ahkpy.send, "{Volume_Down}") ``` 相同的處理程序可以分配到多個熱鍵: ```python import os import re import subprocess import ahkpy def open_explorer(mode): """ Ctrl+Shift+O to open containing folder in Explorer. Ctrl+Shift+E to open folder with current file selected. Supports SciTE and Notepad++. """ path = ahkpy.windows.get_active().title if not path: return mo = re.match(r"\*?((.*)\\[^\\]+)(?= [-*] )", path) if not mo: return file = mo.group(1) folder = mo.group(2) if mode == "folder" and os.path.exists(folder): subprocess.Popen(["explorer.exe", f'/select,"{folder}"') else: subprocess.Popen(["explorer.exe", f'"{file}"') ahkpy.hotkey("^+o", open_explorer, "file") ahkpy.hotkey("^+e", open_explorer, "folder") ``` 更多的範例可以參考原始[Hotkeys](https://www.autohotkey.com/docs/Hotkeys.htm)的用法。 ## Window Management AutoHotkey.py提供`Windows`的類別,其預設實例(default instances)為:`windows`與`all_windows`。 使用`windows`這個類別的話,你就可以透過多個搜尋參數(search parameters)來查詢視窗(windows),像是title與window class。如果要查詢windows,那就用`filter()`來設置條件。舉例來說,下面就準備對類別`ConsoleWindowClass`的所有windows查詢。 ```python console_windows = ahkpy.windows.filter(class_name="ConsoleWindowClass") ``` `filter()`的唯一規則就是打包查詢參數。在你設置好你想要的過濾器(fifters)之後,你就可以執行一個實際的操作,像是取得匹配到的windows的數量。 ```python console_windows Windows(class_name='ConsoleWindowClass', hidden_windows=False, hidden_text=True, title_mode='startswith', text_mode='fast') len(console_windows) # Check how many console windows there are now. 3 if console_windows: print("yes") # Executed if there's at least one console window. yes list(console_windows) # Retrieve the list of window instances. [Window(id=39784856), Window(id=29757762), Window(id=262780)] [win.title for win in console_windows] ['Command Prompt', 'Windows PowerShell', 'C:\\Windows\\py.exe'] ``` 幫`filter()`指定多個條件可以將搜尋範圍縮小到所有條件都匹配的視窗(windows)。下面的範例中,腳本會等待一個標題包含`My File.txt`且類別為Notepad的視窗: ```python ahkpy.windows.filter("My File.txt", class_name="Notepad").wait() # Filter chaining gives the same result. ahkpy.windows.filter("My File.txt").filter(class_name="Notepad").wait() ``` 當你想要建立並重用一個選用的視窗的時候,呼叫`filter()`是很有用的一個作法。然而,所有的`windows`的方法都會接收到搜尋條件,因此,上面的`wait()`的範例可以縮減成下面這樣: ```python ahkpy.windows.wait("My File.txt", class_name="Notepad") ``` `exclude()`這個方法是`filter()`的好朋友,它可以從搜尋中排除一個視窗: ```python non_cmd_windows = ahkpy.windows.exclude(title="Command Prompt") ``` 更細膩的視窗過濾就可以使用列表生成式: ```python # Get all tool windows of paint.net. [ win.title for win in ahkpy.windows.filter(exe="PaintDotNet.exe") if ahkpy.ExWindowStyle.TOOLWINDOW in win.ex_style ] ['Colors', 'Layers', 'History', 'Tools'] ``` 要取得當下的[現用視窗](http://terms.naer.edu.tw/detail/19309613/)的話,可以使用`get_active()`: ```python # Press Win+↑ to maximize the active window. ahkpy.hotkey("#Up", lambda: ahkpy.windows.get_active().maximize()) ``` 要取得查詢中得到的第一個(最上面)視窗,就可以使用`first()`: ```python >>>ahkpy.windows.first(class_name="Notepad") Window(id=6426410) ``` `first(), last(), get_active(), wait()`,這幾個方法都會回傳一個`Window`實例。如果沒有匹配的視窗,那就會回傳`Window(None)`。這個物件是虛的(falsy),它的多數屬性都會回傳`None`: ```python >>> win = ahkpy.windows.first(class_name="there's no such window") >>> win Window(id=None) win.exists False >>> if win: ... print("window exists") # Will not be printed. ... >>> win.is_visible False >>> win.show() # Does nothing. >>> win.class_name is None True ``` 還有啊,如果有個視窗它原本是在的,但是後來被關了,那它就會跟`Window(None)`一樣。所以啊,使用之前請記得檢查屬性值是不是`None`。 ```python >>> win = ahkpy.windows.first(class_name="Notepad") >>> win Window(id=6819626) >>> win.close() >>> win.exists False >>> bool(win) False >>> win.class_name is None True ``` ## DLL Calls 使用`ctypes`來呼叫DLL function: ```python >>> from ctypes import windll >>> windll.user32.MessageBoxW(0, "Press Yes or No", "Title of box", 4) 6 ``` 結構範例 [#11](https://www.autohotkey.com/docs/commands/DllCall.htm#ExStruct): ```python >>> import subprocess >>> from ctypes import byref, windll >>> from ctypes.wintypes import RECT >>> >>> subprocess.Popen(["notepad"]) >>> notepad = ahkpy.windows.wait("Untitled - Notepad") >>> rect = RECT() >>> windll.user32.GetWindowRect(notepad.id, byref(rect)) 1 >>> (rect.left, rect.top, rect.right, rect.bottom) (1063, 145, 1667, 824) ``` 結構範例 [#12](https://www.autohotkey.com/docs/commands/DllCall.htm#ExStructRect): ```python >>> from ctypes import byref, windll >>> from ctypes.wintypes import HANDLE, RECT >>> >>> screen_width = windll.user32.GetSystemMetrics(0) >>> screen_height = windll.user32.GetSystemMetrics(1) >>> rect = RECT(0, 0, screen_width//2, screen_height//2) >>> # Pass zero to get the desktop's device context. >>> dc = windll.user32.GetDC(0) >>> # Create a red brush (0x0000FF is in BGR format). >>> brush = windll.gdi32.CreateSolidBrush(0x0000FF) >>> # Fill the specified rectangle using the brush above. >>> windll.user32.FillRect(dc, byref(rect), brush) >>> windll.gdi32.DeleteObject(brush) # Clean-up. >>> windll.user32.ReleaseDC(0, HANDLE(dc)) # Clean-up. ``` ## Settings callback([回呼](http://terms.naer.edu.tw/detail/19324886/))它是一種由[timer](https://ahkpy.readthedocs.io/api.html#ahkpy.set_timer)、[window message](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.on_message)、或是改變[剪貼板](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.on_clipboard_change)、或是觸發一個[hotkey](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.HotkeyContext.hotkey)或是[hostring](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.HotkeyContext.hotstring)所呼叫的函數。 在原始的AutoHoekty中,hotkey callback是以全域設置(global setting)的複本(copy)在執行的。相對的,AutoHotkey.py的話,callback會取得當下[Settings](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.Settings)這物件的一個參照,該物件是由 [set_settings()](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.set_settings)所呼叫。這意味著,changing the individual settings in the Python callback changes them everywhere.(真的翻不出來,在Python中,回呼會在任一個意方去改變它們各自的設置?)。有些時候你會希望可以避免這麼做,這種情況下,你應該使用[local_setting()](https://ahkpy.readthedocs.io/en/latest/api.html#ahkpy.local_settings)。其它時候,實作(implementation)會派上用場,像是,當你希望建立一個hotkey可以改變全域的AHK設置的話: ```python ahkpy.default_settings.win_delay = 0.1 # The callback stores only the reference to # ahkpy.default_settings, not the actual settings values. ahkpy.hotkey("F1", lambda: print(ahkpy.get_settings().win_delay)) @ahkpy.hotkey("F2") def change_defaults(): ahkpy.default_settings.win_delay = 0.2 assert ahkpy.get_settings() is ahkpy.default_settings ``` 如果你按下`F1`,你就會看到`0.1`被列印出來,那就是當前的`win_delay`。按下`F2`,然後按`F1`,你就會看到`0.2`被列印出來。另外就是,`F2`透過hotkey callback所呼叫的`get_settings()`所得到的設置物件會跟`F1`的hotkey所得到的設置物件完全相同。 ## Debugging AutoHotkey.py支援`pdb`,Ptyhon內建的除錯器。就只要把`breakpoint()`放到你希望的地方,然後執行程式。它在主要的區域與回呼裡面都是有效的: ```python x = 0 @ahkpy.hotkey("F1") def cb(): global x x += 1 breakpoint() # Breakpoint in a callback breakpoint() # Breakpoint in the main section ``` Visual Studio Code的debugger也可以設置搭配AutoHotkey.py一起作業。跟著下面的指南建立你的`lanuch.json`。建立之後,把`lanuch.json`的Python解釋器改成`ahkpy.exe`,舉例來說: ``` { "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", // Add the following settings: "python": "ahkpy.exe", "pythonArgs": ["--no-tray"] } ] } ``` 現在,你可以在設置Visual Studio Code中設置中斷點,然後檢查AutoHotkey.py的程序,就像你平常在寫Python程式那樣。