# 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程式那樣。