Skip to content

Commit 06e2012

Browse files
committed
+ Application mode feature
Current options: Do nothing | Apply inversion rules (default)
1 parent 1832d7b commit 06e2012

5 files changed

Lines changed: 120 additions & 37 deletions

File tree

active_window_checker.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import sys
1515
from asyncio import to_thread
1616
from dataclasses import dataclass
17+
from enum import Enum, auto
1718
from functools import cached_property
1819
import inject
1920
import win32con
@@ -193,6 +194,11 @@ def listen_switch_events(callback, close_manager: AppCloseManager):
193194
ole32.CoUninitialize()
194195

195196

197+
class AppMode(Enum):
198+
DISABLE = auto()
199+
RULES = auto()
200+
201+
196202
@dataclass
197203
class WinTrackerSettings:
198204
"""
@@ -205,6 +211,13 @@ class WinTrackerSettings:
205211
[{default!r}] Display all window switch events
206212
""", locals())
207213

214+
mode: AppMode = AppMode.RULES
215+
_comments_.add(f"""
216+
[{{default.name}}] How to process events: {' | '.join(e.name for e in AppMode)}
217+
\t{AppMode.DISABLE.name} - Ignore all events
218+
\t{AppMode.RULES.name} - Use rules to determine what to do
219+
""", locals())
220+
208221

209222
class FilterStateController:
210223
config = inject.attr(WinTrackerSettings)
@@ -249,4 +262,8 @@ def on_active_window_switched(self,
249262
def update_filter_state(self, winfo: WindowInfo = None):
250263
if winfo is None:
251264
winfo = self.last_active_window
252-
color_filter.set_active(self.rules.is_inversion_required(winfo))
265+
266+
mode = self.config.mode
267+
268+
if mode == AppMode.RULES:
269+
color_filter.set_active(self.rules.is_inversion_required(winfo))

app_start.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from inversion_rules import InversionRulesController
1010
from main_thread_loop import MainExecutor
1111
from settings import UserSettings, UserSettingsController
12-
from tray import Tray
12+
from tray.tray import Tray
1313

1414

1515
class AppStartManager:

tray/__init__.py

Whitespace-only changes.

tray.py renamed to tray/tray.py

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,23 @@
11
from asyncio import to_thread
2+
from traceback import print_exc
23
import inject
34
import win32gui
45
from win32con import SW_HIDE, SW_SHOW
56
import win32console
67
from PIL import Image
78
from pystray import Menu, MenuItem, Icon
89
import _meta as app
10+
from active_window_checker import AppMode
11+
from tray.utils import ref, make_toggle, make_radiobutton
912
from uac import has_admin_rights
1013
from app_close import AppCloseManager
1114
from auto_update import AutoUpdater
1215
from interaction import InteractionManager
1316
from inversion_rules import InversionRulesController
14-
from settings import UserSettingsController
17+
from settings import UserSettingsController, UserSettings, OPTION_PATH, OPTION_CHANGE_HANDLER, T
1518
from utils import explore, app_abs_path
1619

1720

18-
def make_toggle(out_func=None, default_value=False):
19-
def decorator(func):
20-
def wrapper(self):
21-
value = [default_value]
22-
23-
def get_value(item):
24-
return value[0]
25-
26-
def toggle():
27-
value[0] ^= True
28-
func(self, value[0])
29-
30-
return toggle, get_value
31-
return wrapper
32-
if out_func:
33-
return decorator(out_func)
34-
return decorator
35-
36-
3721
class Tray:
3822
settings_controller = inject.attr(UserSettingsController)
3923
inversion_rules = inject.attr(InversionRulesController)
@@ -56,12 +40,16 @@ def setup(self):
5640
win32gui.ShowWindow(self.console_hwnd, SW_HIDE)
5741

5842
def run(self):
59-
self.tray = Icon(
60-
app.__product_name__,
61-
Image.open(app_abs_path(app.__icon__)),
62-
menu=self.build_menu()
63-
)
64-
self.tray.run()
43+
try:
44+
self.tray = Icon(
45+
app.__product_name__,
46+
Image.open(app_abs_path(app.__icon__)),
47+
menu=self.build_menu()
48+
)
49+
self.tray.run()
50+
except:
51+
print_exc()
52+
raise
6553

6654
async def run_async(self):
6755
try:
@@ -82,14 +70,12 @@ def _wrapper(*ignore):
8270
def _open(path):
8371
return callback(explore, path)
8472

85-
def ref(text: str):
86-
"""
87-
Make first letter underscored, also
88-
mark this letter as shortcut for system tray,
89-
so you may press this letter on keyboard
90-
to select corresponding menu item
91-
"""
92-
return f'&{text[0]}\u0332{text[1:]}'
73+
change_mode_menu, change_mode_setter = self.change_mode()
74+
75+
self._link_with_settings(
76+
lambda settings: settings.win_tracker.mode,
77+
change_mode_setter
78+
)
9379

9480
im = self.im
9581
return Menu(
@@ -102,6 +88,10 @@ def ref(text: str):
10288
ref("Show console"),
10389
*self.toggle_console()
10490
),
91+
MenuItem(
92+
ref("Mode"),
93+
change_mode_menu
94+
),
10595
Menu.SEPARATOR,
10696
MenuItem(
10797
ref('Open'),
@@ -124,7 +114,7 @@ def ref(text: str):
124114
MenuItem(ref('Settings file'),
125115
callback(self.settings_controller.load)),
126116
MenuItem(ref('Inversion rules file'),
127-
callback(self.settings_controller.load)),
117+
callback(self.inversion_rules.load)),
128118
)
129119
),
130120
Menu.SEPARATOR,
@@ -140,9 +130,29 @@ def ref(text: str):
140130
callback(self.close_manager.close)),
141131
)
142132

133+
def _link_with_settings(self,
134+
path: OPTION_PATH,
135+
handler: OPTION_CHANGE_HANDLER,):
136+
def new_handler(value: T):
137+
handler(value)
138+
if self.tray:
139+
self.tray.update_menu()
140+
141+
self.settings_controller.add_option_change_handler(
142+
path, new_handler, True
143+
)
144+
143145
@make_toggle
144146
def toggle_console(self, value):
145147
win32gui.ShowWindow(
146148
self.console_hwnd,
147149
SW_SHOW if value else SW_HIDE
148150
)
151+
152+
@make_radiobutton({
153+
AppMode.DISABLE: ref("Ignore All"),
154+
AppMode.RULES: ref("According with rules"),
155+
}, AppMode.RULES)
156+
def change_mode(self, value: AppMode):
157+
self.settings_controller.settings.win_tracker.mode = value
158+
self.settings_controller.save()

tray/utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from enum import Enum
2+
from typing import Type, Hashable
3+
4+
from pystray import Menu, MenuItem
5+
6+
7+
def ref(text: str):
8+
"""
9+
Make first letter underscored, also
10+
mark this letter as shortcut for system tray,
11+
so you may press this letter on keyboard
12+
to select corresponding menu item
13+
"""
14+
return f'&{text[0]}\u0332{text[1:]}'
15+
16+
17+
def make_toggle(out_func=None, default_value=False):
18+
def decorator(func):
19+
def wrapper(self):
20+
value = [default_value]
21+
22+
def get_value(item):
23+
return value[0]
24+
25+
def toggle():
26+
value[0] ^= True
27+
func(self, value[0])
28+
29+
return toggle, get_value
30+
return wrapper
31+
if out_func:
32+
return decorator(out_func)
33+
return decorator
34+
35+
36+
def make_radiobutton(values: dict[Hashable, str],
37+
default_value=None,
38+
):
39+
def decorator(func):
40+
def wrapper(*args, **kwargs):
41+
value_ref = [default_value or next(iter(values))]
42+
43+
def change(value):
44+
value_ref[0] = value
45+
func(*args, value=value_ref[0], **kwargs)
46+
47+
return Menu(*[
48+
MenuItem(
49+
name,
50+
lambda *_, value=value: change(value),
51+
lambda *_, value=value: value_ref[0] == value,
52+
True
53+
) for value, name in values.items()
54+
]), change
55+
return wrapper
56+
return decorator

0 commit comments

Comments
 (0)