1+ import argparse
2+ import subprocess
3+ import sys
4+ import logging
5+ import os
6+ import time
7+ import re
8+ import winshell
9+ import psutil
10+ import win32com .client
11+ import datetime
12+ import getpass
13+ import win32security
14+ import ctypes
15+ from settings import BUILD_VERSION
16+ from colorama import just_fix_windows_console
17+ just_fix_windows_console ()
18+
19+ # Check if exe file exists
20+ OS_STARTUP_FOLDER = os .path .join (os .environ ['APPDATA' ], r'Microsoft\Windows\Start Menu\Programs\Startup' )
21+ INSTALLATION_PATH = os .path .abspath (os .path .join (__file__ , "../../.." ))
22+ EXE_PATH = os .path .join (INSTALLATION_PATH , 'yasb.exe' )
23+ SHORTCUT_FILENAME = "yasb.lnk"
24+ AUTOSTART_FILE = EXE_PATH if os .path .exists (EXE_PATH ) else None
25+ WORKING_DIRECTORY = INSTALLATION_PATH if os .path .exists (EXE_PATH ) else None
26+
27+ def is_process_running (process_name ):
28+ for proc in psutil .process_iter (['name' ]):
29+ if proc .info ['name' ] == process_name :
30+ return True
31+ return False
32+
33+ class Format :
34+ end = '\033 [0m'
35+ underline = '\033 [4m'
36+ gray = '\033 [90m'
37+ red = '\033 [91m'
38+ yellow = '\033 [93m'
39+ blue = '\033 [94m'
40+ green = '\033 [92m'
41+
42+ def format_log_line (line ):
43+ line = re .sub (r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' , f"{ Format .gray } \\ g<0>{ Format .end } " , line , count = 1 )
44+
45+ log_levels = ["CRITICAL" , "ERROR" , "WARNING" , "NOTICE" , "INFO" , "DEBUG" , "TRACE" ]
46+ for level in log_levels :
47+ if level in line :
48+ padded_level = level .ljust (8 )
49+ if level in ['CRITICAL' , 'ERROR' ]:
50+ line = line .replace (level , f"{ Format .red } { padded_level } { Format .end } " )
51+ elif level == "WARNING" :
52+ line = line .replace (level , f"{ Format .yellow } { padded_level } { Format .end } " )
53+ elif level == "NOTICE" :
54+ line = line .replace (level , f"{ Format .green } { padded_level } { Format .end } " )
55+ elif level == "TRACE" :
56+ line = line .replace (level , f"{ Format .blue } { padded_level } { Format .end } " )
57+ else :
58+ line = line .replace (level , padded_level )
59+ break
60+ return line
61+
62+ class CustomArgumentParser (argparse .ArgumentParser ):
63+ def error (self , message ):
64+ print (f'\n { Format .red } Error:{ Format .end } { message } \n ' )
65+ sys .exit (2 )
66+
67+ class CLIHandler :
68+
69+ def _enable_startup ():
70+ shortcut_path = os .path .join (OS_STARTUP_FOLDER , SHORTCUT_FILENAME )
71+ try :
72+ with winshell .shortcut (shortcut_path ) as shortcut :
73+ shortcut .path = AUTOSTART_FILE
74+ shortcut .working_directory = WORKING_DIRECTORY
75+ shortcut .description = "Shortcut to yasb.exe"
76+ logging .info (f"Created shortcut at { shortcut_path } " )
77+ print (f"Created shortcut at { shortcut_path } " )
78+ except Exception as e :
79+ logging .error (f"Failed to create startup shortcut: { e } " )
80+ print (f"Failed to create startup shortcut: { e } " )
81+
82+ def _disable_startup ():
83+ shortcut_path = os .path .join (OS_STARTUP_FOLDER , SHORTCUT_FILENAME )
84+ if os .path .exists (shortcut_path ):
85+ try :
86+ os .remove (shortcut_path )
87+ logging .info (f"Removed shortcut from { shortcut_path } " )
88+ print (f"Removed shortcut from { shortcut_path } " )
89+ except Exception as e :
90+ logging .error (f"Failed to remove startup shortcut: { e } " )
91+ print (f"Failed to remove startup shortcut: { e } " )
92+
93+
94+ def parse_arguments ():
95+ parser = CustomArgumentParser (description = "The command-line interface for YASB Reborn." , add_help = False )
96+ subparsers = parser .add_subparsers (dest = 'command' , help = 'Commands' )
97+
98+ subparsers .add_parser ('start' , help = 'Start the application' )
99+ subparsers .add_parser ('stop' , help = 'Stop the application' )
100+ subparsers .add_parser ('reload' , help = 'Reload the application' )
101+
102+ enable_autostart_parser = subparsers .add_parser ('enable-autostart' , help = 'Enable autostart on system boot' )
103+ enable_autostart_parser .add_argument ('--task' , action = 'store_true' , help = 'Enable autostart as a scheduled task' )
104+
105+ disable_autostart_parser = subparsers .add_parser ('disable-autostart' , help = 'Disable autostart on system boot' )
106+ disable_autostart_parser .add_argument ('--task' , action = 'store_true' , help = 'Disable autostart as a scheduled task' )
107+
108+ subparsers .add_parser ('help' , help = 'Show help message' )
109+ subparsers .add_parser ('log' , help = 'Tail yasb process logs (cancel with Ctrl-C)' )
110+ parser .add_argument ('-v' , '--version' , action = 'version' , version = f'YASB Reborn v{ BUILD_VERSION } ' , help = "Show program's version number and exit." )
111+ parser .add_argument ('-h' , '--help' , action = 'store_true' , help = 'Show help message' )
112+ args = parser .parse_args ()
113+
114+ if args .command == 'start' :
115+ print ("Start YASB in background..." )
116+ subprocess .Popen (["yasb.exe" ])
117+ sys .exit (0 )
118+
119+ elif args .command == 'stop' :
120+ print ("Stop YASB..." )
121+ subprocess .run (["taskkill" , "/f" , "/im" , "yasb.exe" ], creationflags = subprocess .CREATE_NO_WINDOW )
122+ sys .exit (0 )
123+
124+ elif args .command == 'reload' :
125+ if is_process_running ("yasb.exe" ):
126+ print ("Reload YASB..." )
127+ subprocess .run (["taskkill" , "/f" , "/im" , "yasb.exe" ], creationflags = subprocess .CREATE_NO_WINDOW )
128+ subprocess .Popen (["yasb.exe" ])
129+ else :
130+ print ("YASB is not running. Reload aborted." )
131+ sys .exit (0 )
132+
133+ elif args .command == 'enable-autostart' :
134+ if args .task :
135+ if not CLITaskHandler .is_admin ():
136+ print ("Please run this command as an administrator." )
137+ else :
138+ CLITaskHandler .create_task ()
139+ else :
140+ CLIHandler ._enable_startup ()
141+ sys .exit (0 )
142+
143+ elif args .command == 'disable-autostart' :
144+ if args .task :
145+ if not CLITaskHandler .is_admin ():
146+ print ("Please run this command as an administrator." )
147+ else :
148+ CLITaskHandler .delete_task ()
149+ else :
150+ CLIHandler ._disable_startup ()
151+ sys .exit (0 )
152+
153+ elif args .command == 'log' :
154+ log_file = os .path .join (os .path .expanduser ("~" ), ".config" , "yasb" , "yasb.log" )
155+ try :
156+ with open (log_file , 'r' ) as f :
157+ f .seek (0 , os .SEEK_END )
158+ while True :
159+ line = f .readline ()
160+ if not line :
161+ time .sleep (0.1 )
162+ continue
163+ formatted_line = format_log_line (line )
164+ print (formatted_line , end = '' )
165+ except KeyboardInterrupt :
166+ pass
167+ sys .exit (0 )
168+
169+ elif args .command == 'help' or args .help :
170+ print ("The command-line interface for YASB Reborn." )
171+ print ('\n ' + Format .underline + 'Usage' + Format .end + ': yasbc <COMMAND>' )
172+ print ('\n ' + Format .underline + 'Commands' + Format .end + ':' )
173+ print (" start Start the application" )
174+ print (" stop Stop the application" )
175+ print (" reload Reload the application" )
176+ print (" enable-autostart Enable autostart on system boot" )
177+ print (" disable-autostart Disable autostart on system boot" )
178+ print (" log Tail yasb process logs (cancel with Ctrl-C)" )
179+ print (" help Print this message" )
180+ print ('\n ' + Format .underline + 'Options' + Format .end + ':' )
181+ print (" --version Print version" )
182+ print (" --help Print this message" )
183+ sys .exit (0 )
184+
185+ else :
186+ logging .info ("Unknown command. Use --help for available options." )
187+ sys .exit (1 )
188+
189+
190+
191+ class CLITaskHandler :
192+
193+ def is_admin ():
194+ try :
195+ return ctypes .windll .shell32 .IsUserAnAdmin ()
196+ except :
197+ return False
198+
199+ def get_current_user_sid ():
200+ username = getpass .getuser ()
201+ domain = os .environ ['USERDOMAIN' ]
202+ user , domain , type = win32security .LookupAccountName (None , username )
203+ sid = win32security .ConvertSidToStringSid (user )
204+ return sid
205+
206+ def create_task ():
207+ scheduler = win32com .client .Dispatch ('Schedule.Service' )
208+ scheduler .Connect ()
209+ root_folder = scheduler .GetFolder ('\\ ' )
210+ task_def = scheduler .NewTask (0 )
211+ task_def .RegistrationInfo .Description = 'A highly configurable Windows status bar.'
212+ task_def .RegistrationInfo .Author = "AmN"
213+ task_def .Settings .Compatibility = 6
214+ trigger = task_def .Triggers .Create (9 )
215+ trigger .Enabled = True
216+ trigger .StartBoundary = datetime .datetime .now ().isoformat ()
217+ principal = task_def .Principal
218+ principal .UserId = CLITaskHandler .get_current_user_sid ()
219+ principal .LogonType = 3
220+ principal .RunLevel = 0
221+ settings = task_def .Settings
222+ settings .Enabled = True
223+ settings .StartWhenAvailable = True
224+ settings .AllowHardTerminate = True
225+ settings .ExecutionTimeLimit = 'PT0S'
226+ settings .Priority = 7
227+ settings .MultipleInstances = 0
228+ settings .DisallowStartIfOnBatteries = False
229+ settings .StopIfGoingOnBatteries = False
230+ settings .Hidden = False
231+ settings .RunOnlyIfIdle = False
232+ settings .DisallowStartOnRemoteAppSession = False
233+ settings .UseUnifiedSchedulingEngine = True
234+ settings .WakeToRun = False
235+ idle_settings = settings .IdleSettings
236+ idle_settings .StopOnIdleEnd = True
237+ idle_settings .RestartOnIdle = False
238+ action = task_def .Actions .Create (0 )
239+ action .Path = EXE_PATH
240+ action .WorkingDirectory = INSTALLATION_PATH
241+ try :
242+ root_folder .RegisterTaskDefinition (
243+ 'YASB Reborn' ,
244+ task_def ,
245+ 6 ,
246+ None ,
247+ None ,
248+ 3 ,
249+ None
250+ )
251+ print (f"Task YASB Reborn created successfully." )
252+ except Exception as e :
253+ print (f"Failed to create task YASB Reborn. Error: { e } " )
254+
255+ def delete_task ():
256+ scheduler = win32com .client .Dispatch ('Schedule.Service' )
257+ scheduler .Connect ()
258+ root_folder = scheduler .GetFolder ('\\ ' )
259+ try :
260+ root_folder .DeleteTask ('YASB Reborn' , 0 )
261+ print (f"Task YASB Reborn deleted successfully." )
262+ except Exception :
263+ print (f"Failed to delete task YASB or task does not exist." )
264+
265+
266+ if __name__ == "__main__" :
267+ CLIHandler .parse_arguments ()
268+ sys .exit (0 )
0 commit comments