persian-clock-widget 0.0.0-stage → 1.0.0
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.
- package/LICENSE +21 -0
- package/README-LINUX.md +128 -0
- package/README.md +263 -2
- package/assets/fonts/BYekan+ Bold.ttf +0 -0
- package/assets/fonts/Iranian Sans.ttf +0 -0
- package/assets/fonts/Vazir-Medium.ttf +0 -0
- package/assets/icons/app_icon.ico +0 -0
- package/assets/icons/app_icon.png +0 -0
- package/assets/sounds/alarm.mp3 +0 -0
- package/assets/sounds/axel.mp3 +0 -0
- package/assets/sounds/heeeeyyyyy.mp3 +0 -0
- package/assets/sounds/liilililililililii.mp3 +0 -0
- package/assets/sounds/morning_glory.mp3 +0 -0
- package/assets/sounds/peter_axel_f.mp3 +0 -0
- package/bin/persian-clock.js +60 -0
- package/main.py +26 -0
- package/package.json +39 -4
- package/release/windows/PersianClockWidget.exe +0 -0
- package/requirements.txt +3 -0
- package/src/__init__.py +8 -0
- package/src/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/__pycache__/alarm_manager.cpython-314.pyc +0 -0
- package/src/__pycache__/calendar_data.cpython-314.pyc +0 -0
- package/src/__pycache__/taskbar_clock.cpython-314.pyc +0 -0
- package/src/__pycache__/widget.cpython-314.pyc +0 -0
- package/src/__pycache__/win_utils.cpython-314.pyc +0 -0
- package/src/alarm_manager.py +1314 -0
- package/src/calendar_data.py +519 -0
- package/src/icon_generator.py +106 -0
- package/src/taskbar_clock.py +834 -0
- package/src/widget.py +3351 -0
- package/src/win_utils.py +541 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
نمایش ساعت و تاریخ شمسی در تسکبار ویندوز:
|
|
4
|
+
|
|
5
|
+
۱. WindowsClockReplacer: جایگزینی کامل ساعت و تاریخ پیشفرض انگلیسی ویندوز
|
|
6
|
+
- یک باکس سالید و مات که دقیقاً روی ساعت و تاریخ ویندوز در گوشه تسکبار مینشیند
|
|
7
|
+
- ساعت انگلیسی ویندوز را ۱۰۰٪ میپوشاند و مخفی میکند
|
|
8
|
+
- ساعت تهران و تاریخ شمسی را با فونت وزیر و خط دور مشکی نمایش میدهد
|
|
9
|
+
- کلیک چپ: باز/بسته کردن تقویم ماهانه
|
|
10
|
+
- کلیک راست: منوی تنظیمات
|
|
11
|
+
- قابلیت جابهجایی دستی با کشیدن ماوس (Drag)
|
|
12
|
+
|
|
13
|
+
۲. TaskbarClockOverlay: ساعت و تاریخ شفاف شناور روی تسکبار (کنار ساعت ویندوز)
|
|
14
|
+
|
|
15
|
+
۳. TaskbarButtonProxy: آیکون/دکمه برنامه در تسکبار ویندوز
|
|
16
|
+
|
|
17
|
+
طراحی و توسعه: شایان بامیار (® Shayan Bamyar)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import ctypes
|
|
21
|
+
from ctypes import wintypes
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
import tkinter as tk
|
|
26
|
+
from tkinter import font as tkfont
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import winreg
|
|
30
|
+
except ImportError:
|
|
31
|
+
winreg = None
|
|
32
|
+
|
|
33
|
+
IS_WIN = sys.platform == "win32"
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from src.alarm_manager import draw_outlined_canvas_text
|
|
37
|
+
from src.win_utils import (
|
|
38
|
+
apply_z_order_and_clickthrough,
|
|
39
|
+
get_resource_path,
|
|
40
|
+
hide_from_taskbar,
|
|
41
|
+
)
|
|
42
|
+
except ImportError:
|
|
43
|
+
from alarm_manager import draw_outlined_canvas_text
|
|
44
|
+
from win_utils import (
|
|
45
|
+
apply_z_order_and_clickthrough,
|
|
46
|
+
get_resource_path,
|
|
47
|
+
hide_from_taskbar,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
TB_TRANSPARENT_KEY = "#010204"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_taskbar_geometry():
|
|
54
|
+
"""
|
|
55
|
+
دریافت مختصات تسکبار اصلی ویندوز با ترکیب SystemParametersInfo و Win32 API
|
|
56
|
+
خروجی: (left, top, right, bottom, tray_left یا None)
|
|
57
|
+
"""
|
|
58
|
+
if not IS_WIN:
|
|
59
|
+
return 0, 1032, 1920, 1080, None
|
|
60
|
+
try:
|
|
61
|
+
user32 = ctypes.windll.user32
|
|
62
|
+
sw = user32.GetSystemMetrics(0)
|
|
63
|
+
sh = user32.GetSystemMetrics(1)
|
|
64
|
+
r = wintypes.RECT()
|
|
65
|
+
# SPI_GETWORKAREA = 0x0030
|
|
66
|
+
user32.SystemParametersInfoW(0x0030, 0, ctypes.byref(r), 0)
|
|
67
|
+
|
|
68
|
+
if r.bottom < sh:
|
|
69
|
+
tb_l, tb_t, tb_r, tb_b = 0, r.bottom, sw, sh
|
|
70
|
+
elif r.top > 0:
|
|
71
|
+
tb_l, tb_t, tb_r, tb_b = 0, 0, sw, r.top
|
|
72
|
+
elif r.right < sw:
|
|
73
|
+
tb_l, tb_t, tb_r, tb_b = r.right, 0, sw, sh
|
|
74
|
+
elif r.left > 0:
|
|
75
|
+
tb_l, tb_t, tb_r, tb_b = 0, 0, r.left, sh
|
|
76
|
+
else:
|
|
77
|
+
tb_l, tb_t, tb_r, tb_b = 0, sh - 48, sw, sh
|
|
78
|
+
|
|
79
|
+
tray_left = None
|
|
80
|
+
hwnd = user32.FindWindowW("Shell_TrayWnd", None)
|
|
81
|
+
if hwnd:
|
|
82
|
+
tray_hwnd = user32.FindWindowExW(hwnd, 0, "TrayNotifyWnd", None)
|
|
83
|
+
if tray_hwnd:
|
|
84
|
+
t_rect = wintypes.RECT()
|
|
85
|
+
user32.GetWindowRect(tray_hwnd, ctypes.byref(t_rect))
|
|
86
|
+
if tb_l <= t_rect.left < tb_r and (t_rect.right - t_rect.left) > 20:
|
|
87
|
+
tray_left = t_rect.left
|
|
88
|
+
|
|
89
|
+
return tb_l, tb_t, tb_r, tb_b, tray_left
|
|
90
|
+
except Exception:
|
|
91
|
+
sw = 1920
|
|
92
|
+
sh = 1080
|
|
93
|
+
return 0, sh - 48, sw, sh, None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def is_taskbar_light_theme() -> bool:
|
|
97
|
+
"""تشخیص تم روشن یا تاریک تسکبار ویندوز از رجیستری"""
|
|
98
|
+
if not IS_WIN or winreg is None:
|
|
99
|
+
return False
|
|
100
|
+
try:
|
|
101
|
+
with winreg.OpenKey(
|
|
102
|
+
winreg.HKEY_CURRENT_USER,
|
|
103
|
+
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
|
104
|
+
) as key:
|
|
105
|
+
val, _ = winreg.QueryValueEx(key, "SystemUsesLightTheme")
|
|
106
|
+
return int(val) == 1
|
|
107
|
+
except Exception:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def detect_taskbar_colors(tb_l: int, tb_t: int, tb_r: int, tb_b: int) -> tuple[str, str, bool]:
|
|
112
|
+
"""
|
|
113
|
+
تشخیص دقیق رنگ پسزمینه تسکبار با نمونهبرداری پیکسل و بررسی تم ویندوز
|
|
114
|
+
خروجی: (base_bg_hex, hover_bg_hex, is_light)
|
|
115
|
+
"""
|
|
116
|
+
is_light = is_taskbar_light_theme()
|
|
117
|
+
default_bg = "#EEEEEE" if is_light else "#1C1C1C"
|
|
118
|
+
default_hover = "#DFDFDF" if is_light else "#2B2B2B"
|
|
119
|
+
if not IS_WIN:
|
|
120
|
+
return default_bg, default_hover, is_light
|
|
121
|
+
try:
|
|
122
|
+
user32 = ctypes.windll.user32
|
|
123
|
+
gdi32 = ctypes.windll.gdi32
|
|
124
|
+
hdc = user32.GetDC(0)
|
|
125
|
+
if hdc:
|
|
126
|
+
sample_x = max(tb_l + 12, tb_r - 240)
|
|
127
|
+
sample_y = tb_t + max(4, (tb_b - tb_t) // 4)
|
|
128
|
+
color_ref = gdi32.GetPixel(hdc, int(sample_x), int(sample_y))
|
|
129
|
+
user32.ReleaseDC(0, hdc)
|
|
130
|
+
if color_ref != -1 and color_ref != 0xFFFFFFFF:
|
|
131
|
+
r = color_ref & 0xFF
|
|
132
|
+
g = (color_ref >> 8) & 0xFF
|
|
133
|
+
b = (color_ref >> 16) & 0xFF
|
|
134
|
+
lum = 0.299 * r + 0.587 * g + 0.114 * b
|
|
135
|
+
# اطمینان از همخوانی منطقی با تم روشن/تیره (تا رنگ تصادفی آیکون یا خطا نگیرد)
|
|
136
|
+
if (not is_light and lum < 110) or (is_light and lum > 150):
|
|
137
|
+
bg_hex = f"#{r:02X}{g:02X}{b:02X}"
|
|
138
|
+
if is_light:
|
|
139
|
+
hr, hg, hb = max(0, r - 18), max(0, g - 18), max(0, b - 18)
|
|
140
|
+
else:
|
|
141
|
+
hr, hg, hb = min(255, r + 18), min(255, g + 18), min(255, b + 18)
|
|
142
|
+
hover_hex = f"#{hr:02X}{hg:02X}{hb:02X}"
|
|
143
|
+
return bg_hex, hover_hex, (lum > 145)
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
return default_bg, default_hover, is_light
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def find_win10_tray_clock_hwnd() -> int | None:
|
|
150
|
+
"""یافتن پنجره ساعت کلاسیک ویندوز ۱۰ (TrayClockWClass) در صورت وجود"""
|
|
151
|
+
if not IS_WIN:
|
|
152
|
+
return None
|
|
153
|
+
try:
|
|
154
|
+
user32 = ctypes.windll.user32
|
|
155
|
+
shell = user32.FindWindowW("Shell_TrayWnd", None)
|
|
156
|
+
if not shell:
|
|
157
|
+
return None
|
|
158
|
+
tray = user32.FindWindowExW(shell, 0, "TrayNotifyWnd", None)
|
|
159
|
+
if not tray:
|
|
160
|
+
return None
|
|
161
|
+
clk = user32.FindWindowExW(tray, 0, "TrayClockWClass", None)
|
|
162
|
+
return int(clk) if clk else None
|
|
163
|
+
except Exception:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_windows11_tray_clock_hidden() -> bool:
|
|
168
|
+
"""بررسی وضعیت مخفی بودن ساعت و تاریخ انگلیسی در تنظیمات تسکبار ویندوز ۱۱"""
|
|
169
|
+
if not IS_WIN or winreg is None:
|
|
170
|
+
return False
|
|
171
|
+
try:
|
|
172
|
+
with winreg.OpenKey(
|
|
173
|
+
winreg.HKEY_CURRENT_USER,
|
|
174
|
+
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
|
|
175
|
+
) as key:
|
|
176
|
+
val, _ = winreg.QueryValueEx(key, "HideSystrayDateTimeValueName")
|
|
177
|
+
return int(val) == 1
|
|
178
|
+
except Exception:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def set_windows11_tray_clock_hidden(hide: bool) -> bool:
|
|
183
|
+
"""
|
|
184
|
+
تنظیم کلید رسمی ویندوز ۱۱ برای نمایش یا حذف ساعت و تاریخ انگلیسی در تسکبار:
|
|
185
|
+
HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\HideSystrayDateTimeValueName
|
|
186
|
+
"""
|
|
187
|
+
if not IS_WIN or winreg is None:
|
|
188
|
+
return False
|
|
189
|
+
try:
|
|
190
|
+
with winreg.CreateKey(
|
|
191
|
+
winreg.HKEY_CURRENT_USER,
|
|
192
|
+
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
|
|
193
|
+
) as key:
|
|
194
|
+
winreg.SetValueEx(key, "HideSystrayDateTimeValueName", 0, winreg.REG_DWORD, 1 if hide else 0)
|
|
195
|
+
# اطلاعرسانی به Explorer برای اعمال تنظیمات
|
|
196
|
+
ctypes.windll.user32.SendMessageTimeoutW(0xFFFF, 0x001A, 0, "TraySettings", 0x0002, 500, None)
|
|
197
|
+
return True
|
|
198
|
+
except Exception:
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class WindowsClockReplacer:
|
|
203
|
+
"""
|
|
204
|
+
جایگزینی کامل ساعت و تاریخ ویندوز در تسکبار:
|
|
205
|
+
- اتصال مستقیم به داخل نوار تسکبار ویندوز (Shell_TrayWnd) با SetParent تا واقعاً جزئی از تسکبار شود
|
|
206
|
+
- مخفی کردن ساعت کلاسیک ویندوز ۱۰ یا پوشاندن یکپارچه ساعت ویندوز ۱۱ با تشخیص خودکار رنگ تسکبار
|
|
207
|
+
- نمایش ساعت تهران و تاریخ شمسی با فونت وزیر
|
|
208
|
+
- کلیک چپ: باز/بسته کردن تقویم ماهانه | کلیک راست: منوی تنظیمات | کشیدن (Drag): تنظیم مکان
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(self, app):
|
|
212
|
+
self.app = app
|
|
213
|
+
self.root = app.root
|
|
214
|
+
self._press_x = 0
|
|
215
|
+
self._press_win_x = 0
|
|
216
|
+
self._dragged = False
|
|
217
|
+
self._last_w = 0
|
|
218
|
+
self._last_h = 0
|
|
219
|
+
self._last_rel_x = 0
|
|
220
|
+
self._last_rel_y = 0
|
|
221
|
+
self._is_hovered = False
|
|
222
|
+
self._last_info: dict | None = None
|
|
223
|
+
self._tip_win: tk.Toplevel | None = None
|
|
224
|
+
self._tip_timer: str | None = None
|
|
225
|
+
self._embedded_parent_hwnd: int | None = None
|
|
226
|
+
self._hidden_win10_clock_hwnd: int | None = None
|
|
227
|
+
self._last_color_probe_ts = 0.0
|
|
228
|
+
self._is_light_tb = is_taskbar_light_theme()
|
|
229
|
+
|
|
230
|
+
self.win = tk.Toplevel(self.root)
|
|
231
|
+
self.win.title("WindowsClockReplacer")
|
|
232
|
+
self.win.overrideredirect(True)
|
|
233
|
+
self.win.attributes("-topmost", True)
|
|
234
|
+
|
|
235
|
+
self.base_bg = "#EEEEEE" if self._is_light_tb else "#1C1C1C"
|
|
236
|
+
self.hover_bg = "#DFDFDF" if self._is_light_tb else "#2B2B2B"
|
|
237
|
+
self.win.configure(bg=self.base_bg)
|
|
238
|
+
|
|
239
|
+
self.canvas = tk.Canvas(
|
|
240
|
+
self.win,
|
|
241
|
+
bg=self.base_bg,
|
|
242
|
+
highlightthickness=0,
|
|
243
|
+
bd=0,
|
|
244
|
+
cursor="hand2",
|
|
245
|
+
)
|
|
246
|
+
self.canvas.pack(fill="both", expand=True)
|
|
247
|
+
|
|
248
|
+
# بایندینگ فقط روی canvas (نه self.win) تا رویداد کلیک دوبار اجرا نشود
|
|
249
|
+
self.canvas.bind("<ButtonPress-1>", self._on_press)
|
|
250
|
+
self.canvas.bind("<B1-Motion>", self._on_motion)
|
|
251
|
+
self.canvas.bind("<ButtonRelease-1>", self._on_release)
|
|
252
|
+
self.canvas.bind("<Button-3>", self._on_right_click)
|
|
253
|
+
self.canvas.bind("<Enter>", self._on_enter)
|
|
254
|
+
self.canvas.bind("<Leave>", self._on_leave)
|
|
255
|
+
|
|
256
|
+
self.win.after(50, self._attach_to_taskbar)
|
|
257
|
+
|
|
258
|
+
def _on_enter(self, event=None):
|
|
259
|
+
self._is_hovered = True
|
|
260
|
+
self.canvas.configure(bg=self.hover_bg)
|
|
261
|
+
self.win.configure(bg=self.hover_bg)
|
|
262
|
+
if self._tip_timer:
|
|
263
|
+
try:
|
|
264
|
+
self.win.after_cancel(self._tip_timer)
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
self._tip_timer = self.win.after(120, self._show_tip)
|
|
268
|
+
|
|
269
|
+
def _on_leave(self, event=None):
|
|
270
|
+
self._is_hovered = False
|
|
271
|
+
if self._tip_timer:
|
|
272
|
+
try:
|
|
273
|
+
self.win.after_cancel(self._tip_timer)
|
|
274
|
+
except Exception:
|
|
275
|
+
pass
|
|
276
|
+
self._tip_timer = None
|
|
277
|
+
self.canvas.configure(bg=self.base_bg)
|
|
278
|
+
self.win.configure(bg=self.base_bg)
|
|
279
|
+
self._hide_tip()
|
|
280
|
+
|
|
281
|
+
def _show_tip(self):
|
|
282
|
+
if not self._is_hovered or self._tip_win is not None or not self._last_info:
|
|
283
|
+
return
|
|
284
|
+
info = self._last_info
|
|
285
|
+
day_n = info.get("day_name", "")
|
|
286
|
+
dt_txt = info.get("date_text", "")
|
|
287
|
+
occ = info.get("occasion_text")
|
|
288
|
+
tip_text = f"{day_n} {dt_txt}"
|
|
289
|
+
if occ:
|
|
290
|
+
tip_text += f"\n{occ}"
|
|
291
|
+
try:
|
|
292
|
+
win = tk.Toplevel(self.win)
|
|
293
|
+
self._tip_win = win
|
|
294
|
+
win.overrideredirect(True)
|
|
295
|
+
win.attributes("-topmost", True)
|
|
296
|
+
lbl = tk.Label(
|
|
297
|
+
win,
|
|
298
|
+
text=tip_text,
|
|
299
|
+
font=(self.app.config.get("font_family", "Vazir"), 8, "bold"),
|
|
300
|
+
bg="#0F172A",
|
|
301
|
+
fg="#F8FAFC",
|
|
302
|
+
bd=1,
|
|
303
|
+
relief="solid",
|
|
304
|
+
padx=8,
|
|
305
|
+
pady=4,
|
|
306
|
+
justify="center",
|
|
307
|
+
)
|
|
308
|
+
lbl.pack()
|
|
309
|
+
win.update_idletasks()
|
|
310
|
+
tw = win.winfo_reqwidth()
|
|
311
|
+
th = win.winfo_reqheight()
|
|
312
|
+
wx = self.win.winfo_rootx()
|
|
313
|
+
wy = self.win.winfo_rooty()
|
|
314
|
+
tx = max(8, wx + (self._last_w - tw) // 2)
|
|
315
|
+
ty = max(8, wy - th - 6)
|
|
316
|
+
win.geometry(f"+{int(tx)}+{int(ty)}")
|
|
317
|
+
apply_z_order_and_clickthrough(win, "topmost", click_through=True)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
def _hide_tip(self):
|
|
322
|
+
if self._tip_win is not None:
|
|
323
|
+
try:
|
|
324
|
+
self._tip_win.destroy()
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
self._tip_win = None
|
|
328
|
+
|
|
329
|
+
def _get_hwnd(self) -> int:
|
|
330
|
+
self.win.update_idletasks()
|
|
331
|
+
wid = self.win.winfo_id()
|
|
332
|
+
try:
|
|
333
|
+
root_hwnd = ctypes.windll.user32.GetAncestor(wid, 2)
|
|
334
|
+
return int(root_hwnd) if root_hwnd else int(wid)
|
|
335
|
+
except Exception:
|
|
336
|
+
return int(wid)
|
|
337
|
+
|
|
338
|
+
def _is_foreground_fullscreen(self) -> bool:
|
|
339
|
+
if not IS_WIN:
|
|
340
|
+
return False
|
|
341
|
+
try:
|
|
342
|
+
user32 = ctypes.windll.user32
|
|
343
|
+
fg = user32.GetForegroundWindow()
|
|
344
|
+
if not fg:
|
|
345
|
+
return False
|
|
346
|
+
cls = ctypes.create_unicode_buffer(64)
|
|
347
|
+
user32.GetClassNameW(fg, cls, 64)
|
|
348
|
+
if cls.value in ("Shell_TrayWnd", "Progman", "WorkerW", "TkTopLevel"):
|
|
349
|
+
return False
|
|
350
|
+
r = wintypes.RECT()
|
|
351
|
+
user32.GetWindowRect(fg, ctypes.byref(r))
|
|
352
|
+
sw = user32.GetSystemMetrics(0)
|
|
353
|
+
sh = user32.GetSystemMetrics(1)
|
|
354
|
+
return r.left <= 0 and r.top <= 0 and r.right >= sw and r.bottom >= sh
|
|
355
|
+
except Exception:
|
|
356
|
+
return False
|
|
357
|
+
|
|
358
|
+
def _attach_to_taskbar(self):
|
|
359
|
+
"""تنظیم سبک غیرفعالساز (بدون قفل شدن صف ورودی) و مخفیسازی ساعت ویندوز ۱۰ در صورت وجود"""
|
|
360
|
+
if not IS_WIN or not self.win or not self.win.winfo_exists():
|
|
361
|
+
return
|
|
362
|
+
try:
|
|
363
|
+
user32 = ctypes.windll.user32
|
|
364
|
+
hwnd = self._get_hwnd()
|
|
365
|
+
GWL_EXSTYLE = -20
|
|
366
|
+
WS_EX_NOACTIVATE = 0x08000000
|
|
367
|
+
WS_EX_TOOLWINDOW = 0x00000080
|
|
368
|
+
WS_EX_APPWINDOW = 0x00040000
|
|
369
|
+
ex_style = user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
|
|
370
|
+
new_ex = (ex_style | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW
|
|
371
|
+
if new_ex != ex_style:
|
|
372
|
+
user32.SetWindowLongW(hwnd, GWL_EXSTYLE, new_ex)
|
|
373
|
+
|
|
374
|
+
# در ویندوز ۱۰، ساعت کلاسیک TrayClockWClass را مستقیماً مخفی کن
|
|
375
|
+
clk_hwnd = find_win10_tray_clock_hwnd()
|
|
376
|
+
if clk_hwnd and clk_hwnd != self._hidden_win10_clock_hwnd:
|
|
377
|
+
user32.ShowWindow(clk_hwnd, 0) # SW_HIDE
|
|
378
|
+
self._hidden_win10_clock_hwnd = clk_hwnd
|
|
379
|
+
except Exception:
|
|
380
|
+
hide_from_taskbar(self.win)
|
|
381
|
+
|
|
382
|
+
def _position_in_taskbar(self, x: int, y: int, w: int, h: int, tb_l: int, tb_t: int):
|
|
383
|
+
"""قرار دادن پنجره دقیقاً در مختصات تسکبار"""
|
|
384
|
+
if self._is_foreground_fullscreen():
|
|
385
|
+
if self.win.winfo_viewable():
|
|
386
|
+
self.win.withdraw()
|
|
387
|
+
return
|
|
388
|
+
if not self.win.winfo_viewable():
|
|
389
|
+
self.win.deiconify()
|
|
390
|
+
self.win.geometry(f"{int(w)}x{int(h)}+{int(x)}+{int(y)}")
|
|
391
|
+
self.keep_on_top()
|
|
392
|
+
|
|
393
|
+
def refresh(self, info: dict):
|
|
394
|
+
if not self.win or not self.win.winfo_exists():
|
|
395
|
+
return
|
|
396
|
+
self._last_info = info
|
|
397
|
+
cfg = self.app.config
|
|
398
|
+
use_fa = cfg.get("persian_digits", False)
|
|
399
|
+
font_fa = cfg.get("font_family", "Vazir")
|
|
400
|
+
font_clock = cfg.get("clock_font_family", "Vazir")
|
|
401
|
+
|
|
402
|
+
geo = get_taskbar_geometry()
|
|
403
|
+
if geo:
|
|
404
|
+
tb_l, tb_t, tb_r, tb_b, _ = geo
|
|
405
|
+
else:
|
|
406
|
+
sw = self.root.winfo_screenwidth()
|
|
407
|
+
sh = self.root.winfo_screenheight()
|
|
408
|
+
tb_l, tb_t, tb_r, tb_b = 0, sh - 48, sw, sh
|
|
409
|
+
|
|
410
|
+
if not self._embedded_parent_hwnd:
|
|
411
|
+
self._attach_to_taskbar()
|
|
412
|
+
|
|
413
|
+
# تشخیص خودکار رنگ تسکبار هر ۱۰ ثانیه (در حالت auto)
|
|
414
|
+
bg_cfg = cfg.get("replace_clock_bg", "auto")
|
|
415
|
+
if bg_cfg == "auto" or not bg_cfg:
|
|
416
|
+
now_ts = time.monotonic()
|
|
417
|
+
if now_ts - self._last_color_probe_ts > 10.0:
|
|
418
|
+
self._last_color_probe_ts = now_ts
|
|
419
|
+
self.base_bg, self.hover_bg, self._is_light_tb = detect_taskbar_colors(tb_l, tb_t, tb_r, tb_b)
|
|
420
|
+
else:
|
|
421
|
+
self.base_bg = bg_cfg
|
|
422
|
+
self._is_light_tb = False
|
|
423
|
+
self.hover_bg = "#2B2B2B"
|
|
424
|
+
|
|
425
|
+
tb_w = tb_r - tb_l
|
|
426
|
+
tb_h = tb_b - tb_t
|
|
427
|
+
horizontal = tb_w >= tb_h
|
|
428
|
+
|
|
429
|
+
# متن ساعت
|
|
430
|
+
time_txt = str(info.get("time_str", ""))[:5]
|
|
431
|
+
if cfg.get("taskbar_clock_seconds", False):
|
|
432
|
+
time_txt = str(info.get("time_str", ""))
|
|
433
|
+
time_txt = self.app.format_digits_public(time_txt, use_fa)
|
|
434
|
+
|
|
435
|
+
# متن تاریخ شمسی (کوتاه و استاندارد برای قرارگیری در تسکبار)
|
|
436
|
+
jy = info.get("jy")
|
|
437
|
+
jm = info.get("jm")
|
|
438
|
+
jd = info.get("jd")
|
|
439
|
+
date_txt = f"{jy:04d}/{jm:02d}/{jd:02d}"
|
|
440
|
+
date_txt = self.app.format_digits_public(date_txt, use_fa)
|
|
441
|
+
|
|
442
|
+
# عرض و ارتفاع باکس پوشش ساعت ویندوز
|
|
443
|
+
win_w = int(cfg.get("replace_clock_width", 118))
|
|
444
|
+
win_h = tb_h if horizontal else 56
|
|
445
|
+
|
|
446
|
+
# موقعیت: دقیقاً روی ساعت پیشفرض ویندوز در گوشه راست تسکبار
|
|
447
|
+
saved_off = cfg.get("replace_clock_offset", 8)
|
|
448
|
+
if horizontal:
|
|
449
|
+
x = tb_r - win_w - int(saved_off)
|
|
450
|
+
y = tb_t
|
|
451
|
+
else:
|
|
452
|
+
x = tb_l
|
|
453
|
+
y = tb_b - win_h - int(saved_off)
|
|
454
|
+
|
|
455
|
+
self.canvas.delete("all")
|
|
456
|
+
self.canvas.configure(width=win_w, height=win_h)
|
|
457
|
+
bg = self.hover_bg if self._is_hovered else self.base_bg
|
|
458
|
+
self.canvas.configure(bg=bg)
|
|
459
|
+
self.win.configure(bg=bg)
|
|
460
|
+
|
|
461
|
+
# حاشیه ملایم در حالت Hover
|
|
462
|
+
if self._is_hovered:
|
|
463
|
+
outline_c = "#CBD5E1" if self._is_light_tb else "#334155"
|
|
464
|
+
self.canvas.create_rectangle(1, 1, win_w - 2, win_h - 2, outline=outline_c, width=1)
|
|
465
|
+
|
|
466
|
+
is_hol = bool(info.get("is_holiday"))
|
|
467
|
+
if self._is_light_tb and (bg_cfg == "auto" or not bg_cfg):
|
|
468
|
+
clock_color = "#0F172A"
|
|
469
|
+
date_color = "#DC2626" if is_hol else "#334155"
|
|
470
|
+
else:
|
|
471
|
+
clock_color = cfg.get("clock_color", "#F8FAFC")
|
|
472
|
+
date_color = cfg.get("holiday_color", "#F87171") if is_hol else cfg.get("date_color", "#CBD5E1")
|
|
473
|
+
|
|
474
|
+
# موقعیت عمودی هماهنگ با تسکبار ویندوز ۱۰ و ۱۱
|
|
475
|
+
y_time = max(3, int(win_h * 0.10))
|
|
476
|
+
y_date = max(y_time + 17, int(win_h * 0.52))
|
|
477
|
+
|
|
478
|
+
if self._is_light_tb and (bg_cfg == "auto" or not bg_cfg):
|
|
479
|
+
self.canvas.create_text(
|
|
480
|
+
win_w // 2,
|
|
481
|
+
y_time,
|
|
482
|
+
text=time_txt,
|
|
483
|
+
font=(font_clock, 10, "bold"),
|
|
484
|
+
fill=clock_color,
|
|
485
|
+
anchor="n",
|
|
486
|
+
)
|
|
487
|
+
self.canvas.create_text(
|
|
488
|
+
win_w // 2,
|
|
489
|
+
y_date,
|
|
490
|
+
text=date_txt,
|
|
491
|
+
font=(font_fa, 8, "bold"),
|
|
492
|
+
fill=date_color,
|
|
493
|
+
anchor="n",
|
|
494
|
+
)
|
|
495
|
+
else:
|
|
496
|
+
# خط ۱: ساعت به وقت تهران
|
|
497
|
+
draw_outlined_canvas_text(
|
|
498
|
+
self.canvas,
|
|
499
|
+
win_w // 2,
|
|
500
|
+
y_time,
|
|
501
|
+
text=time_txt,
|
|
502
|
+
font_spec=(font_clock, 10, "bold"),
|
|
503
|
+
fill=clock_color,
|
|
504
|
+
outline="#000000",
|
|
505
|
+
anchor="n",
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# خط ۲: تاریخ شمسی
|
|
509
|
+
draw_outlined_canvas_text(
|
|
510
|
+
self.canvas,
|
|
511
|
+
win_w // 2,
|
|
512
|
+
y_date,
|
|
513
|
+
text=date_txt,
|
|
514
|
+
font_spec=(font_fa, 8, "bold"),
|
|
515
|
+
fill=date_color,
|
|
516
|
+
outline="#000000",
|
|
517
|
+
anchor="n",
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
self._last_w, self._last_h = win_w, win_h
|
|
521
|
+
if not self._dragged:
|
|
522
|
+
self._position_in_taskbar(x, y, win_w, win_h, tb_l, tb_t)
|
|
523
|
+
else:
|
|
524
|
+
self.keep_on_top()
|
|
525
|
+
|
|
526
|
+
def keep_on_top(self):
|
|
527
|
+
if not self.win or not self.win.winfo_exists():
|
|
528
|
+
return
|
|
529
|
+
if self._is_foreground_fullscreen():
|
|
530
|
+
if self.win.winfo_viewable():
|
|
531
|
+
self.win.withdraw()
|
|
532
|
+
return
|
|
533
|
+
if not self.win.winfo_viewable():
|
|
534
|
+
self.win.deiconify()
|
|
535
|
+
apply_z_order_and_clickthrough(self.win, "topmost", click_through=False)
|
|
536
|
+
|
|
537
|
+
def _on_press(self, event):
|
|
538
|
+
self._hide_tip()
|
|
539
|
+
self._press_x = event.x_root
|
|
540
|
+
self._press_win_x = self.win.winfo_rootx()
|
|
541
|
+
self._dragged = False
|
|
542
|
+
return "break"
|
|
543
|
+
|
|
544
|
+
def _on_motion(self, event):
|
|
545
|
+
dx = event.x_root - self._press_x
|
|
546
|
+
if abs(dx) > 4:
|
|
547
|
+
self._dragged = True
|
|
548
|
+
if self._dragged:
|
|
549
|
+
geo = get_taskbar_geometry()
|
|
550
|
+
new_x = self._press_win_x + dx
|
|
551
|
+
tb_l, tb_t = 0, self.win.winfo_rooty()
|
|
552
|
+
if geo:
|
|
553
|
+
tb_l, tb_t, tb_r, _, _ = geo
|
|
554
|
+
new_x = max(tb_l, min(new_x, tb_r - self._last_w))
|
|
555
|
+
self._position_in_taskbar(int(new_x), int(tb_t), self._last_w, self._last_h, tb_l, tb_t)
|
|
556
|
+
return "break"
|
|
557
|
+
|
|
558
|
+
def _on_release(self, event):
|
|
559
|
+
if self._dragged:
|
|
560
|
+
geo = get_taskbar_geometry()
|
|
561
|
+
tb_r = geo[2] if geo else self.root.winfo_screenwidth()
|
|
562
|
+
cur_x = self.win.winfo_rootx()
|
|
563
|
+
offset = tb_r - (cur_x + self._last_w)
|
|
564
|
+
self.app.config["replace_clock_offset"] = max(-10, min(800, int(offset)))
|
|
565
|
+
self.app._save_config()
|
|
566
|
+
self._dragged = False
|
|
567
|
+
return "break"
|
|
568
|
+
self.app.on_taskbar_clicked()
|
|
569
|
+
return "break"
|
|
570
|
+
|
|
571
|
+
def _on_right_click(self, event):
|
|
572
|
+
self._hide_tip()
|
|
573
|
+
self.app.show_menu_at_pointer()
|
|
574
|
+
return "break"
|
|
575
|
+
|
|
576
|
+
def destroy(self):
|
|
577
|
+
if self._tip_timer:
|
|
578
|
+
try:
|
|
579
|
+
self.win.after_cancel(self._tip_timer)
|
|
580
|
+
except Exception:
|
|
581
|
+
pass
|
|
582
|
+
self._tip_timer = None
|
|
583
|
+
self._hide_tip()
|
|
584
|
+
try:
|
|
585
|
+
user32 = ctypes.windll.user32
|
|
586
|
+
if self._hidden_win10_clock_hwnd:
|
|
587
|
+
user32.ShowWindow(self._hidden_win10_clock_hwnd, 5) # SW_SHOW
|
|
588
|
+
self._hidden_win10_clock_hwnd = None
|
|
589
|
+
if self._embedded_parent_hwnd and self.win and self.win.winfo_exists():
|
|
590
|
+
user32.SetParent(self._get_hwnd(), 0)
|
|
591
|
+
self._embedded_parent_hwnd = None
|
|
592
|
+
except Exception:
|
|
593
|
+
pass
|
|
594
|
+
try:
|
|
595
|
+
if self.win and self.win.winfo_exists():
|
|
596
|
+
self.win.destroy()
|
|
597
|
+
except Exception:
|
|
598
|
+
pass
|
|
599
|
+
self.win = None
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
class TaskbarClockOverlay:
|
|
603
|
+
"""ساعت و تاریخ شمسی شناور و شفاف روی نوار تسکبار (کنار ساعت ویندوز)"""
|
|
604
|
+
|
|
605
|
+
def __init__(self, app):
|
|
606
|
+
self.app = app
|
|
607
|
+
self.root = app.root
|
|
608
|
+
self._press_x = 0
|
|
609
|
+
self._press_win_x = 0
|
|
610
|
+
self._dragged = False
|
|
611
|
+
self._last_w = 0
|
|
612
|
+
self._last_h = 0
|
|
613
|
+
|
|
614
|
+
self.win = tk.Toplevel(self.root)
|
|
615
|
+
self.win.title("PersianTaskbarClock")
|
|
616
|
+
self.win.overrideredirect(True)
|
|
617
|
+
self.win.configure(bg=TB_TRANSPARENT_KEY)
|
|
618
|
+
self.win.attributes("-transparentcolor", TB_TRANSPARENT_KEY)
|
|
619
|
+
|
|
620
|
+
self.canvas = tk.Canvas(
|
|
621
|
+
self.win,
|
|
622
|
+
bg=TB_TRANSPARENT_KEY,
|
|
623
|
+
highlightthickness=0,
|
|
624
|
+
bd=0,
|
|
625
|
+
cursor="hand2",
|
|
626
|
+
)
|
|
627
|
+
self.canvas.pack(fill="both", expand=True)
|
|
628
|
+
|
|
629
|
+
# بایندینگ فقط روی canvas تا رویداد کلیک دوبار اجرا نشود
|
|
630
|
+
self.canvas.bind("<ButtonPress-1>", self._on_press)
|
|
631
|
+
self.canvas.bind("<B1-Motion>", self._on_motion)
|
|
632
|
+
self.canvas.bind("<ButtonRelease-1>", self._on_release)
|
|
633
|
+
self.canvas.bind("<Button-3>", self._on_right_click)
|
|
634
|
+
|
|
635
|
+
self.win.after(50, lambda: hide_from_taskbar(self.win))
|
|
636
|
+
|
|
637
|
+
def _measure(self, text: str, font_spec: tuple) -> tuple[int, int]:
|
|
638
|
+
f = tkfont.Font(family=font_spec[0], size=font_spec[1], weight="bold")
|
|
639
|
+
return f.measure(text), f.metrics("linespace")
|
|
640
|
+
|
|
641
|
+
def refresh(self, info: dict):
|
|
642
|
+
if not self.win or not self.win.winfo_exists():
|
|
643
|
+
return
|
|
644
|
+
cfg = self.app.config
|
|
645
|
+
use_fa = cfg.get("persian_digits", False)
|
|
646
|
+
font_fa = cfg.get("font_family", "Vazir")
|
|
647
|
+
font_clock = cfg.get("clock_font_family", "Vazir")
|
|
648
|
+
|
|
649
|
+
geo = get_taskbar_geometry()
|
|
650
|
+
if geo:
|
|
651
|
+
tb_l, tb_t, tb_r, tb_b, tray_left = geo
|
|
652
|
+
else:
|
|
653
|
+
sw = self.root.winfo_screenwidth()
|
|
654
|
+
sh = self.root.winfo_screenheight()
|
|
655
|
+
tb_l, tb_t, tb_r, tb_b, tray_left = 0, sh - 48, sw, sh, None
|
|
656
|
+
|
|
657
|
+
tb_w = tb_r - tb_l
|
|
658
|
+
tb_h = tb_b - tb_t
|
|
659
|
+
horizontal = tb_w >= tb_h
|
|
660
|
+
|
|
661
|
+
time_txt = str(info.get("time_str", ""))[:5]
|
|
662
|
+
if cfg.get("taskbar_clock_seconds", False):
|
|
663
|
+
time_txt = str(info.get("time_str", ""))
|
|
664
|
+
date_txt = f"{info.get('weekday_name', '')} {info.get('date_numeric', '')}"
|
|
665
|
+
if info.get("has_occasion") and info.get("is_holiday"):
|
|
666
|
+
date_txt += " • تعطیل"
|
|
667
|
+
|
|
668
|
+
time_txt = self.app.format_digits_public(time_txt, use_fa)
|
|
669
|
+
date_txt = self.app.format_digits_public(date_txt, use_fa)
|
|
670
|
+
|
|
671
|
+
bar_h = tb_h if horizontal else 56
|
|
672
|
+
t_size = 10 if bar_h >= 44 else 9
|
|
673
|
+
d_size = 8 if bar_h >= 44 else 7
|
|
674
|
+
t_spec = (font_clock, t_size, "bold")
|
|
675
|
+
d_spec = (font_fa, d_size, "bold")
|
|
676
|
+
tw, th = self._measure(time_txt, t_spec)
|
|
677
|
+
dw, dh = self._measure(date_txt, d_spec)
|
|
678
|
+
|
|
679
|
+
win_w = max(tw, dw) + 16
|
|
680
|
+
win_h = bar_h if horizontal else (th + dh + 10)
|
|
681
|
+
if not horizontal:
|
|
682
|
+
win_w = tb_w
|
|
683
|
+
|
|
684
|
+
if horizontal:
|
|
685
|
+
saved_off = cfg.get("taskbar_clock_offset")
|
|
686
|
+
if saved_off is None:
|
|
687
|
+
anchor_right = (tray_left if tray_left else tb_r - 240) - 6
|
|
688
|
+
x = anchor_right - win_w
|
|
689
|
+
else:
|
|
690
|
+
x = tb_r - int(saved_off) - win_w
|
|
691
|
+
x = max(tb_l, min(x, tb_r - win_w))
|
|
692
|
+
y = tb_t
|
|
693
|
+
else:
|
|
694
|
+
x = tb_l
|
|
695
|
+
y = tb_b - win_h - 110
|
|
696
|
+
|
|
697
|
+
self.canvas.delete("all")
|
|
698
|
+
self.canvas.configure(width=win_w, height=win_h)
|
|
699
|
+
total_text_h = th + dh
|
|
700
|
+
top = max(0, (win_h - total_text_h) // 2)
|
|
701
|
+
is_hol = bool(info.get("is_holiday"))
|
|
702
|
+
draw_outlined_canvas_text(
|
|
703
|
+
self.canvas,
|
|
704
|
+
win_w // 2,
|
|
705
|
+
top,
|
|
706
|
+
text=time_txt,
|
|
707
|
+
font_spec=t_spec,
|
|
708
|
+
fill=cfg.get("clock_color", "#F8FAFC"),
|
|
709
|
+
outline="#000000",
|
|
710
|
+
anchor="n",
|
|
711
|
+
)
|
|
712
|
+
draw_outlined_canvas_text(
|
|
713
|
+
self.canvas,
|
|
714
|
+
win_w // 2,
|
|
715
|
+
top + th,
|
|
716
|
+
text=date_txt,
|
|
717
|
+
font_spec=d_spec,
|
|
718
|
+
fill=cfg.get("holiday_color", "#F87171") if is_hol else cfg.get("date_color", "#CBD5E1"),
|
|
719
|
+
outline="#000000",
|
|
720
|
+
anchor="n",
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
self._last_w, self._last_h = win_w, win_h
|
|
724
|
+
|
|
725
|
+
if not self._dragged:
|
|
726
|
+
self.win.geometry(f"{int(win_w)}x{int(win_h)}+{int(x)}+{int(y)}")
|
|
727
|
+
self.keep_on_top()
|
|
728
|
+
|
|
729
|
+
def keep_on_top(self):
|
|
730
|
+
if self.win and self.win.winfo_exists():
|
|
731
|
+
apply_z_order_and_clickthrough(self.win, "topmost", click_through=False)
|
|
732
|
+
|
|
733
|
+
def _on_press(self, event):
|
|
734
|
+
self._press_x = event.x_root
|
|
735
|
+
self._press_win_x = self.win.winfo_x()
|
|
736
|
+
self._dragged = False
|
|
737
|
+
|
|
738
|
+
def _on_motion(self, event):
|
|
739
|
+
dx = event.x_root - self._press_x
|
|
740
|
+
if abs(dx) > 4:
|
|
741
|
+
self._dragged = True
|
|
742
|
+
if self._dragged:
|
|
743
|
+
geo = get_taskbar_geometry()
|
|
744
|
+
new_x = self._press_win_x + dx
|
|
745
|
+
if geo:
|
|
746
|
+
tb_l, _, tb_r, _, _ = geo
|
|
747
|
+
new_x = max(tb_l, min(new_x, tb_r - self._last_w))
|
|
748
|
+
self.win.geometry(f"+{int(new_x)}+{self.win.winfo_y()}")
|
|
749
|
+
|
|
750
|
+
def _on_release(self, event):
|
|
751
|
+
if self._dragged:
|
|
752
|
+
geo = get_taskbar_geometry()
|
|
753
|
+
tb_r = geo[2] if geo else self.root.winfo_screenwidth()
|
|
754
|
+
offset = tb_r - (self.win.winfo_x() + self._last_w)
|
|
755
|
+
self.app.config["taskbar_clock_offset"] = int(offset)
|
|
756
|
+
self.app._save_config()
|
|
757
|
+
self._dragged = False
|
|
758
|
+
return
|
|
759
|
+
self.app.on_taskbar_clicked()
|
|
760
|
+
|
|
761
|
+
def _on_right_click(self, event):
|
|
762
|
+
self.app.show_menu_at_pointer()
|
|
763
|
+
|
|
764
|
+
def destroy(self):
|
|
765
|
+
try:
|
|
766
|
+
if self.win and self.win.winfo_exists():
|
|
767
|
+
self.win.destroy()
|
|
768
|
+
except Exception:
|
|
769
|
+
pass
|
|
770
|
+
self.win = None
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
class TaskbarButtonProxy:
|
|
774
|
+
"""دکمه/آیکون معمولی برنامه در تسکبار ویندوز"""
|
|
775
|
+
|
|
776
|
+
def __init__(self, app):
|
|
777
|
+
self.app = app
|
|
778
|
+
self.root = app.root
|
|
779
|
+
self._busy = False
|
|
780
|
+
|
|
781
|
+
self.win = tk.Toplevel(self.root)
|
|
782
|
+
self.win.title("ساعت و تقویم شمسی")
|
|
783
|
+
self.win.geometry("1x1+-32000+-32000")
|
|
784
|
+
self.win.attributes("-alpha", 0.0)
|
|
785
|
+
ico_path = get_resource_path(os.path.join("assets", "icons", "app_icon.ico"))
|
|
786
|
+
if os.path.exists(ico_path):
|
|
787
|
+
try:
|
|
788
|
+
self.win.iconbitmap(ico_path)
|
|
789
|
+
except Exception:
|
|
790
|
+
pass
|
|
791
|
+
self.win.protocol("WM_DELETE_WINDOW", self._on_close_request)
|
|
792
|
+
self.win.update_idletasks()
|
|
793
|
+
self.win.iconify()
|
|
794
|
+
self.win.bind("<Map>", self._on_map)
|
|
795
|
+
|
|
796
|
+
def _on_map(self, event=None):
|
|
797
|
+
if self._busy or event is None or event.widget is not self.win:
|
|
798
|
+
return
|
|
799
|
+
if self.win.state() != "normal":
|
|
800
|
+
return
|
|
801
|
+
self._busy = True
|
|
802
|
+
|
|
803
|
+
def _act():
|
|
804
|
+
try:
|
|
805
|
+
self.win.iconify()
|
|
806
|
+
except Exception:
|
|
807
|
+
pass
|
|
808
|
+
self.app.on_taskbar_clicked()
|
|
809
|
+
self.win.after(250, self._release)
|
|
810
|
+
|
|
811
|
+
self.win.after(10, _act)
|
|
812
|
+
|
|
813
|
+
def _release(self):
|
|
814
|
+
self._busy = False
|
|
815
|
+
|
|
816
|
+
def _on_close_request(self):
|
|
817
|
+
self.app.config["taskbar_button"] = False
|
|
818
|
+
self.app._save_config()
|
|
819
|
+
self.app.sync_taskbar_features()
|
|
820
|
+
|
|
821
|
+
def update_title(self, text: str):
|
|
822
|
+
try:
|
|
823
|
+
if self.win and self.win.winfo_exists():
|
|
824
|
+
self.win.title(text)
|
|
825
|
+
except Exception:
|
|
826
|
+
pass
|
|
827
|
+
|
|
828
|
+
def destroy(self):
|
|
829
|
+
try:
|
|
830
|
+
if self.win and self.win.winfo_exists():
|
|
831
|
+
self.win.destroy()
|
|
832
|
+
except Exception:
|
|
833
|
+
pass
|
|
834
|
+
self.win = None
|