prkit 0.3.1__tar.gz → 0.3.2__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.
Files changed (27) hide show
  1. {prkit-0.3.1 → prkit-0.3.2}/PKG-INFO +6 -4
  2. {prkit-0.3.1 → prkit-0.3.2}/README.md +5 -3
  3. {prkit-0.3.1 → prkit-0.3.2}/pyproject.toml +1 -1
  4. {prkit-0.3.1 → prkit-0.3.2}/pyproject.toml.orig +1 -1
  5. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/__init__.py +1 -1
  6. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/app.py +37 -6
  7. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/language.py +5 -0
  8. prkit-0.3.2/src/prkit/message_dialog.py +87 -0
  9. prkit-0.3.2/src/prkit/single_instance.py +23 -0
  10. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/tray.py +1 -3
  11. {prkit-0.3.1 → prkit-0.3.2}/LICENSE +0 -0
  12. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/__main__.py +0 -0
  13. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/autostart.py +0 -0
  14. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/capslock.py +0 -0
  15. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/cursor_watchdog.py +0 -0
  16. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/icon.py +0 -0
  17. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/input_source.py +0 -0
  18. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/keyboard.py +0 -0
  19. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/keys.py +0 -0
  20. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/mac_capslock.py +0 -0
  21. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/mac_keyboard.py +0 -0
  22. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/overlays.py +0 -0
  23. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/platform.py +0 -0
  24. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/py.typed +0 -0
  25. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/settings.py +0 -0
  26. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/spotlight.py +0 -0
  27. {prkit-0.3.1 → prkit-0.3.2}/src/prkit/windows_cursor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prkit
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A cross-platform system-tray presentation toolkit
5
5
  Author: meebox
6
6
  Author-email: meebox <meebox@gmail.com>
@@ -23,6 +23,8 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  跨平台的系統匣簡報輔助工具。程式不會開啟主視窗,所有設定都在系統匣圖示的右鍵選單中完成。
25
25
 
26
+ 同一使用者只能執行一份 PRKit;再次啟動時會提示已有程式在執行,不會建立第二個系統匣圖示。
27
+
26
28
  ## 功能
27
29
 
28
30
  - 聚光燈游標:可調整顏色、透明度與 48–320 px 圓圈大小;Windows 使用真正的系統游標,macOS/Linux 使用不攔截點擊的透明覆蓋層。
@@ -38,8 +40,8 @@ Description-Content-Type: text/markdown
38
40
 
39
41
  | 平台 | 下載檔案 | 啟動方式 |
40
42
  | --- | --- | --- |
41
- | Windows x64 | `PRKit-0.3.1-windows-x64.zip` | 解壓完整資料夾後執行 `PRKit/PRKit.exe` |
42
- | macOS Apple Silicon | `PRKit-0.3.1-macos-arm64.dmg` | 開啟 DMG,將 `PRKit.app` 拖到「Applications」捷徑後啟動 |
43
+ | Windows x64 | `PRKit-0.3.2-windows-x64.zip` | 解壓完整資料夾後執行 `PRKit/PRKit.exe` |
44
+ | macOS Apple Silicon | `PRKit-0.3.2-macos-arm64.dmg` | 開啟 DMG,將 `PRKit.app` 拖到「Applications」捷徑後啟動 |
43
45
 
44
46
  Windows ZIP 包含完整執行環境與游標復原 watchdog;請保留整個 `PRKit` 資料夾。macOS 目前僅提供 Apple Silicon 版。程式啟動後顯示在系統匣/選單列,不會開啟主視窗。若啟用了開機自動啟動,搬移 Windows 資料夾後請在選單取消勾選再重新勾選,以更新啟動路徑。
45
47
 
@@ -72,7 +74,7 @@ uv tool install git+https://github.com/codemee/prkit.git@latest
72
74
  prkit
73
75
  ```
74
76
 
75
- 若要固定特定版本,將 `latest` 改成版本標籤,例如 `v0.3.1`。
77
+ 若要固定特定版本,將 `latest` 改成版本標籤,例如 `v0.3.2`。
76
78
 
77
79
  ## 授權
78
80
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  跨平台的系統匣簡報輔助工具。程式不會開啟主視窗,所有設定都在系統匣圖示的右鍵選單中完成。
6
6
 
7
+ 同一使用者只能執行一份 PRKit;再次啟動時會提示已有程式在執行,不會建立第二個系統匣圖示。
8
+
7
9
  ## 功能
8
10
 
9
11
  - 聚光燈游標:可調整顏色、透明度與 48–320 px 圓圈大小;Windows 使用真正的系統游標,macOS/Linux 使用不攔截點擊的透明覆蓋層。
@@ -19,8 +21,8 @@
19
21
 
20
22
  | 平台 | 下載檔案 | 啟動方式 |
21
23
  | --- | --- | --- |
22
- | Windows x64 | `PRKit-0.3.1-windows-x64.zip` | 解壓完整資料夾後執行 `PRKit/PRKit.exe` |
23
- | macOS Apple Silicon | `PRKit-0.3.1-macos-arm64.dmg` | 開啟 DMG,將 `PRKit.app` 拖到「Applications」捷徑後啟動 |
24
+ | Windows x64 | `PRKit-0.3.2-windows-x64.zip` | 解壓完整資料夾後執行 `PRKit/PRKit.exe` |
25
+ | macOS Apple Silicon | `PRKit-0.3.2-macos-arm64.dmg` | 開啟 DMG,將 `PRKit.app` 拖到「Applications」捷徑後啟動 |
24
26
 
25
27
  Windows ZIP 包含完整執行環境與游標復原 watchdog;請保留整個 `PRKit` 資料夾。macOS 目前僅提供 Apple Silicon 版。程式啟動後顯示在系統匣/選單列,不會開啟主視窗。若啟用了開機自動啟動,搬移 Windows 資料夾後請在選單取消勾選再重新勾選,以更新啟動路徑。
26
28
 
@@ -53,7 +55,7 @@ uv tool install git+https://github.com/codemee/prkit.git@latest
53
55
  prkit
54
56
  ```
55
57
 
56
- 若要固定特定版本,將 `latest` 改成版本標籤,例如 `v0.3.1`。
58
+ 若要固定特定版本,將 `latest` 改成版本標籤,例如 `v0.3.2`。
57
59
 
58
60
  ## 授權
59
61
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prkit"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "A cross-platform system-tray presentation toolkit"
5
5
  readme = "README.md"
6
6
  license = "GPL-3.0-or-later"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prkit"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "A cross-platform system-tray presentation toolkit"
5
5
  readme = "README.md"
6
6
  license = "GPL-3.0-or-later"
@@ -1,3 +1,3 @@
1
1
  """Presentation tools for the system tray."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.3.2"
@@ -10,9 +10,11 @@ from PySide6.QtWidgets import QApplication, QMessageBox
10
10
 
11
11
  from prkit import autostart
12
12
  from prkit.capslock import CapsLockMonitor
13
+ from prkit.icon import tray_icon
13
14
  from prkit.input_source import InputSourceMonitor
14
15
  from prkit.keyboard import KeyboardMonitor
15
16
  from prkit.language import effective_language, tr
17
+ from prkit.message_dialog import MessageDialog
16
18
  from prkit.overlays import CapsLockOverlay, KeystrokeOverlay
17
19
  from prkit.settings import SettingsStore, SettingsStoreProtocol
18
20
  from prkit.spotlight import SpotlightBackend, create_spotlight
@@ -344,18 +346,47 @@ def create_application(arguments: Sequence[str] | None = None) -> QApplication:
344
346
  app = QApplication(list(arguments) if arguments is not None else sys.argv)
345
347
  app.setOrganizationName("prkit")
346
348
  app.setApplicationName("簡報瑞士刀")
349
+ app.setWindowIcon(tray_icon())
347
350
  app.setQuitOnLastWindowClosed(False)
348
351
  return app
349
352
 
350
353
 
354
+ def show_message(app: QApplication, icon: QMessageBox.Icon, language: str, message: str) -> None:
355
+ dialog = MessageDialog(app, icon, language, message)
356
+ dialog.exec()
357
+
358
+
351
359
  def main() -> int:
352
360
  app = create_application()
353
- if not TrayController.is_available():
361
+ from prkit.single_instance import acquire
362
+
363
+ try:
364
+ instance_lock = acquire()
365
+ except OSError as error:
354
366
  language = effective_language(SettingsStore().load().language)
355
- QMessageBox.critical(
356
- None, tr("簡報瑞士刀", language), tr("此桌面環境沒有可用的系統匣。", language)
367
+ show_message(
368
+ app,
369
+ QMessageBox.Icon.Critical,
370
+ language,
371
+ tr("無法檢查是否已有執行中的 PRKit:{error}", language).format(error=error),
357
372
  )
358
373
  return 1
359
- controller = AppController(app)
360
- controller.start()
361
- return app.exec()
374
+ if instance_lock is None:
375
+ language = effective_language(SettingsStore().load().language)
376
+ show_message(app, QMessageBox.Icon.Information, language, tr("PRKit 已在執行。", language))
377
+ return 0
378
+ try:
379
+ if not TrayController.is_available():
380
+ language = effective_language(SettingsStore().load().language)
381
+ show_message(
382
+ app,
383
+ QMessageBox.Icon.Critical,
384
+ language,
385
+ tr("此桌面環境沒有可用的系統匣。", language),
386
+ )
387
+ return 1
388
+ controller = AppController(app)
389
+ controller.start()
390
+ return app.exec()
391
+ finally:
392
+ instance_lock.unlock()
@@ -32,6 +32,7 @@ ENGLISH = {
32
32
  "英文": "English",
33
33
  "中文(注音)": "Chinese (Zhuyin)",
34
34
  "結束": "Quit",
35
+ "確定": "OK",
35
36
  "無法設定開機自動啟動:{error}": "Could not change start at login: {error}",
36
37
  "無法顯示鍵盤狀態:{message}": "Could not display keyboard status: {message}",
37
38
  "無法啟用聚光燈游標:{error}": "Could not enable spotlight cursor: {error}",
@@ -40,6 +41,10 @@ ENGLISH = {
40
41
  "未知錯誤": "Unknown error",
41
42
  "按鍵監聽已停止:{message}": "Keystroke monitoring stopped: {message}",
42
43
  "此桌面環境沒有可用的系統匣。": "No system tray is available in this desktop environment.",
44
+ "PRKit 已在執行。": "PRKit is already running.",
45
+ "無法檢查是否已有執行中的 PRKit:{error}": (
46
+ "Could not check for a running PRKit instance: {error}"
47
+ ),
43
48
  }
44
49
 
45
50
 
@@ -0,0 +1,87 @@
1
+ """Compact, branded dialogs for startup messages."""
2
+
3
+ from PySide6.QtCore import Qt
4
+ from PySide6.QtWidgets import (
5
+ QApplication,
6
+ QDialog,
7
+ QHBoxLayout,
8
+ QLabel,
9
+ QMessageBox,
10
+ QPushButton,
11
+ QVBoxLayout,
12
+ )
13
+
14
+ from prkit.language import tr
15
+
16
+
17
+ class MessageDialog(QDialog):
18
+ def __init__(
19
+ self, app: QApplication, icon: QMessageBox.Icon, language: str, message: str
20
+ ) -> None:
21
+ super().__init__()
22
+ self.setObjectName("prkitMessageDialog")
23
+ self.setWindowTitle(tr("簡報瑞士刀", language))
24
+ self.setWindowIcon(app.windowIcon())
25
+ self.setWindowFlag(Qt.WindowType.WindowContextHelpButtonHint, False)
26
+
27
+ critical = icon == QMessageBox.Icon.Critical
28
+ self.badge = QLabel("!" if critical else "i", self)
29
+ self.badge.setObjectName("statusBadge")
30
+ self.badge.setFixedSize(20, 20)
31
+ self.badge.setAlignment(Qt.AlignmentFlag.AlignCenter)
32
+
33
+ self.message_label = QLabel(message, self)
34
+ self.message_label.setObjectName("messageText")
35
+ self.message_label.setWordWrap(True)
36
+ self.message_label.setMaximumWidth(360)
37
+ self.message_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
38
+ self.message_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
39
+
40
+ content = QHBoxLayout()
41
+ content.setSpacing(12)
42
+ content.addWidget(self.badge, alignment=Qt.AlignmentFlag.AlignVCenter)
43
+ content.addWidget(self.message_label, 1, Qt.AlignmentFlag.AlignVCenter)
44
+
45
+ self.confirm_button = QPushButton(tr("確定", language), self)
46
+ self.confirm_button.setObjectName("confirmButton")
47
+ self.confirm_button.setFixedSize(76, 32)
48
+ self.confirm_button.setDefault(True)
49
+ self.confirm_button.clicked.connect(self.accept)
50
+
51
+ actions = QHBoxLayout()
52
+ actions.addStretch()
53
+ actions.addWidget(self.confirm_button)
54
+
55
+ layout = QVBoxLayout(self)
56
+ layout.setContentsMargins(20, 20, 20, 18)
57
+ layout.setSpacing(22)
58
+ layout.addLayout(content)
59
+ layout.addLayout(actions)
60
+
61
+ badge_color = "#D64545" if critical else "#2563EB"
62
+ self.setStyleSheet(
63
+ "QDialog#prkitMessageDialog { background-color: #FFFFFF; }"
64
+ "QLabel#statusBadge { background-color: "
65
+ f"{badge_color}; color: #FFFFFF; border-radius: 10px; "
66
+ "font-size: 12px; font-weight: 600; }"
67
+ "QLabel#messageText { color: #263445; font-size: 13px; }"
68
+ "QPushButton#confirmButton { background-color: #2563EB; color: #FFFFFF; "
69
+ "border: 0; border-radius: 6px; font-size: 13px; }"
70
+ "QPushButton#confirmButton:hover { background-color: #1D4ED8; }"
71
+ "QPushButton#confirmButton:pressed { background-color: #1E40AF; }"
72
+ )
73
+ self.ensurePolished()
74
+ self.message_label.ensurePolished()
75
+ text_width = self.message_label.fontMetrics().horizontalAdvance(message)
76
+ margins = layout.contentsMargins()
77
+ non_text_width = margins.left() + self.badge.width() + content.spacing() + margins.right()
78
+ width = min(420, max(240, text_width + non_text_width))
79
+ text_height = self.message_label.heightForWidth(width - non_text_width)
80
+ height = (
81
+ margins.top()
82
+ + max(self.badge.height(), text_height)
83
+ + layout.spacing()
84
+ + self.confirm_button.height()
85
+ + margins.bottom()
86
+ )
87
+ self.setFixedSize(width, height)
@@ -0,0 +1,23 @@
1
+ """Keep one GUI process per user across installed and frozen copies."""
2
+
3
+ from pathlib import Path
4
+
5
+ from PySide6.QtCore import QLockFile, QStandardPaths
6
+
7
+
8
+ def acquire(directory: Path | None = None) -> QLockFile | None:
9
+ if directory is None:
10
+ location = QStandardPaths.writableLocation(
11
+ QStandardPaths.StandardLocation.AppLocalDataLocation
12
+ )
13
+ if not location:
14
+ raise OSError("No local application data directory is available")
15
+ directory = Path(location)
16
+ directory.mkdir(parents=True, exist_ok=True)
17
+ lock = QLockFile(str(directory / "prkit.lock"))
18
+ lock.setStaleLockTime(0)
19
+ if lock.tryLock(0):
20
+ return lock
21
+ if lock.error() == QLockFile.LockError.LockFailedError:
22
+ return None
23
+ raise OSError(f"Could not acquire the PRKit instance lock: {lock.error().name}")
@@ -336,9 +336,7 @@ class TrayController(QObject):
336
336
  self.tray.hide()
337
337
 
338
338
  def notify_warning(self, message: str) -> None:
339
- self.tray.showMessage(
340
- self.tray.toolTip(), message, QSystemTrayIcon.MessageIcon.Warning, 5000
341
- )
339
+ self.tray.showMessage(self.tray.toolTip(), message, self.tray.icon(), 5000)
342
340
 
343
341
  def connect_settings(
344
342
  self,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes