prkit 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prkit-0.2.0/PKG-INFO +139 -0
- prkit-0.2.0/README.md +122 -0
- {prkit-0.1.0 → prkit-0.2.0}/pyproject.toml +6 -4
- {prkit-0.1.0 → prkit-0.2.0}/pyproject.toml.orig +8 -4
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/__init__.py +1 -1
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/app.py +82 -1
- prkit-0.2.0/src/prkit/capslock.py +100 -0
- prkit-0.2.0/src/prkit/input_source.py +150 -0
- prkit-0.2.0/src/prkit/mac_capslock.py +57 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/mac_keyboard.py +8 -2
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/overlays.py +131 -7
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/platform.py +5 -1
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/settings.py +38 -2
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/tray.py +42 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/windows_cursor.py +1 -1
- prkit-0.1.0/PKG-INFO +0 -100
- prkit-0.1.0/README.md +0 -84
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/__main__.py +0 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/cursor_watchdog.py +0 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/icon.py +0 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/keyboard.py +0 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/keys.py +0 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/py.typed +0 -0
- {prkit-0.1.0 → prkit-0.2.0}/src/prkit/spotlight.py +0 -0
prkit-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: prkit
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A cross-platform system-tray presentation toolkit
|
|
5
|
+
Author: meebox
|
|
6
|
+
Author-email: meebox <meebox@gmail.com>
|
|
7
|
+
Requires-Dist: pynput>=1.8.2
|
|
8
|
+
Requires-Dist: pyobjc-framework-cocoa>=12.2.1 ; sys_platform == 'darwin'
|
|
9
|
+
Requires-Dist: pyobjc-framework-quartz>=12.2.1 ; sys_platform == 'darwin'
|
|
10
|
+
Requires-Dist: pyside6>=6.11.1
|
|
11
|
+
Requires-Dist: python-xlib>=0.33 ; sys_platform == 'linux'
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Project-URL: Homepage, https://github.com/codemee/prkit
|
|
14
|
+
Project-URL: Repository, https://github.com/codemee/prkit
|
|
15
|
+
Project-URL: Issues, https://github.com/codemee/prkit/issues
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# PRKit:簡報瑞士刀
|
|
19
|
+
|
|
20
|
+
[繁體中文](README.md) | [English](README.en.md)
|
|
21
|
+
|
|
22
|
+
跨平台的系統匣簡報輔助工具。程式不會開啟主視窗,所有設定都在系統匣圖示的右鍵選單中完成。
|
|
23
|
+
|
|
24
|
+
## 功能
|
|
25
|
+
|
|
26
|
+
- 聚光燈游標:可調整顏色、透明度與 48–320 px 圓圈大小;Windows 使用真正的系統游標,macOS/Linux 使用不攔截點擊的透明覆蓋層。
|
|
27
|
+
- 按鍵顯示:在游標所在螢幕中央下方顯示單鍵或組合鍵,不會攔截原本的鍵盤輸入。
|
|
28
|
+
- Caps Lock 狀態:以可拖曳的小圓角矩形顯示 ON/OFF,可設定顯示時間、背景顏色與透明度。
|
|
29
|
+
- macOS 輸入法狀態:可切換為顯示目前的中文/英文輸入來源,與 Caps Lock 燈號分開追蹤。
|
|
30
|
+
- 自動保存:記住各項功能的啟用狀態、外觀設定與 Caps Lock 提示位置。
|
|
31
|
+
- 多螢幕與高 DPI:使用 Qt 邏輯像素定位與繪製。
|
|
32
|
+
|
|
33
|
+
## 下載桌面版
|
|
34
|
+
|
|
35
|
+
從 [GitHub Releases](https://github.com/codemee/prkit/releases/latest) 下載,不需另外安裝 Python 或 uv:
|
|
36
|
+
|
|
37
|
+
| 平台 | 下載檔案 | 啟動方式 |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Windows x64 | `PRKit-0.2.0-windows-x64.zip` | 完整解壓後執行 `PRKit/PRKit.exe` |
|
|
40
|
+
| macOS Apple Silicon | `PRKit-0.2.0-macos-arm64.zip` | 解壓後將 `PRKit.app` 移到「應用程式」再開啟 |
|
|
41
|
+
|
|
42
|
+
Windows 須保留同資料夾的 `prkit-cursor-watchdog.exe` 與 `_internal`,不可只複製主程式。macOS 目前僅提供 Apple Silicon 版。程式啟動後顯示在系統匣/選單列,不會開啟主視窗。
|
|
43
|
+
|
|
44
|
+
桌面版尚未使用 Windows 發行者憑證或 Apple Developer ID 簽署/公證;macOS 僅使用 ad-hoc 簽章。若系統阻擋你確認來源的下載檔案,請依系統安全提示允許開啟。macOS 按鍵顯示仍需在「輸入監控」允許 `PRKit`;輸入法狀態模式不需此權限。每個 ZIP 另提供 `.sha256` 校驗檔。
|
|
45
|
+
|
|
46
|
+
版本標籤會自動觸發 GitHub 雲端打包與成品測試;手動建置可在 Actions 的 **Build desktop apps** 執行,成品在該次執行的 Artifacts。詳細操作見[發佈指南](docs/RELEASING.md)。
|
|
47
|
+
|
|
48
|
+
## 從 PyPI 安裝
|
|
49
|
+
|
|
50
|
+
需要先安裝 [uv](https://docs.astral.sh/uv/),再執行:
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
uv tool install prkit
|
|
54
|
+
prkit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
也可以從已下載的專案目錄安裝:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
uv tool install .
|
|
61
|
+
prkit
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
或直接從 GitHub 安裝最新正式版;`latest` 標籤會在每次正式發布時更新:
|
|
65
|
+
|
|
66
|
+
```powershell
|
|
67
|
+
uv tool install git+https://github.com/codemee/prkit.git@latest
|
|
68
|
+
prkit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
若要固定特定版本,將 `latest` 改成版本標籤,例如 `v0.2.0`。
|
|
72
|
+
|
|
73
|
+
若安裝後找不到 `prkit` 指令,執行 `uv tool update-shell`,再重新開啟終端機。移除工具可使用 `uv tool uninstall prkit`。
|
|
74
|
+
|
|
75
|
+
## 使用 uvx 免安裝執行
|
|
76
|
+
|
|
77
|
+
直接從 PyPI 執行:
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
uvx prkit
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
也可以在已下載的專案目錄執行,或直接從 GitHub 執行最新正式版:
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
uvx --from . prkit
|
|
87
|
+
uvx --from git+https://github.com/codemee/prkit.git@latest prkit
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`uvx` 會在隔離環境中準備並執行程式,不會把 `prkit` 持久安裝到工具目錄。
|
|
91
|
+
|
|
92
|
+
## Caps Lock/輸入法狀態顯示
|
|
93
|
+
|
|
94
|
+
啟動後,右鍵點擊系統匣圖示,在「Caps Lock/輸入法狀態」勾選「啟用狀態顯示」。此功能可獨立於按鍵顯示使用,啟用時會先顯示目前狀態。
|
|
95
|
+
|
|
96
|
+
macOS 可在「顯示內容」選擇「Caps Lock 指示燈」或「目前輸入法(中文/英文)」。輸入法模式直接追蹤系統選中的輸入來源:內建注音顯示「中文(注音)」,ABC/美式鍵盤顯示「英文」,其他輸入來源顯示其名稱;使用 Caps Lock、快捷鍵或選單切換都會更新。此模式不需要輸入監控權限,也不會把切換中英文時短暫出現的 Caps Lock 狀態當成大寫鎖定。
|
|
97
|
+
|
|
98
|
+
- **顯示時間**:設定狀態變化後顯示 1–60 秒;設為 0(「一直顯示」)便會持續顯示目前狀態。
|
|
99
|
+
- **背景顏色與透明度**:透過色塊與滑桿調整,修改時會顯示預覽。
|
|
100
|
+
- **移動位置**:直接用滑鼠左鍵拖曳提示框;拖曳期間暫停隱藏倒數,放開後重新計時並保存位置。下次啟動沿用,若原螢幕已移除則回到可見螢幕。
|
|
101
|
+
|
|
102
|
+
| 平台 | Caps Lock ON/OFF | 目前輸入法 |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| Windows | 支援 | 未支援,不顯示選項 |
|
|
105
|
+
| macOS | 支援 | 支援 |
|
|
106
|
+
| Linux X11 | 支援 | 未支援,不顯示選項 |
|
|
107
|
+
| Wayland | 未支援 | 未支援 |
|
|
108
|
+
|
|
109
|
+
macOS 啟用 Caps Lock 切換輸入方式時,短按切換中英文,按住直到大寫鎖定符號出現才啟用持續大寫。若要跟隨中英文,請選擇輸入法模式;Caps Lock 模式顯示系統鎖定狀態。輸入法模式不推測第三方輸入法在同一來源內的私有中英文狀態。Wayland 啟用時會提示並自動停用。
|
|
110
|
+
|
|
111
|
+
## 開發
|
|
112
|
+
|
|
113
|
+
Python 3.12、虛擬環境及相依套件均由 uv 管理:
|
|
114
|
+
|
|
115
|
+
```powershell
|
|
116
|
+
uv sync
|
|
117
|
+
uv run prkit
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
品質檢查:
|
|
121
|
+
|
|
122
|
+
```powershell
|
|
123
|
+
uv run ruff check .
|
|
124
|
+
uv run ruff format --check .
|
|
125
|
+
uv run pytest -q
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 平台注意事項
|
|
129
|
+
|
|
130
|
+
- Windows:不需要額外權限。啟用聚光燈時會暫時替換系統游標,停用或結束時自動恢復;隨工具一併安裝的獨立 watchdog 會在主程式異常終止時復原游標。
|
|
131
|
+
- macOS:按鍵顯示使用 HID 層的唯讀事件監看,需要在「系統設定 → 隱私權與安全性 → 輸入監控」允許啟動本程式的應用程式(例如 ChatGPT、Terminal 或其他啟動 `prkit` 的終端程式)。監看器不會修改或攔截事件;即使組合鍵被其他快捷鍵工具處理,仍可先顯示按鍵。
|
|
132
|
+
- Linux:完整支援目標為 X11,桌面環境必須提供 StatusNotifierItem 或 XEmbed 系統匣。GNOME 可能需要 AppIndicator 類型的擴充套件。
|
|
133
|
+
- Wayland:受全域輸入與覆蓋層協定限制,按鍵顯示和點擊穿透不保證可用;程式會在選單中顯示警告。
|
|
134
|
+
|
|
135
|
+
按鍵內容只用於即時畫面顯示,不會寫入設定、檔案或日誌。
|
|
136
|
+
|
|
137
|
+
實作架構、macOS 原生視窗層級、權限模型與鍵盤事件流程請參閱[技術設計](docs/TECHNICAL.md)。
|
|
138
|
+
|
|
139
|
+
各版本變更見[版本紀錄](CHANGELOG.md),雲端打包與 PyPI 發佈程序見[發佈指南](docs/RELEASING.md)。
|
prkit-0.2.0/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# PRKit:簡報瑞士刀
|
|
2
|
+
|
|
3
|
+
[繁體中文](README.md) | [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
跨平台的系統匣簡報輔助工具。程式不會開啟主視窗,所有設定都在系統匣圖示的右鍵選單中完成。
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- 聚光燈游標:可調整顏色、透明度與 48–320 px 圓圈大小;Windows 使用真正的系統游標,macOS/Linux 使用不攔截點擊的透明覆蓋層。
|
|
10
|
+
- 按鍵顯示:在游標所在螢幕中央下方顯示單鍵或組合鍵,不會攔截原本的鍵盤輸入。
|
|
11
|
+
- Caps Lock 狀態:以可拖曳的小圓角矩形顯示 ON/OFF,可設定顯示時間、背景顏色與透明度。
|
|
12
|
+
- macOS 輸入法狀態:可切換為顯示目前的中文/英文輸入來源,與 Caps Lock 燈號分開追蹤。
|
|
13
|
+
- 自動保存:記住各項功能的啟用狀態、外觀設定與 Caps Lock 提示位置。
|
|
14
|
+
- 多螢幕與高 DPI:使用 Qt 邏輯像素定位與繪製。
|
|
15
|
+
|
|
16
|
+
## 下載桌面版
|
|
17
|
+
|
|
18
|
+
從 [GitHub Releases](https://github.com/codemee/prkit/releases/latest) 下載,不需另外安裝 Python 或 uv:
|
|
19
|
+
|
|
20
|
+
| 平台 | 下載檔案 | 啟動方式 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Windows x64 | `PRKit-0.2.0-windows-x64.zip` | 完整解壓後執行 `PRKit/PRKit.exe` |
|
|
23
|
+
| macOS Apple Silicon | `PRKit-0.2.0-macos-arm64.zip` | 解壓後將 `PRKit.app` 移到「應用程式」再開啟 |
|
|
24
|
+
|
|
25
|
+
Windows 須保留同資料夾的 `prkit-cursor-watchdog.exe` 與 `_internal`,不可只複製主程式。macOS 目前僅提供 Apple Silicon 版。程式啟動後顯示在系統匣/選單列,不會開啟主視窗。
|
|
26
|
+
|
|
27
|
+
桌面版尚未使用 Windows 發行者憑證或 Apple Developer ID 簽署/公證;macOS 僅使用 ad-hoc 簽章。若系統阻擋你確認來源的下載檔案,請依系統安全提示允許開啟。macOS 按鍵顯示仍需在「輸入監控」允許 `PRKit`;輸入法狀態模式不需此權限。每個 ZIP 另提供 `.sha256` 校驗檔。
|
|
28
|
+
|
|
29
|
+
版本標籤會自動觸發 GitHub 雲端打包與成品測試;手動建置可在 Actions 的 **Build desktop apps** 執行,成品在該次執行的 Artifacts。詳細操作見[發佈指南](docs/RELEASING.md)。
|
|
30
|
+
|
|
31
|
+
## 從 PyPI 安裝
|
|
32
|
+
|
|
33
|
+
需要先安裝 [uv](https://docs.astral.sh/uv/),再執行:
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
uv tool install prkit
|
|
37
|
+
prkit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
也可以從已下載的專案目錄安裝:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
uv tool install .
|
|
44
|
+
prkit
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
或直接從 GitHub 安裝最新正式版;`latest` 標籤會在每次正式發布時更新:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
uv tool install git+https://github.com/codemee/prkit.git@latest
|
|
51
|
+
prkit
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
若要固定特定版本,將 `latest` 改成版本標籤,例如 `v0.2.0`。
|
|
55
|
+
|
|
56
|
+
若安裝後找不到 `prkit` 指令,執行 `uv tool update-shell`,再重新開啟終端機。移除工具可使用 `uv tool uninstall prkit`。
|
|
57
|
+
|
|
58
|
+
## 使用 uvx 免安裝執行
|
|
59
|
+
|
|
60
|
+
直接從 PyPI 執行:
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
uvx prkit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
也可以在已下載的專案目錄執行,或直接從 GitHub 執行最新正式版:
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
uvx --from . prkit
|
|
70
|
+
uvx --from git+https://github.com/codemee/prkit.git@latest prkit
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`uvx` 會在隔離環境中準備並執行程式,不會把 `prkit` 持久安裝到工具目錄。
|
|
74
|
+
|
|
75
|
+
## Caps Lock/輸入法狀態顯示
|
|
76
|
+
|
|
77
|
+
啟動後,右鍵點擊系統匣圖示,在「Caps Lock/輸入法狀態」勾選「啟用狀態顯示」。此功能可獨立於按鍵顯示使用,啟用時會先顯示目前狀態。
|
|
78
|
+
|
|
79
|
+
macOS 可在「顯示內容」選擇「Caps Lock 指示燈」或「目前輸入法(中文/英文)」。輸入法模式直接追蹤系統選中的輸入來源:內建注音顯示「中文(注音)」,ABC/美式鍵盤顯示「英文」,其他輸入來源顯示其名稱;使用 Caps Lock、快捷鍵或選單切換都會更新。此模式不需要輸入監控權限,也不會把切換中英文時短暫出現的 Caps Lock 狀態當成大寫鎖定。
|
|
80
|
+
|
|
81
|
+
- **顯示時間**:設定狀態變化後顯示 1–60 秒;設為 0(「一直顯示」)便會持續顯示目前狀態。
|
|
82
|
+
- **背景顏色與透明度**:透過色塊與滑桿調整,修改時會顯示預覽。
|
|
83
|
+
- **移動位置**:直接用滑鼠左鍵拖曳提示框;拖曳期間暫停隱藏倒數,放開後重新計時並保存位置。下次啟動沿用,若原螢幕已移除則回到可見螢幕。
|
|
84
|
+
|
|
85
|
+
| 平台 | Caps Lock ON/OFF | 目前輸入法 |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| Windows | 支援 | 未支援,不顯示選項 |
|
|
88
|
+
| macOS | 支援 | 支援 |
|
|
89
|
+
| Linux X11 | 支援 | 未支援,不顯示選項 |
|
|
90
|
+
| Wayland | 未支援 | 未支援 |
|
|
91
|
+
|
|
92
|
+
macOS 啟用 Caps Lock 切換輸入方式時,短按切換中英文,按住直到大寫鎖定符號出現才啟用持續大寫。若要跟隨中英文,請選擇輸入法模式;Caps Lock 模式顯示系統鎖定狀態。輸入法模式不推測第三方輸入法在同一來源內的私有中英文狀態。Wayland 啟用時會提示並自動停用。
|
|
93
|
+
|
|
94
|
+
## 開發
|
|
95
|
+
|
|
96
|
+
Python 3.12、虛擬環境及相依套件均由 uv 管理:
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
uv sync
|
|
100
|
+
uv run prkit
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
品質檢查:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
uv run ruff check .
|
|
107
|
+
uv run ruff format --check .
|
|
108
|
+
uv run pytest -q
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 平台注意事項
|
|
112
|
+
|
|
113
|
+
- Windows:不需要額外權限。啟用聚光燈時會暫時替換系統游標,停用或結束時自動恢復;隨工具一併安裝的獨立 watchdog 會在主程式異常終止時復原游標。
|
|
114
|
+
- macOS:按鍵顯示使用 HID 層的唯讀事件監看,需要在「系統設定 → 隱私權與安全性 → 輸入監控」允許啟動本程式的應用程式(例如 ChatGPT、Terminal 或其他啟動 `prkit` 的終端程式)。監看器不會修改或攔截事件;即使組合鍵被其他快捷鍵工具處理,仍可先顯示按鍵。
|
|
115
|
+
- Linux:完整支援目標為 X11,桌面環境必須提供 StatusNotifierItem 或 XEmbed 系統匣。GNOME 可能需要 AppIndicator 類型的擴充套件。
|
|
116
|
+
- Wayland:受全域輸入與覆蓋層協定限制,按鍵顯示和點擊穿透不保證可用;程式會在選單中顯示警告。
|
|
117
|
+
|
|
118
|
+
按鍵內容只用於即時畫面顯示,不會寫入設定、檔案或日誌。
|
|
119
|
+
|
|
120
|
+
實作架構、macOS 原生視窗層級、權限模型與鍵盤事件流程請參閱[技術設計](docs/TECHNICAL.md)。
|
|
121
|
+
|
|
122
|
+
各版本變更見[版本紀錄](CHANGELOG.md),雲端打包與 PyPI 發佈程序見[發佈指南](docs/RELEASING.md)。
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "prkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "A cross-platform system-tray presentation toolkit"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"pynput>=1.8.2",
|
|
9
9
|
"pyobjc-framework-cocoa>=12.2.1 ; sys_platform == 'darwin'",
|
|
10
|
+
"pyobjc-framework-quartz>=12.2.1 ; sys_platform == 'darwin'",
|
|
10
11
|
"pyside6>=6.11.1",
|
|
11
12
|
"python-xlib>=0.33 ; sys_platform == 'linux'",
|
|
12
13
|
]
|
|
@@ -20,9 +21,9 @@ prkit = "prkit.app:main"
|
|
|
20
21
|
prkit-cursor-watchdog = "prkit.cursor_watchdog:main"
|
|
21
22
|
|
|
22
23
|
[project.urls]
|
|
23
|
-
Homepage = "https://github.com/codemee/
|
|
24
|
-
Repository = "https://github.com/codemee/
|
|
25
|
-
Issues = "https://github.com/codemee/
|
|
24
|
+
Homepage = "https://github.com/codemee/prkit"
|
|
25
|
+
Repository = "https://github.com/codemee/prkit"
|
|
26
|
+
Issues = "https://github.com/codemee/prkit/issues"
|
|
26
27
|
|
|
27
28
|
[tool.pytest.ini_options]
|
|
28
29
|
addopts = "-q"
|
|
@@ -47,6 +48,7 @@ requires = ["uv_build>=0.11.28,<0.12.0"]
|
|
|
47
48
|
build-backend = "uv_build"
|
|
48
49
|
|
|
49
50
|
[dependency-groups]
|
|
51
|
+
build = ["pyinstaller>=6.16,<7"]
|
|
50
52
|
dev = [
|
|
51
53
|
"pytest>=9.1.1",
|
|
52
54
|
"pytest-qt>=4.5.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "prkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "A cross-platform system-tray presentation toolkit"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -10,6 +10,7 @@ requires-python = ">=3.12"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pynput>=1.8.2",
|
|
12
12
|
"pyobjc-framework-cocoa>=12.2.1 ; sys_platform == 'darwin'",
|
|
13
|
+
"pyobjc-framework-quartz>=12.2.1 ; sys_platform == 'darwin'",
|
|
13
14
|
"pyside6>=6.11.1",
|
|
14
15
|
"python-xlib>=0.33 ; sys_platform == 'linux'",
|
|
15
16
|
]
|
|
@@ -19,9 +20,9 @@ prkit = "prkit.app:main"
|
|
|
19
20
|
prkit-cursor-watchdog = "prkit.cursor_watchdog:main"
|
|
20
21
|
|
|
21
22
|
[project.urls]
|
|
22
|
-
Homepage = "https://github.com/codemee/
|
|
23
|
-
Repository = "https://github.com/codemee/
|
|
24
|
-
Issues = "https://github.com/codemee/
|
|
23
|
+
Homepage = "https://github.com/codemee/prkit"
|
|
24
|
+
Repository = "https://github.com/codemee/prkit"
|
|
25
|
+
Issues = "https://github.com/codemee/prkit/issues"
|
|
25
26
|
|
|
26
27
|
[tool.pytest.ini_options]
|
|
27
28
|
addopts = "-q"
|
|
@@ -39,6 +40,9 @@ requires = ["uv_build>=0.11.28,<0.12.0"]
|
|
|
39
40
|
build-backend = "uv_build"
|
|
40
41
|
|
|
41
42
|
[dependency-groups]
|
|
43
|
+
build = [
|
|
44
|
+
"pyinstaller>=6.16,<7",
|
|
45
|
+
]
|
|
42
46
|
dev = [
|
|
43
47
|
"pytest>=9.1.1",
|
|
44
48
|
"pytest-qt>=4.5.0",
|
|
@@ -7,8 +7,10 @@ from contextlib import suppress
|
|
|
7
7
|
from PySide6.QtCore import QObject, QSignalBlocker, QTimer
|
|
8
8
|
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
9
9
|
|
|
10
|
+
from prkit.capslock import CapsLockMonitor
|
|
11
|
+
from prkit.input_source import InputSourceMonitor
|
|
10
12
|
from prkit.keyboard import KeyboardMonitor
|
|
11
|
-
from prkit.overlays import KeystrokeOverlay
|
|
13
|
+
from prkit.overlays import CapsLockOverlay, KeystrokeOverlay
|
|
12
14
|
from prkit.settings import SettingsStore, SettingsStoreProtocol
|
|
13
15
|
from prkit.spotlight import SpotlightBackend, create_spotlight
|
|
14
16
|
from prkit.tray import TrayController
|
|
@@ -21,6 +23,8 @@ class AppController(QObject):
|
|
|
21
23
|
store: SettingsStoreProtocol | None = None,
|
|
22
24
|
monitor: KeyboardMonitor | None = None,
|
|
23
25
|
spotlight: SpotlightBackend | None = None,
|
|
26
|
+
capslock_monitor: CapsLockMonitor | None = None,
|
|
27
|
+
input_source_monitor: InputSourceMonitor | None = None,
|
|
24
28
|
) -> None:
|
|
25
29
|
super().__init__()
|
|
26
30
|
self._app = app
|
|
@@ -28,6 +32,11 @@ class AppController(QObject):
|
|
|
28
32
|
self.settings = self._store.load()
|
|
29
33
|
self.spotlight = spotlight or create_spotlight(self.settings.spotlight)
|
|
30
34
|
self.keystroke = KeystrokeOverlay(self.settings.keystroke)
|
|
35
|
+
self.capslock = CapsLockOverlay(self.settings.capslock)
|
|
36
|
+
self.capslock_monitor = capslock_monitor or CapsLockMonitor(self)
|
|
37
|
+
self.input_source_monitor = input_source_monitor or InputSourceMonitor(self)
|
|
38
|
+
if sys.platform != "darwin":
|
|
39
|
+
self.settings.capslock.mode = "capslock"
|
|
31
40
|
self.monitor = monitor or KeyboardMonitor(self)
|
|
32
41
|
self.tray = TrayController(self.settings, self)
|
|
33
42
|
self._shutting_down = False
|
|
@@ -43,6 +52,16 @@ class AppController(QObject):
|
|
|
43
52
|
keystroke_opacity=self.set_keystroke_opacity,
|
|
44
53
|
)
|
|
45
54
|
self.tray.exit_requested.connect(self.shutdown)
|
|
55
|
+
self.tray.panel.capslock_enabled_changed.connect(self.set_capslock_enabled)
|
|
56
|
+
self.tray.panel.capslock_color_changed.connect(self.set_capslock_color)
|
|
57
|
+
self.tray.panel.capslock_opacity_changed.connect(self.set_capslock_opacity)
|
|
58
|
+
self.tray.panel.capslock_duration_changed.connect(self.set_capslock_duration)
|
|
59
|
+
self.tray.panel.capslock_mode_changed.connect(self.set_capslock_mode)
|
|
60
|
+
self.capslock_monitor.changed.connect(self._show_capslock_state)
|
|
61
|
+
self.input_source_monitor.changed.connect(self._show_input_source)
|
|
62
|
+
self.input_source_monitor.failed.connect(self._capslock_failed)
|
|
63
|
+
self.capslock_monitor.failed.connect(self._capslock_failed)
|
|
64
|
+
self.capslock.position_changed.connect(self._save_capslock_position)
|
|
46
65
|
self.tray.menu.aboutToShow.connect(self.spotlight.sync_position)
|
|
47
66
|
self.tray.menu.aboutToHide.connect(self._start_pending_keyboard_monitor)
|
|
48
67
|
self.monitor.chord_pressed.connect(self.keystroke.show_chord)
|
|
@@ -55,6 +74,65 @@ class AppController(QObject):
|
|
|
55
74
|
self.spotlight.set_enabled(self.settings.spotlight.enabled)
|
|
56
75
|
if self.settings.keystroke.enabled:
|
|
57
76
|
self.set_keystroke_enabled(True)
|
|
77
|
+
if self.settings.capslock.enabled:
|
|
78
|
+
self.set_capslock_enabled(True)
|
|
79
|
+
|
|
80
|
+
def set_capslock_enabled(self, enabled: bool) -> None:
|
|
81
|
+
self.settings.capslock.enabled = enabled
|
|
82
|
+
self.capslock_monitor.stop()
|
|
83
|
+
self.input_source_monitor.stop()
|
|
84
|
+
self.capslock.hide_now()
|
|
85
|
+
if enabled:
|
|
86
|
+
if self.settings.capslock.mode == "input_source":
|
|
87
|
+
self.input_source_monitor.start()
|
|
88
|
+
else:
|
|
89
|
+
self.capslock_monitor.start()
|
|
90
|
+
self._save()
|
|
91
|
+
|
|
92
|
+
def set_capslock_mode(self, mode: str) -> None:
|
|
93
|
+
if mode not in {"capslock", "input_source"}:
|
|
94
|
+
return
|
|
95
|
+
if mode == "input_source" and sys.platform != "darwin":
|
|
96
|
+
return
|
|
97
|
+
self.settings.capslock.mode = mode
|
|
98
|
+
combo = self.tray.panel.capslock_mode
|
|
99
|
+
with QSignalBlocker(combo):
|
|
100
|
+
combo.setCurrentIndex(combo.findData(mode))
|
|
101
|
+
self.set_capslock_enabled(self.settings.capslock.enabled)
|
|
102
|
+
|
|
103
|
+
def _show_capslock_state(self, state: bool) -> None:
|
|
104
|
+
if self.settings.capslock.mode == "capslock":
|
|
105
|
+
self.capslock.show_state(state)
|
|
106
|
+
|
|
107
|
+
def _show_input_source(self, name: str) -> None:
|
|
108
|
+
if self.settings.capslock.mode == "input_source":
|
|
109
|
+
self.capslock.show_input_source(name)
|
|
110
|
+
|
|
111
|
+
def set_capslock_color(self, color: str) -> None:
|
|
112
|
+
self.settings.capslock.color = color
|
|
113
|
+
self._apply_capslock_settings()
|
|
114
|
+
|
|
115
|
+
def set_capslock_opacity(self, opacity: int) -> None:
|
|
116
|
+
self.settings.capslock.opacity = opacity
|
|
117
|
+
self._apply_capslock_settings()
|
|
118
|
+
|
|
119
|
+
def set_capslock_duration(self, duration: int) -> None:
|
|
120
|
+
self.settings.capslock.duration = duration
|
|
121
|
+
self._apply_capslock_settings()
|
|
122
|
+
|
|
123
|
+
def _apply_capslock_settings(self) -> None:
|
|
124
|
+
self.capslock.apply_settings()
|
|
125
|
+
self._save()
|
|
126
|
+
|
|
127
|
+
def _save_capslock_position(self, x: int, y: int) -> None:
|
|
128
|
+
self.settings.capslock.x, self.settings.capslock.y = x, y
|
|
129
|
+
self._save()
|
|
130
|
+
|
|
131
|
+
def _capslock_failed(self, message: str) -> None:
|
|
132
|
+
self.set_capslock_enabled(False)
|
|
133
|
+
with QSignalBlocker(self.tray.panel.capslock_enabled):
|
|
134
|
+
self.tray.panel.capslock_enabled.setChecked(False)
|
|
135
|
+
self.tray.notify_warning(f"無法顯示鍵盤狀態:{message}")
|
|
58
136
|
|
|
59
137
|
def set_spotlight_enabled(self, enabled: bool) -> None:
|
|
60
138
|
try:
|
|
@@ -159,6 +237,9 @@ class AppController(QObject):
|
|
|
159
237
|
|
|
160
238
|
def cleanup(self) -> None:
|
|
161
239
|
self._keystroke_start_pending = False
|
|
240
|
+
self.capslock_monitor.stop()
|
|
241
|
+
self.input_source_monitor.stop()
|
|
242
|
+
self.capslock.hide_now()
|
|
162
243
|
self.monitor.stop()
|
|
163
244
|
self.spotlight.set_enabled(False)
|
|
164
245
|
self.keystroke.hide_now()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QObject, QTimer, Signal, Slot
|
|
8
|
+
|
|
9
|
+
from prkit.platform import is_wayland
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CapsLockMonitor(QObject):
|
|
13
|
+
"""Read the actual lock state independently of the keystroke display."""
|
|
14
|
+
|
|
15
|
+
changed = Signal(bool)
|
|
16
|
+
failed = Signal(str)
|
|
17
|
+
_refresh = Signal()
|
|
18
|
+
|
|
19
|
+
def __init__(self, parent: QObject | None = None) -> None:
|
|
20
|
+
super().__init__(parent)
|
|
21
|
+
self._read: Callable[[], bool] | None = None
|
|
22
|
+
self._connection: Any = None
|
|
23
|
+
self._listener: Any = None
|
|
24
|
+
self._state: bool | None = None
|
|
25
|
+
self._timer = QTimer(self)
|
|
26
|
+
self._timer.setInterval(100)
|
|
27
|
+
self._timer.timeout.connect(self._poll)
|
|
28
|
+
self._refresh.connect(self._poll)
|
|
29
|
+
|
|
30
|
+
def start(self) -> None:
|
|
31
|
+
self.stop()
|
|
32
|
+
try:
|
|
33
|
+
if sys.platform == "win32":
|
|
34
|
+
import ctypes
|
|
35
|
+
|
|
36
|
+
self._read = lambda: bool(ctypes.windll.user32.GetKeyState(0x14) & 1)
|
|
37
|
+
elif sys.platform == "darwin":
|
|
38
|
+
from prkit.mac_capslock import MacCapsLockState
|
|
39
|
+
|
|
40
|
+
self._connection = MacCapsLockState()
|
|
41
|
+
self._read = self._connection.read
|
|
42
|
+
elif sys.platform.startswith("linux") and not is_wayland():
|
|
43
|
+
from Xlib import X, display
|
|
44
|
+
|
|
45
|
+
self._connection = display.Display()
|
|
46
|
+
root = self._connection.screen().root
|
|
47
|
+
self._read = lambda: bool(root.query_pointer().mask & X.LockMask)
|
|
48
|
+
else:
|
|
49
|
+
raise RuntimeError("此平台不支援 Caps Lock 狀態偵測(Linux 需要 X11)。")
|
|
50
|
+
self._poll()
|
|
51
|
+
if self._read is not None:
|
|
52
|
+
if sys.platform == "darwin":
|
|
53
|
+
self._start_macos_listener()
|
|
54
|
+
self._timer.start()
|
|
55
|
+
except Exception as error:
|
|
56
|
+
self.stop()
|
|
57
|
+
self.failed.emit(str(error))
|
|
58
|
+
|
|
59
|
+
def stop(self) -> None:
|
|
60
|
+
self._timer.stop()
|
|
61
|
+
if self._listener is not None:
|
|
62
|
+
self._listener.stop()
|
|
63
|
+
self._listener = None
|
|
64
|
+
self._read = None
|
|
65
|
+
self._state = None
|
|
66
|
+
if self._connection is not None:
|
|
67
|
+
self._connection.close()
|
|
68
|
+
self._connection = None
|
|
69
|
+
|
|
70
|
+
def _start_macos_listener(self) -> None:
|
|
71
|
+
from prkit.mac_keyboard import MacKeyboardListener
|
|
72
|
+
|
|
73
|
+
# Event flags can be transient or disagree with the lock indicator.
|
|
74
|
+
# Events only wake the Qt thread to query the same source as polling.
|
|
75
|
+
listener = MacKeyboardListener(
|
|
76
|
+
on_press=lambda key: None,
|
|
77
|
+
on_release=lambda key: None,
|
|
78
|
+
on_capslock=lambda state: self._refresh.emit(),
|
|
79
|
+
)
|
|
80
|
+
success, _ = listener.start()
|
|
81
|
+
if success:
|
|
82
|
+
self._listener = listener
|
|
83
|
+
else:
|
|
84
|
+
listener.stop()
|
|
85
|
+
|
|
86
|
+
@Slot(bool)
|
|
87
|
+
def _set_state(self, state: bool) -> None:
|
|
88
|
+
if self._read is not None and state != self._state:
|
|
89
|
+
self._state = state
|
|
90
|
+
self.changed.emit(state)
|
|
91
|
+
|
|
92
|
+
@Slot()
|
|
93
|
+
def _poll(self) -> None:
|
|
94
|
+
try:
|
|
95
|
+
if self._read is None:
|
|
96
|
+
return
|
|
97
|
+
self._set_state(self._read())
|
|
98
|
+
except Exception as error:
|
|
99
|
+
self.stop()
|
|
100
|
+
self.failed.emit(str(error))
|