11import sys
22import logging
33import signal
4- import webbrowser
54import os
65import subprocess
76from collections import defaultdict
87from typing import Any , DefaultDict , List , Optional
8+ import webbrowser
99
1010from PyQt5 import QtCore
11- from PyQt5 .QtWidgets import QApplication , QSystemTrayIcon , QMessageBox , QMenu , QWidget , QPushButton
11+ from PyQt5 .QtWidgets import (
12+ QApplication ,
13+ QSystemTrayIcon ,
14+ QMessageBox ,
15+ QMenu ,
16+ QWidget ,
17+ QPushButton ,
18+ )
1219from PyQt5 .QtGui import QIcon
1320
1421import aw_core
1825logger = logging .getLogger (__name__ )
1926
2027
28+ def get_env ():
29+ """
30+ Necessary for xdg-open to work properly when PyInstaller overrides LD_LIBRARY_PATH
31+
32+ https://github.com/ActivityWatch/activitywatch/issues/208#issuecomment-417346407
33+ """
34+ env = dict (os .environ ) # make a copy of the environment
35+ lp_key = "LD_LIBRARY_PATH" # for GNU/Linux and *BSD.
36+ lp_orig = env .get (lp_key + "_ORIG" )
37+ if lp_orig is not None :
38+ env [lp_key ] = lp_orig # restore the original, unmodified value
39+ else :
40+ # This happens when LD_LIBRARY_PATH was not set.
41+ # Remove the env var as a last resort:
42+ env .pop (lp_key , None )
43+ return env
44+
45+
46+ def open_url (url ):
47+ if sys .platform == "linux" :
48+ env = get_env ()
49+ subprocess .Popen (["xdg-open" , url ], env = env )
50+ else :
51+ webbrowser .open (url )
52+
53+
2154def open_webui (root_url : str ) -> None :
2255 print ("Opening dashboard" )
23- webbrowser . open (root_url )
56+ open_url (root_url )
2457
2558
2659def open_apibrowser (root_url : str ) -> None :
2760 print ("Opening api browser" )
28- webbrowser . open (root_url + "/api" )
61+ open_url (root_url + "/api" )
2962
3063
31- def open_dir (d : str )-> None :
64+ def open_dir (d : str ) -> None :
3265 """From: http://stackoverflow.com/a/1795849/965332"""
33- if sys .platform == ' win32' :
66+ if sys .platform == " win32" :
3467 os .startfile (d )
35- elif sys .platform == ' darwin' :
36- subprocess .Popen ([' open' , d ])
68+ elif sys .platform == " darwin" :
69+ subprocess .Popen ([" open" , d ])
3770 else :
38- subprocess .Popen ([' xdg-open' , d ])
71+ subprocess .Popen ([" xdg-open" , d ])
3972
4073
4174class TrayIcon (QSystemTrayIcon ):
42- def __init__ (self , manager : Manager , icon : QIcon , parent : Optional [QWidget ]= None , testing : bool = False ) -> None :
75+ def __init__ (
76+ self ,
77+ manager : Manager ,
78+ icon : QIcon ,
79+ parent : Optional [QWidget ] = None ,
80+ testing : bool = False ,
81+ ) -> None :
4382 QSystemTrayIcon .__init__ (self , icon , parent )
44- self ._parent = parent # QSystemTrayIcon also tries to save parent info but it screws up the type info
83+ self ._parent = parent # QSystemTrayIcon also tries to save parent info but it screws up the type info
4584 self .setToolTip ("ActivityWatch" + (" (testing)" if testing else "" ))
4685
4786 self .manager = manager
@@ -68,10 +107,14 @@ def _build_rootmenu(self) -> None:
68107 self ._build_modulemenu (modulesMenu )
69108
70109 menu .addSeparator ()
71- menu .addAction ("Open log folder" , lambda : open_dir (aw_core .dirs .get_log_dir (None )))
110+ menu .addAction (
111+ "Open log folder" , lambda : open_dir (aw_core .dirs .get_log_dir (None ))
112+ )
72113 menu .addSeparator ()
73114
74- exitIcon = QIcon .fromTheme ("application-exit" , QIcon ("media/application_exit.png" ))
115+ exitIcon = QIcon .fromTheme (
116+ "application-exit" , QIcon ("media/application_exit.png" )
117+ )
75118 # This check is an attempted solution to: https://github.com/ActivityWatch/activitywatch/issues/62
76119 # Seems to be in agreement with: https://github.com/OtterBrowser/otter-browser/issues/1313
77120 # "it seems that the bug is also triggered when creating a QIcon with an invalid path"
@@ -105,6 +148,7 @@ def rebuild_modules_menu() -> None:
105148
106149 # TODO: Do it in a better way, singleShot isn't pretty...
107150 QtCore .QTimer .singleShot (2000 , rebuild_modules_menu )
151+
108152 QtCore .QTimer .singleShot (2000 , rebuild_modules_menu )
109153
110154 def check_module_status () -> None :
@@ -116,6 +160,7 @@ def check_module_status() -> None:
116160
117161 # TODO: Do it in a better way, singleShot isn't pretty...
118162 QtCore .QTimer .singleShot (2000 , rebuild_modules_menu )
163+
119164 QtCore .QTimer .singleShot (2000 , check_module_status )
120165
121166 def _build_modulemenu (self , moduleMenu : QMenu ) -> None :
@@ -130,11 +175,15 @@ def add_module_menuitem(module: Module) -> None:
130175 ac .setChecked (module .is_alive ())
131176
132177 # Merged from branch dev/autodetect-modules, still kind of in progress with making this actually work
133- modules_by_location : DefaultDict [str , List [Module ]] = defaultdict (lambda : list ())
178+ modules_by_location : DefaultDict [str , List [Module ]] = defaultdict (
179+ lambda : list ()
180+ )
134181 for module in sorted (self .manager .modules .values (), key = lambda m : m .name ):
135182 modules_by_location [module .location ].append (module )
136183
137- for location , modules in sorted (modules_by_location .items (), key = lambda kv : kv [0 ]):
184+ for location , modules in sorted (
185+ modules_by_location .items (), key = lambda kv : kv [0 ]
186+ ):
138187 header = moduleMenu .addAction (location )
139188 header .setEnabled (False )
140189
@@ -159,27 +208,28 @@ def run(manager: Manager, testing: bool = False) -> Any:
159208
160209 app = QApplication (sys .argv )
161210
162- # Ensure cleanup happens on SIGTERM and SIGINT (kill and ctrl+c etc)
163- # The 2 un-used variables are necessary
164- signal . signal ( signal . SIGINT , lambda _ , __ : exit ( manager ))
165- signal .signal (signal .SIGTERM , lambda _ , __ : exit (manager ))
211+ # Without this, Ctrl+C will have no effect
212+ signal . signal ( signal . SIGINT , lambda * args : exit ( manager ))
213+ # Ensure cleanup happens on SIGTERM
214+ signal .signal (signal .SIGTERM , lambda * args : exit (manager ))
166215
167216 timer = QtCore .QTimer ()
168217 timer .start (100 ) # You may change this if you wish.
169218 timer .timeout .connect (lambda : None ) # Let the interpreter run each 500 ms.
170219
171220 if not QSystemTrayIcon .isSystemTrayAvailable ():
172- QMessageBox .critical (None , "Systray" , "I couldn't detect any system tray on this system. Either get one or run the ActivityWatch modules from the console." )
221+ QMessageBox .critical (
222+ None ,
223+ "Systray" ,
224+ "I couldn't detect any system tray on this system. Either get one or run the ActivityWatch modules from the console." ,
225+ )
173226 sys .exit (1 )
174227
175228 widget = QWidget ()
176229 if sys .platform == "darwin" :
177- from Foundation import NSUserDefaults
178- style = NSUserDefaults .standardUserDefaults ().stringForKey_ ('AppleInterfaceStyle' )
179- if style == "Dark" :
180- icon = QIcon (":/white-monochrome-logo.png" )
181- else :
182- icon = QIcon (":/black-monochrome-logo.png" )
230+ icon = QIcon (":/black-monochrome-logo.png" )
231+ # Allow macOS to use filters for changing the icon's color
232+ icon .setIsMask (True )
183233 else :
184234 icon = QIcon (":/logo.png" )
185235
0 commit comments