Skip to content

Commit 65b1e9a

Browse files
committed
feat(utils): add automatic desktop shortcut creation
1 parent b1194d8 commit 65b1e9a

3 files changed

Lines changed: 281 additions & 0 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies = [
3737
[project.optional-dependencies]
3838
serve = ["flask>=3.0", "pyyaml>=6.0"]
3939
serve-restful = ["flask>=3.0", "flask-cors>=4.0"]
40+
shortcuts = ["pywin32; sys_platform == 'win32'", "winshell; sys_platform == 'win32'"]
4041
dev = ["build", "pyinstaller", "pytest>=7.0.0", "pytest-cov", "pytest-asyncio", "pytest-mock"]
4142

4243
[tool.setuptools.packages.find]

weeb_cli/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ def start():
6565
if not config.get("language"):
6666
run_setup()
6767

68+
# Create desktop shortcuts in background (non-blocking)
69+
import threading
70+
def _create_shortcuts_background():
71+
try:
72+
from weeb_cli.utils.shortcuts import create_shortcuts
73+
create_shortcuts()
74+
except Exception:
75+
pass # Silently fail, shortcuts are optional
76+
77+
threading.Thread(target=_create_shortcuts_background, daemon=True).start()
78+
6879
# Initialize AniSkip service with config
6980
from weeb_cli.services.aniskip import aniskip_service
7081
aniskip_enabled = config.get("aniskip_enabled", False)

weeb_cli/utils/shortcuts.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
"""Desktop shortcut creation utilities.
2+
3+
Creates platform-specific shortcuts for easy application access:
4+
- Linux: .desktop file in ~/.local/share/applications/
5+
- macOS: .app alias in /Applications/
6+
- Windows: Start Menu shortcut
7+
"""
8+
9+
import os
10+
import sys
11+
import shutil
12+
from pathlib import Path
13+
from typing import Optional
14+
15+
16+
def get_executable_path() -> str:
17+
"""Get the path to the weeb-cli executable.
18+
19+
Returns:
20+
Path to the executable or python script.
21+
"""
22+
if getattr(sys, 'frozen', False):
23+
# Running as compiled executable
24+
return sys.executable
25+
else:
26+
# Running as Python script
27+
return shutil.which('weeb-cli') or sys.executable
28+
29+
30+
def get_icon_path() -> Optional[str]:
31+
"""Get the path to the application icon if available.
32+
33+
Returns:
34+
Path to icon file or None.
35+
"""
36+
# Try to find icon in package
37+
try:
38+
import weeb_cli
39+
package_dir = Path(weeb_cli.__file__).parent
40+
icon_path = package_dir / 'assets' / 'icon.png'
41+
if icon_path.exists():
42+
return str(icon_path)
43+
except Exception:
44+
pass
45+
return None
46+
47+
48+
def create_linux_desktop_file() -> bool:
49+
"""Create .desktop file for Linux.
50+
51+
Creates a desktop entry in ~/.local/share/applications/
52+
following the freedesktop.org specification.
53+
54+
Returns:
55+
True if successful, False otherwise.
56+
"""
57+
try:
58+
apps_dir = Path.home() / '.local' / 'share' / 'applications'
59+
apps_dir.mkdir(parents=True, exist_ok=True)
60+
61+
desktop_file = apps_dir / 'weeb-cli.desktop'
62+
63+
# Don't recreate if it already exists
64+
if desktop_file.exists():
65+
return True
66+
67+
executable = get_executable_path()
68+
icon = get_icon_path() or 'utilities-terminal'
69+
70+
content = f"""[Desktop Entry]
71+
Version=1.0
72+
Type=Application
73+
Name=Weeb CLI
74+
Comment=Terminal-based anime streaming application
75+
Exec={executable}
76+
Icon={icon}
77+
Terminal=true
78+
Categories=AudioVideo;Video;Player;
79+
Keywords=anime;streaming;video;
80+
StartupNotify=false
81+
"""
82+
83+
desktop_file.write_text(content)
84+
desktop_file.chmod(0o755)
85+
86+
return True
87+
except Exception:
88+
return False
89+
90+
91+
def create_macos_alias() -> bool:
92+
"""Create application alias for macOS.
93+
94+
Creates a symbolic link in /Applications/ if user has permissions,
95+
otherwise creates in ~/Applications/.
96+
97+
Returns:
98+
True if successful, False otherwise.
99+
"""
100+
try:
101+
executable = get_executable_path()
102+
103+
# Try system Applications first, fallback to user Applications
104+
for apps_dir in [Path('/Applications'), Path.home() / 'Applications']:
105+
apps_dir.mkdir(parents=True, exist_ok=True)
106+
107+
link_path = apps_dir / 'Weeb CLI'
108+
109+
# Don't recreate if it already exists
110+
if link_path.exists():
111+
return True
112+
113+
try:
114+
# Create symbolic link
115+
link_path.symlink_to(executable)
116+
return True
117+
except PermissionError:
118+
# Try next location
119+
continue
120+
121+
return False
122+
except Exception:
123+
return False
124+
125+
126+
def create_windows_shortcut() -> bool:
127+
"""Create Start Menu shortcut for Windows.
128+
129+
Creates a shortcut in the Start Menu using Windows COM API.
130+
131+
Returns:
132+
True if successful, False otherwise.
133+
"""
134+
try:
135+
import winshell
136+
from win32com.client import Dispatch
137+
138+
start_menu = Path(winshell.start_menu())
139+
programs = start_menu / 'Programs'
140+
programs.mkdir(parents=True, exist_ok=True)
141+
142+
shortcut_path = programs / 'Weeb CLI.lnk'
143+
144+
# Don't recreate if it already exists
145+
if shortcut_path.exists():
146+
return True
147+
148+
executable = get_executable_path()
149+
150+
shell = Dispatch('WScript.Shell')
151+
shortcut = shell.CreateShortCut(str(shortcut_path))
152+
shortcut.TargetPath = executable
153+
shortcut.WorkingDirectory = str(Path.home())
154+
shortcut.Description = 'Terminal-based anime streaming application'
155+
156+
icon = get_icon_path()
157+
if icon:
158+
shortcut.IconLocation = icon
159+
160+
shortcut.save()
161+
162+
return True
163+
except ImportError:
164+
# winshell or pywin32 not available
165+
return False
166+
except Exception:
167+
return False
168+
169+
170+
def should_create_shortcuts() -> bool:
171+
"""Check if shortcuts should be created.
172+
173+
Returns:
174+
True if this is first run or shortcuts are missing.
175+
"""
176+
from weeb_cli.config import CONFIG_DIR
177+
178+
marker_file = CONFIG_DIR / '.shortcuts_created'
179+
180+
# If marker exists, check if shortcuts still exist
181+
if marker_file.exists():
182+
if sys.platform == 'linux':
183+
desktop_file = Path.home() / '.local' / 'share' / 'applications' / 'weeb-cli.desktop'
184+
return not desktop_file.exists()
185+
elif sys.platform == 'darwin':
186+
for apps_dir in [Path('/Applications'), Path.home() / 'Applications']:
187+
if (apps_dir / 'Weeb CLI').exists():
188+
return False
189+
return True
190+
elif sys.platform == 'win32':
191+
try:
192+
import winshell
193+
start_menu = Path(winshell.start_menu())
194+
shortcut = start_menu / 'Programs' / 'Weeb CLI.lnk'
195+
return not shortcut.exists()
196+
except ImportError:
197+
return False
198+
199+
return True
200+
201+
202+
def create_shortcuts() -> bool:
203+
"""Create platform-specific shortcuts.
204+
205+
Automatically detects the platform and creates appropriate shortcuts.
206+
Only runs on first launch or if shortcuts are missing.
207+
208+
Returns:
209+
True if shortcuts were created successfully.
210+
"""
211+
if not should_create_shortcuts():
212+
return True
213+
214+
success = False
215+
216+
if sys.platform == 'linux':
217+
success = create_linux_desktop_file()
218+
elif sys.platform == 'darwin':
219+
success = create_macos_alias()
220+
elif sys.platform == 'win32':
221+
success = create_windows_shortcut()
222+
223+
# Mark as created even if failed to avoid repeated attempts
224+
if success:
225+
from weeb_cli.config import CONFIG_DIR
226+
marker_file = CONFIG_DIR / '.shortcuts_created'
227+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
228+
marker_file.touch()
229+
230+
return success
231+
232+
233+
def remove_shortcuts() -> bool:
234+
"""Remove all platform-specific shortcuts.
235+
236+
Returns:
237+
True if shortcuts were removed successfully.
238+
"""
239+
try:
240+
if sys.platform == 'linux':
241+
desktop_file = Path.home() / '.local' / 'share' / 'applications' / 'weeb-cli.desktop'
242+
if desktop_file.exists():
243+
desktop_file.unlink()
244+
245+
elif sys.platform == 'darwin':
246+
for apps_dir in [Path('/Applications'), Path.home() / 'Applications']:
247+
link_path = apps_dir / 'Weeb CLI'
248+
if link_path.exists():
249+
link_path.unlink()
250+
251+
elif sys.platform == 'win32':
252+
try:
253+
import winshell
254+
start_menu = Path(winshell.start_menu())
255+
shortcut = start_menu / 'Programs' / 'Weeb CLI.lnk'
256+
if shortcut.exists():
257+
shortcut.unlink()
258+
except ImportError:
259+
pass
260+
261+
# Remove marker
262+
from weeb_cli.config import CONFIG_DIR
263+
marker_file = CONFIG_DIR / '.shortcuts_created'
264+
if marker_file.exists():
265+
marker_file.unlink()
266+
267+
return True
268+
except Exception:
269+
return False

0 commit comments

Comments
 (0)