@@ -42,55 +42,83 @@ def __init__(self, args):
4242 self .db = set ()
4343
4444 # create an indicator applet
45- self .ind = AppIndicator .Indicator .new ("Hacker Tray" , "hacker-tray" , AppIndicator .IndicatorCategory .APPLICATION_STATUS )
45+ self .ind = AppIndicator .Indicator .new ("Hacker Tray" , "hacker-tray" , AppIndicator .IndicatorCategory .APPLICATION_STATUS )
4646 self .ind .set_status (AppIndicator .IndicatorStatus .ACTIVE )
47- self .ind .set_icon (self .get_icon_filename ("hacker-tray.png" ))
47+ self .ind .set_icon_theme_path (self ._icon_theme_path ())
48+ icon_name = "hacker-tray-light" if self ._is_light_theme () else "hacker-tray"
49+ self .ind .set_icon (icon_name )
4850
4951 # create a menu
5052 self .menu = Gtk .Menu ()
5153
52- # The default state is false, and it toggles when you click on it
5354 self .commentState = args .comments
5455 self .reverse = args .reverse
56+ self .chrome_data_directory = args .chrome
5557
56- # create items for the menu - refresh, quit and a separator
58+ # Resolve firefox: None = not requested, "auto" = detect, else = specific path
59+ self .firefox_explicit = args .firefox is not None and args .firefox != "auto"
60+ if args .firefox == "auto" :
61+ self .firefox_data_directory = Firefox .default_firefox_profile_path ()
62+ else :
63+ self .firefox_data_directory = args .firefox
64+
65+ # create items for the menu - separator, settings, about, refresh, quit
5766 menuSeparator = Gtk .SeparatorMenuItem ()
5867 menuSeparator .show ()
5968 self .add (menuSeparator )
6069
61- btnComments = Gtk .CheckMenuItem .new_with_label ("Show Comments" )
70+ # Settings submenu
71+ settingsItem = Gtk .MenuItem .new_with_label ("Settings" )
72+ settingsMenu = Gtk .Menu ()
73+ settingsItem .set_submenu (settingsMenu )
74+
75+ btnComments = Gtk .CheckMenuItem .new_with_label ("Open Comments" )
76+ btnComments .set_active (args .comments )
6277 btnComments .connect ("toggled" , self .toggleComments )
63- self . add (btnComments )
78+ settingsMenu . append (btnComments )
6479 btnComments .show ()
65- btnComments .set_active (args .comments )
6680
67- btnAbout = Gtk .MenuItem ("About" )
81+ btnReverse = Gtk .CheckMenuItem .new_with_label ("Reverse Ordering" )
82+ btnReverse .set_active (args .reverse )
83+ btnReverse .connect ("toggled" , self .toggleReverse )
84+ settingsMenu .append (btnReverse )
85+ btnReverse .show ()
86+
87+ # Only show Firefox toggle if --firefox was unset or "auto"
88+ if not self .firefox_explicit :
89+ btnFirefox = Gtk .CheckMenuItem .new_with_label ("Detect Firefox read items" )
90+ btnFirefox .set_active (self .firefox_data_directory is not None )
91+ btnFirefox .connect ("toggled" , self .toggleFirefox )
92+ settingsMenu .append (btnFirefox )
93+ btnFirefox .show ()
94+
95+ self .add (settingsItem )
96+ settingsItem .show ()
97+
98+ btnAbout = Gtk .MenuItem .new_with_label ("About" )
6899 btnAbout .show ()
69100 btnAbout .connect ("activate" , self .showAbout )
70101 self .add (btnAbout )
71102
72- btnRefresh = Gtk .MenuItem ("Refresh" )
103+ btnRefresh = Gtk .MenuItem . new_with_label ("Refresh" )
73104 btnRefresh .show ()
74- # the last parameter is for not running the timer
75- btnRefresh .connect ("activate" , self .refresh , True , args .chrome )
105+ btnRefresh .connect ("activate" , self .refresh , True )
76106 self .add (btnRefresh )
77107
78108 if Version .new_available ():
79- btnUpdate = Gtk .MenuItem ("New Update Available" )
109+ btnUpdate = Gtk .MenuItem . new_with_label ("New Update Available" )
80110 btnUpdate .show ()
81111 btnUpdate .connect ('activate' , self .showUpdate )
82112 self .add (btnUpdate )
83113
84- btnQuit = Gtk .MenuItem ("Quit" )
114+ btnQuit = Gtk .MenuItem . new_with_label ("Quit" )
85115 btnQuit .show ()
86116 btnQuit .connect ("activate" , self .quit )
87117 self .add (btnQuit )
88118 self .menu .show ()
89119 self .ind .set_menu (self .menu )
90120
91- if args .firefox == "auto" :
92- args .firefox = Firefox .default_firefox_profile_path ()
93- self .refresh (chrome_data_directory = args .chrome , firefox_data_directory = args .firefox )
121+ self .refresh ()
94122
95123 def add (self , item ):
96124 if self .reverse :
@@ -102,6 +130,20 @@ def toggleComments(self, widget):
102130 """Whether comments page is opened or not"""
103131 self .commentState = widget .get_active ()
104132
133+ def toggleReverse (self , widget ):
134+ self .reverse = widget .get_active ()
135+
136+ def toggleFirefox (self , widget ):
137+ if widget .get_active ():
138+ try :
139+ self .firefox_data_directory = Firefox .default_firefox_profile_path ()
140+ except RuntimeError :
141+ print ("[+] Could not find Firefox profile" )
142+ widget .set_active (False )
143+ return
144+ else :
145+ self .firefox_data_directory = None
146+
105147 def showUpdate (self , widget ):
106148 """Handle the update button"""
107149 webbrowser .open (HackerNewsApp .UPDATE_URL )
@@ -115,10 +157,9 @@ def showAbout(self, widget):
115157 # ToDo: Handle keyboard interrupt properly
116158 def quit (self , widget , data = None ):
117159 """ Handler for the quit button"""
118- l = list (self .db )
160+ l = list (self .db )[ - 200 :]
119161 home = expanduser ("~" )
120162
121- # truncate the file
122163 with open (home + '/.hackertray.json' , 'w+' ) as file :
123164 file .write (json .dumps (l ))
124165
@@ -165,28 +206,28 @@ def addItem(self, item):
165206 i .url = item ['url' ]
166207 tooltip = "{url}\n Posted by {user} {timeago}" .format (url = item ['url' ], user = item ['user' ], timeago = item ['time_ago' ])
167208 i .set_tooltip_text (tooltip )
168- i .signal_id = i .connect ('activate' , self .open )
169209 i .hn_id = item ['id' ]
170210 i .item_id = item ['id' ]
211+ i .set_active (visited )
212+ i .signal_id = i .connect ('activate' , self .open )
171213 if self .reverse :
172214 self .menu .append (i )
173215 else :
174216 self .menu .prepend (i )
175217 i .show ()
176- i .set_active (visited )
177218
178- def refresh (self , widget = None , no_timer = False , chrome_data_directory = None , firefox_data_directory = None ):
219+ def refresh (self , widget = None , no_timer = False ):
179220 """Refreshes the menu """
180221 try :
181222 # Create an array of 20 false to denote matches in History
182223 searchResults = [False ]* 20
183224 data = list (reversed (HackerNews .getHomePage ()[0 :20 ]))
184225 urls = [item ['url' ] for item in data ]
185- if ( chrome_data_directory ) :
186- searchResults = self .mergeBoolArray (searchResults , Chrome .search (urls , chrome_data_directory ))
226+ if self . chrome_data_directory :
227+ searchResults = self .mergeBoolArray (searchResults , Chrome .search (urls , self . chrome_data_directory ))
187228
188- if ( firefox_data_directory ) :
189- searchResults = self .mergeBoolArray (searchResults , Firefox .search (urls , firefox_data_directory ))
229+ if self . firefox_data_directory :
230+ searchResults = self .mergeBoolArray (searchResults , Firefox .search (urls , self . firefox_data_directory ))
190231
191232 # Remove all the current stories
192233 for i in self .menu .get_children ():
@@ -206,18 +247,35 @@ def refresh(self, widget=None, no_timer=False, chrome_data_directory=None, firef
206247 finally :
207248 # Call every 10 minutes
208249 if not no_timer :
209- GLib .timeout_add (10 * 30 * 1000 , self .refresh , widget , no_timer , chrome_data_directory )
250+ GLib .timeout_add (10 * 30 * 1000 , self .refresh )
210251
211252 # Merges two boolean arrays, using OR operation against each pair
212253 def mergeBoolArray (self , original , patch ):
213254 for index , var in enumerate (original ):
214255 original [index ] = original [index ] or patch [index ]
215256 return original
216257
217- def get_icon_filename (self , icon_name ):
218- ref = importlib .resources .files ('hackertray.data' ) / 'hacker-tray.png'
219- with importlib .resources .as_file (ref ) as path :
220- return str (path )
258+ @staticmethod
259+ def _icon_theme_path ():
260+ """Return the icon data dir as a host-accessible path.
261+
262+ AppIndicator sends this path over D-Bus to the tray host, which runs
263+ outside the Flatpak sandbox. Inside a Flatpak, /app/ paths are not
264+ accessible from the host, so we translate via /.flatpak-info."""
265+ data_dir = str (importlib .resources .files ('hackertray.data' ))
266+ if os .path .exists ("/.flatpak-info" ):
267+ import configparser
268+ info = configparser .ConfigParser ()
269+ info .read ("/.flatpak-info" )
270+ app_path = info .get ("Instance" , "app-path" )
271+ data_dir = app_path + data_dir .removeprefix ("/app" )
272+ return data_dir
273+
274+ @staticmethod
275+ def _is_light_theme ():
276+ settings = Gtk .Settings .get_default ()
277+ if settings and settings .get_property ("gtk-application-prefer-dark-theme" ):
278+ return False
221279
222280
223281def main ():
0 commit comments