tool-tray 0.3.8__py3-none-any.whl
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.
- tool_tray/__init__.py +368 -0
- tool_tray/__main__.py +4 -0
- tool_tray/autostart.py +248 -0
- tool_tray/config.py +109 -0
- tool_tray/desktop.py +103 -0
- tool_tray/logging.py +76 -0
- tool_tray/manifest.py +61 -0
- tool_tray/setup_dialog.py +83 -0
- tool_tray/state.py +107 -0
- tool_tray/tray.py +414 -0
- tool_tray/updater.py +139 -0
- tool_tray-0.3.8.dist-info/METADATA +185 -0
- tool_tray-0.3.8.dist-info/RECORD +15 -0
- tool_tray-0.3.8.dist-info/WHEEL +4 -0
- tool_tray-0.3.8.dist-info/entry_points.txt +3 -0
tool_tray/__init__.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
__version__ = "0.3.8"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main() -> None:
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
args = sys.argv[1:]
|
|
8
|
+
|
|
9
|
+
if not args:
|
|
10
|
+
# Default: run tray app
|
|
11
|
+
from tool_tray.tray import run_tray
|
|
12
|
+
|
|
13
|
+
run_tray()
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
command = args[0]
|
|
17
|
+
|
|
18
|
+
if command == "encode":
|
|
19
|
+
_cmd_encode(args[1:])
|
|
20
|
+
elif command == "setup":
|
|
21
|
+
_cmd_setup(args[1:])
|
|
22
|
+
elif command == "reset":
|
|
23
|
+
_cmd_reset()
|
|
24
|
+
elif command == "init":
|
|
25
|
+
_cmd_init()
|
|
26
|
+
elif command == "autostart":
|
|
27
|
+
_cmd_autostart(args[1:])
|
|
28
|
+
elif command == "logs":
|
|
29
|
+
_cmd_logs(args[1:])
|
|
30
|
+
elif command == "cleanup":
|
|
31
|
+
_cmd_cleanup(args[1:])
|
|
32
|
+
elif command in ("-h", "--help", "help"):
|
|
33
|
+
_cmd_help()
|
|
34
|
+
elif command in ("-v", "--version", "version"):
|
|
35
|
+
print(f"tooltray {__version__}")
|
|
36
|
+
else:
|
|
37
|
+
print(f"Unknown command: {command}")
|
|
38
|
+
print("Run 'tooltray --help' for usage")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _cmd_help() -> None:
|
|
43
|
+
print("""Tool Tray - System tray tool manager
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
tooltray Run system tray app
|
|
47
|
+
tooltray setup Configure via GUI dialog
|
|
48
|
+
tooltray reset Remove config and start fresh
|
|
49
|
+
tooltray init Create tooltray.toml in current directory
|
|
50
|
+
tooltray encode Generate config code for sharing
|
|
51
|
+
tooltray autostart Manage system autostart
|
|
52
|
+
tooltray logs View log file
|
|
53
|
+
tooltray cleanup Remove orphaned desktop icons
|
|
54
|
+
|
|
55
|
+
Setup options:
|
|
56
|
+
--code CODE Config code (skip GUI dialog)
|
|
57
|
+
|
|
58
|
+
Encode options:
|
|
59
|
+
--token TOKEN GitHub PAT (required)
|
|
60
|
+
--repo ORG/REPO Repository to include (can be repeated)
|
|
61
|
+
--prefix PREFIX Code prefix for branding (default: TB)
|
|
62
|
+
|
|
63
|
+
Autostart options:
|
|
64
|
+
--enable Add tooltray to system startup
|
|
65
|
+
--disable Remove from system startup
|
|
66
|
+
--status Check if autostart is enabled
|
|
67
|
+
|
|
68
|
+
Logs options:
|
|
69
|
+
-f, --follow Tail log file (like tail -f)
|
|
70
|
+
--path Print log file path
|
|
71
|
+
|
|
72
|
+
Cleanup options:
|
|
73
|
+
--dry-run Show what would be removed
|
|
74
|
+
--force Remove without confirmation
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
tooltray setup
|
|
78
|
+
tooltray setup --code "TB-eyJ0b2tlbi..."
|
|
79
|
+
tooltray encode --token ghp_xxx --repo myorg/myapp --repo myorg/cli
|
|
80
|
+
tooltray autostart --enable
|
|
81
|
+
tooltray cleanup --dry-run
|
|
82
|
+
""")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _cmd_setup(args: list[str]) -> None:
|
|
86
|
+
from tool_tray.config import decode_config, save_config
|
|
87
|
+
|
|
88
|
+
# Check for --code flag
|
|
89
|
+
code: str | None = None
|
|
90
|
+
i = 0
|
|
91
|
+
while i < len(args):
|
|
92
|
+
if args[i] == "--code" and i + 1 < len(args):
|
|
93
|
+
code = args[i + 1]
|
|
94
|
+
break
|
|
95
|
+
i += 1
|
|
96
|
+
|
|
97
|
+
if code:
|
|
98
|
+
# Direct CLI mode
|
|
99
|
+
try:
|
|
100
|
+
config = decode_config(code)
|
|
101
|
+
save_config(config)
|
|
102
|
+
print("Configuration saved successfully!")
|
|
103
|
+
except ValueError as e:
|
|
104
|
+
print(f"Error: {e}")
|
|
105
|
+
else:
|
|
106
|
+
# GUI dialog mode
|
|
107
|
+
from tool_tray.setup_dialog import show_setup_dialog
|
|
108
|
+
|
|
109
|
+
if show_setup_dialog():
|
|
110
|
+
print("Configuration saved successfully!")
|
|
111
|
+
else:
|
|
112
|
+
print("Setup cancelled")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _cmd_reset() -> None:
|
|
116
|
+
from tool_tray.config import get_config_path
|
|
117
|
+
|
|
118
|
+
path = get_config_path()
|
|
119
|
+
if not path.exists():
|
|
120
|
+
print("No config found")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
print(f"Config file: {path}")
|
|
124
|
+
try:
|
|
125
|
+
confirm = input("Remove config? [y/N] ").strip().lower()
|
|
126
|
+
except (EOFError, KeyboardInterrupt):
|
|
127
|
+
print()
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if confirm == "y":
|
|
131
|
+
path.unlink()
|
|
132
|
+
print("Config removed")
|
|
133
|
+
else:
|
|
134
|
+
print("Cancelled")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _cmd_init() -> None:
|
|
138
|
+
from pathlib import Path
|
|
139
|
+
|
|
140
|
+
manifest_path = Path("tooltray.toml")
|
|
141
|
+
if manifest_path.exists():
|
|
142
|
+
print(f"Already exists: {manifest_path}")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
template = """name = "" # Display name in tray menu
|
|
146
|
+
type = "uv" # uv | git
|
|
147
|
+
launch = "" # Command to run when clicked
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
manifest_path.write_text(template)
|
|
151
|
+
|
|
152
|
+
print("""tooltray.toml created!
|
|
153
|
+
|
|
154
|
+
Tool Tray is a system tray app that manages tools from private GitHub repos.
|
|
155
|
+
Users get a config code with repo list + token, tooltray fetches manifests.
|
|
156
|
+
|
|
157
|
+
Edit tooltray.toml:
|
|
158
|
+
name - Display name in the tray menu
|
|
159
|
+
type - "uv" for Python tools, "git" for clone+build
|
|
160
|
+
launch - Command name to run when clicked (usually same as name)
|
|
161
|
+
|
|
162
|
+
Optional fields:
|
|
163
|
+
build - Build command for git type (e.g. "npm install")
|
|
164
|
+
desktop_icon - Set to true to create desktop shortcut
|
|
165
|
+
autostart - Set to true to launch on system startup
|
|
166
|
+
icon - Path to icon file in repo
|
|
167
|
+
|
|
168
|
+
Once configured, commit tooltray.toml to your repo.
|
|
169
|
+
""")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _cmd_autostart(args: list[str]) -> None:
|
|
173
|
+
import sys
|
|
174
|
+
|
|
175
|
+
from tool_tray.autostart import (
|
|
176
|
+
disable_autostart,
|
|
177
|
+
enable_autostart,
|
|
178
|
+
is_autostart_enabled,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if not args:
|
|
182
|
+
print("Usage: tooltray autostart [--enable|--disable|--status]")
|
|
183
|
+
sys.exit(1)
|
|
184
|
+
|
|
185
|
+
option = args[0]
|
|
186
|
+
if option == "--enable":
|
|
187
|
+
if enable_autostart():
|
|
188
|
+
print("Autostart enabled")
|
|
189
|
+
else:
|
|
190
|
+
sys.exit(1)
|
|
191
|
+
elif option == "--disable":
|
|
192
|
+
if disable_autostart():
|
|
193
|
+
print("Autostart disabled")
|
|
194
|
+
else:
|
|
195
|
+
sys.exit(1)
|
|
196
|
+
elif option == "--status":
|
|
197
|
+
if is_autostart_enabled():
|
|
198
|
+
print("Autostart: enabled")
|
|
199
|
+
else:
|
|
200
|
+
print("Autostart: disabled")
|
|
201
|
+
else:
|
|
202
|
+
print(f"Unknown option: {option}")
|
|
203
|
+
print("Usage: tooltray autostart [--enable|--disable|--status]")
|
|
204
|
+
sys.exit(1)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _cmd_logs(args: list[str]) -> None:
|
|
208
|
+
import time
|
|
209
|
+
|
|
210
|
+
from tool_tray.logging import get_log_dir
|
|
211
|
+
|
|
212
|
+
log_file = get_log_dir() / "tooltray.log"
|
|
213
|
+
|
|
214
|
+
if "--path" in args:
|
|
215
|
+
print(log_file)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
if not log_file.exists():
|
|
219
|
+
print(f"No log file yet: {log_file}")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
if "-f" in args or "--follow" in args:
|
|
223
|
+
try:
|
|
224
|
+
with open(log_file) as f:
|
|
225
|
+
f.seek(0, 2)
|
|
226
|
+
while True:
|
|
227
|
+
line = f.readline()
|
|
228
|
+
if line:
|
|
229
|
+
print(line, end="")
|
|
230
|
+
else:
|
|
231
|
+
time.sleep(0.5)
|
|
232
|
+
except KeyboardInterrupt:
|
|
233
|
+
pass
|
|
234
|
+
else:
|
|
235
|
+
lines = log_file.read_text().splitlines()
|
|
236
|
+
for line in lines[-50:]:
|
|
237
|
+
print(line)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _cmd_cleanup(args: list[str]) -> None:
|
|
241
|
+
from pathlib import Path
|
|
242
|
+
|
|
243
|
+
from tool_tray.config import load_config
|
|
244
|
+
from tool_tray.desktop import remove_desktop_icon
|
|
245
|
+
from tool_tray.manifest import fetch_manifest
|
|
246
|
+
from tool_tray.state import load_state, remove_icon_record
|
|
247
|
+
|
|
248
|
+
dry_run = "--dry-run" in args
|
|
249
|
+
force = "--force" in args
|
|
250
|
+
|
|
251
|
+
# Load config to get active repos
|
|
252
|
+
config = load_config()
|
|
253
|
+
if not config:
|
|
254
|
+
print("No config found. Run 'tooltray setup' first.")
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
token = config.get("token", "")
|
|
258
|
+
repos = config.get("repos", [])
|
|
259
|
+
active_repos = set(repos)
|
|
260
|
+
|
|
261
|
+
# Build manifest lookup for active repos
|
|
262
|
+
manifest_by_repo: dict[str, bool] = {} # repo -> desktop_icon enabled
|
|
263
|
+
for repo in repos:
|
|
264
|
+
manifest = fetch_manifest(repo, token)
|
|
265
|
+
if manifest:
|
|
266
|
+
manifest_by_repo[repo] = manifest.desktop_icon
|
|
267
|
+
|
|
268
|
+
# Find orphaned icons
|
|
269
|
+
state = load_state()
|
|
270
|
+
orphans: list[tuple[str, str, str]] = [] # (tool_name, path, reason)
|
|
271
|
+
|
|
272
|
+
for tool_name, record in state.desktop_icons.items():
|
|
273
|
+
icon_path = Path(record.path)
|
|
274
|
+
|
|
275
|
+
if not icon_path.exists():
|
|
276
|
+
orphans.append((tool_name, record.path, "file missing"))
|
|
277
|
+
elif record.repo not in active_repos:
|
|
278
|
+
orphans.append((tool_name, record.path, "repo removed"))
|
|
279
|
+
elif record.repo in manifest_by_repo and not manifest_by_repo[record.repo]:
|
|
280
|
+
orphans.append((tool_name, record.path, "desktop_icon disabled"))
|
|
281
|
+
|
|
282
|
+
if not orphans:
|
|
283
|
+
print("No orphaned icons found.")
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
# Display orphans
|
|
287
|
+
print(f"Found {len(orphans)} orphaned icon(s):\n")
|
|
288
|
+
for tool_name, path, reason in orphans:
|
|
289
|
+
print(f" {tool_name}")
|
|
290
|
+
print(f" Path: {path}")
|
|
291
|
+
print(f" Reason: {reason}\n")
|
|
292
|
+
|
|
293
|
+
if dry_run:
|
|
294
|
+
print("Dry run - no changes made.")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
# Confirm unless --force
|
|
298
|
+
if not force:
|
|
299
|
+
try:
|
|
300
|
+
confirm = input("Remove these icons? [y/N] ").strip().lower()
|
|
301
|
+
except (EOFError, KeyboardInterrupt):
|
|
302
|
+
print()
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
if confirm != "y":
|
|
306
|
+
print("Cancelled")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
# Remove orphans
|
|
310
|
+
removed = 0
|
|
311
|
+
for tool_name, path, reason in orphans:
|
|
312
|
+
if reason == "file missing":
|
|
313
|
+
remove_icon_record(tool_name)
|
|
314
|
+
removed += 1
|
|
315
|
+
print(f"Removed record: {tool_name}")
|
|
316
|
+
else:
|
|
317
|
+
if remove_desktop_icon(tool_name):
|
|
318
|
+
remove_icon_record(tool_name)
|
|
319
|
+
removed += 1
|
|
320
|
+
print(f"Removed: {tool_name}")
|
|
321
|
+
else:
|
|
322
|
+
print(f"Failed to remove: {tool_name}")
|
|
323
|
+
|
|
324
|
+
print(f"\nCleaned up {removed} icon(s).")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _cmd_encode(args: list[str]) -> None:
|
|
328
|
+
import sys
|
|
329
|
+
|
|
330
|
+
from tool_tray.config import encode_config
|
|
331
|
+
|
|
332
|
+
token = ""
|
|
333
|
+
prefix = "TB"
|
|
334
|
+
repos: list[str] = []
|
|
335
|
+
|
|
336
|
+
i = 0
|
|
337
|
+
while i < len(args):
|
|
338
|
+
arg = args[i]
|
|
339
|
+
if arg == "--token" and i + 1 < len(args):
|
|
340
|
+
token = args[i + 1]
|
|
341
|
+
i += 2
|
|
342
|
+
elif arg == "--prefix" and i + 1 < len(args):
|
|
343
|
+
prefix = args[i + 1]
|
|
344
|
+
i += 2
|
|
345
|
+
elif arg == "--repo" and i + 1 < len(args):
|
|
346
|
+
from urllib.parse import unquote
|
|
347
|
+
|
|
348
|
+
repo = unquote(args[i + 1]).strip().strip("'\"")
|
|
349
|
+
if "/" not in repo:
|
|
350
|
+
print(f"Invalid repo format: {repo}")
|
|
351
|
+
print("Expected: ORG/REPO (e.g., myorg/myapp)")
|
|
352
|
+
sys.exit(1)
|
|
353
|
+
repos.append(repo)
|
|
354
|
+
i += 2
|
|
355
|
+
else:
|
|
356
|
+
print(f"Unknown option: {arg}")
|
|
357
|
+
sys.exit(1)
|
|
358
|
+
|
|
359
|
+
if not token:
|
|
360
|
+
print("Error: --token is required")
|
|
361
|
+
sys.exit(1)
|
|
362
|
+
|
|
363
|
+
if not repos:
|
|
364
|
+
print("Error: at least one --repo is required")
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
code = encode_config(token, repos, prefix)
|
|
368
|
+
print(code)
|
tool_tray/__main__.py
ADDED
tool_tray/autostart.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _get_tooltray_path() -> str:
|
|
7
|
+
"""Get the path to tooltray executable."""
|
|
8
|
+
try:
|
|
9
|
+
result = subprocess.run(
|
|
10
|
+
["uv", "tool", "list", "--show-paths"],
|
|
11
|
+
capture_output=True,
|
|
12
|
+
text=True,
|
|
13
|
+
check=True,
|
|
14
|
+
)
|
|
15
|
+
for line in result.stdout.splitlines():
|
|
16
|
+
if line.startswith("- tooltray ") or line.startswith("- tool-tray "):
|
|
17
|
+
start = line.find("(")
|
|
18
|
+
end = line.find(")")
|
|
19
|
+
if start != -1 and end != -1:
|
|
20
|
+
return line[start + 1 : end]
|
|
21
|
+
except subprocess.CalledProcessError:
|
|
22
|
+
pass
|
|
23
|
+
return "tooltray"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _linux_autostart_enable() -> bool:
|
|
27
|
+
"""Add tooltray to Linux autostart."""
|
|
28
|
+
from tool_tray.logging import log_error, log_info
|
|
29
|
+
|
|
30
|
+
desktop_file = Path.home() / ".config/autostart/tooltray.desktop"
|
|
31
|
+
desktop_file.parent.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
exe = _get_tooltray_path()
|
|
33
|
+
try:
|
|
34
|
+
desktop_file.write_text(f"""[Desktop Entry]
|
|
35
|
+
Type=Application
|
|
36
|
+
Name=Tool Tray
|
|
37
|
+
Comment=System tray tool manager
|
|
38
|
+
Exec={exe}
|
|
39
|
+
Hidden=false
|
|
40
|
+
NoDisplay=false
|
|
41
|
+
X-GNOME-Autostart-enabled=true
|
|
42
|
+
""")
|
|
43
|
+
log_info(f"Autostart enabled: {desktop_file}")
|
|
44
|
+
print(f"Autostart enabled: {desktop_file}")
|
|
45
|
+
return True
|
|
46
|
+
except OSError as e:
|
|
47
|
+
log_error(f"Failed to enable autostart: {desktop_file}", e)
|
|
48
|
+
print(f"Failed to enable autostart: {e}")
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _linux_autostart_disable() -> bool:
|
|
53
|
+
"""Remove tooltray from Linux autostart."""
|
|
54
|
+
from tool_tray.logging import log_info
|
|
55
|
+
|
|
56
|
+
desktop_file = Path.home() / ".config/autostart/tooltray.desktop"
|
|
57
|
+
if desktop_file.exists():
|
|
58
|
+
desktop_file.unlink()
|
|
59
|
+
log_info(f"Autostart disabled: {desktop_file}")
|
|
60
|
+
print(f"Autostart disabled: {desktop_file}")
|
|
61
|
+
return True
|
|
62
|
+
print("Autostart was not enabled")
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _macos_autostart_enable() -> bool:
|
|
67
|
+
"""Add tooltray to macOS autostart via LaunchAgent."""
|
|
68
|
+
import shutil
|
|
69
|
+
|
|
70
|
+
from tool_tray.logging import log_error, log_info
|
|
71
|
+
|
|
72
|
+
plist = Path.home() / "Library/LaunchAgents/com.tooltray.plist"
|
|
73
|
+
plist.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
# Use sys.executable to run with same Python interpreter
|
|
76
|
+
python_exe = sys.executable
|
|
77
|
+
|
|
78
|
+
# Find uv binary path for PATH env (needed for get_installed_version)
|
|
79
|
+
uv_bin = shutil.which("uv")
|
|
80
|
+
if not uv_bin:
|
|
81
|
+
log_error("Cannot enable autostart: uv not found in PATH")
|
|
82
|
+
print("Error: uv not found in PATH. Install uv first.")
|
|
83
|
+
return False
|
|
84
|
+
uv_dir = str(Path(uv_bin).parent)
|
|
85
|
+
|
|
86
|
+
log_dir = Path.home() / "Library/Logs/tooltray"
|
|
87
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
try:
|
|
89
|
+
plist.write_text(f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
90
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
91
|
+
<plist version="1.0">
|
|
92
|
+
<dict>
|
|
93
|
+
<key>Label</key>
|
|
94
|
+
<string>com.tooltray</string>
|
|
95
|
+
<key>ProgramArguments</key>
|
|
96
|
+
<array>
|
|
97
|
+
<string>{python_exe}</string>
|
|
98
|
+
<string>-m</string>
|
|
99
|
+
<string>tool_tray</string>
|
|
100
|
+
</array>
|
|
101
|
+
<key>RunAtLoad</key>
|
|
102
|
+
<true/>
|
|
103
|
+
<key>KeepAlive</key>
|
|
104
|
+
<false/>
|
|
105
|
+
<key>EnvironmentVariables</key>
|
|
106
|
+
<dict>
|
|
107
|
+
<key>PATH</key>
|
|
108
|
+
<string>{uv_dir}:/usr/bin:/bin</string>
|
|
109
|
+
</dict>
|
|
110
|
+
<key>StandardOutPath</key>
|
|
111
|
+
<string>{log_dir}/stdout.log</string>
|
|
112
|
+
<key>StandardErrorPath</key>
|
|
113
|
+
<string>{log_dir}/stderr.log</string>
|
|
114
|
+
</dict>
|
|
115
|
+
</plist>""")
|
|
116
|
+
subprocess.run(["launchctl", "load", str(plist)], check=False)
|
|
117
|
+
log_info(f"Autostart enabled: {plist}")
|
|
118
|
+
print(f"Autostart enabled: {plist}")
|
|
119
|
+
return True
|
|
120
|
+
except OSError as e:
|
|
121
|
+
log_error(f"Failed to enable autostart: {plist}", e)
|
|
122
|
+
print(f"Failed to enable autostart: {e}")
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _macos_autostart_disable() -> bool:
|
|
127
|
+
"""Remove tooltray from macOS autostart."""
|
|
128
|
+
from tool_tray.logging import log_info
|
|
129
|
+
|
|
130
|
+
plist = Path.home() / "Library/LaunchAgents/com.tooltray.plist"
|
|
131
|
+
if plist.exists():
|
|
132
|
+
subprocess.run(["launchctl", "unload", str(plist)], check=False)
|
|
133
|
+
plist.unlink()
|
|
134
|
+
log_info(f"Autostart disabled: {plist}")
|
|
135
|
+
print(f"Autostart disabled: {plist}")
|
|
136
|
+
return True
|
|
137
|
+
print("Autostart was not enabled")
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _windows_autostart_enable() -> bool:
|
|
142
|
+
"""Add tooltray to Windows autostart via registry."""
|
|
143
|
+
from tool_tray.logging import log_error, log_info
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
import winreg
|
|
147
|
+
except ImportError:
|
|
148
|
+
print("winreg not available (not Windows)")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
exe = _get_tooltray_path()
|
|
152
|
+
try:
|
|
153
|
+
key = winreg.OpenKey(
|
|
154
|
+
winreg.HKEY_CURRENT_USER,
|
|
155
|
+
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
|
156
|
+
0,
|
|
157
|
+
winreg.KEY_SET_VALUE,
|
|
158
|
+
)
|
|
159
|
+
winreg.SetValueEx(key, "ToolTray", 0, winreg.REG_SZ, exe)
|
|
160
|
+
winreg.CloseKey(key)
|
|
161
|
+
log_info("Autostart enabled via registry")
|
|
162
|
+
print("Autostart enabled via registry")
|
|
163
|
+
return True
|
|
164
|
+
except OSError as e:
|
|
165
|
+
log_error("Failed to enable autostart via registry", e)
|
|
166
|
+
print(f"Failed to enable autostart: {e}")
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _windows_autostart_disable() -> bool:
|
|
171
|
+
"""Remove tooltray from Windows autostart."""
|
|
172
|
+
from tool_tray.logging import log_info
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
import winreg
|
|
176
|
+
except ImportError:
|
|
177
|
+
print("winreg not available (not Windows)")
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
key = winreg.OpenKey(
|
|
182
|
+
winreg.HKEY_CURRENT_USER,
|
|
183
|
+
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
|
184
|
+
0,
|
|
185
|
+
winreg.KEY_SET_VALUE,
|
|
186
|
+
)
|
|
187
|
+
winreg.DeleteValue(key, "ToolTray")
|
|
188
|
+
winreg.CloseKey(key)
|
|
189
|
+
log_info("Autostart disabled via registry")
|
|
190
|
+
print("Autostart disabled")
|
|
191
|
+
return True
|
|
192
|
+
except FileNotFoundError:
|
|
193
|
+
print("Autostart was not enabled")
|
|
194
|
+
return False
|
|
195
|
+
except OSError as e:
|
|
196
|
+
print(f"Failed to disable autostart: {e}")
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def enable_autostart() -> bool:
|
|
201
|
+
"""Add tooltray to system autostart."""
|
|
202
|
+
from tool_tray.logging import log_debug
|
|
203
|
+
|
|
204
|
+
log_debug(f"Enabling autostart on {sys.platform}")
|
|
205
|
+
if sys.platform == "darwin":
|
|
206
|
+
return _macos_autostart_enable()
|
|
207
|
+
elif sys.platform == "win32":
|
|
208
|
+
return _windows_autostart_enable()
|
|
209
|
+
else:
|
|
210
|
+
return _linux_autostart_enable()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def disable_autostart() -> bool:
|
|
214
|
+
"""Remove tooltray from system autostart."""
|
|
215
|
+
from tool_tray.logging import log_debug
|
|
216
|
+
|
|
217
|
+
log_debug(f"Disabling autostart on {sys.platform}")
|
|
218
|
+
if sys.platform == "darwin":
|
|
219
|
+
return _macos_autostart_disable()
|
|
220
|
+
elif sys.platform == "win32":
|
|
221
|
+
return _windows_autostart_disable()
|
|
222
|
+
else:
|
|
223
|
+
return _linux_autostart_disable()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def is_autostart_enabled() -> bool:
|
|
227
|
+
"""Check if autostart is currently enabled."""
|
|
228
|
+
if sys.platform == "darwin":
|
|
229
|
+
plist = Path.home() / "Library/LaunchAgents/com.tooltray.plist"
|
|
230
|
+
return plist.exists()
|
|
231
|
+
elif sys.platform == "win32":
|
|
232
|
+
try:
|
|
233
|
+
import winreg
|
|
234
|
+
|
|
235
|
+
key = winreg.OpenKey(
|
|
236
|
+
winreg.HKEY_CURRENT_USER,
|
|
237
|
+
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
|
238
|
+
0,
|
|
239
|
+
winreg.KEY_READ,
|
|
240
|
+
)
|
|
241
|
+
winreg.QueryValueEx(key, "ToolTray")
|
|
242
|
+
winreg.CloseKey(key)
|
|
243
|
+
return True
|
|
244
|
+
except (ImportError, FileNotFoundError, OSError):
|
|
245
|
+
return False
|
|
246
|
+
else:
|
|
247
|
+
desktop_file = Path.home() / ".config/autostart/tooltray.desktop"
|
|
248
|
+
return desktop_file.exists()
|