def __schedule_resume(self, time_minutes): """ Schedule a local timer to enable Safe Eyes after the given timeout. """ self.idle_condition.acquire() self.idle_condition.wait(time_minutes * 60) # Convert to seconds self.idle_condition.release() with self.lock: if not self.active: Utility.execute_main_thread(self.item_enable.activate)
def show_alert(message, image_name): """ Receive the break signal from core and pass it to the break screen. """ logging.info("Show the break screen") notification.close() plugins_data = plugins.pre_break(context) break_screen.show_message(message, Utility.get_resource_path(image_name), plugins_data) if config['strict_break'] and is_active: Utility.execute_main_thread(tray_icon.unlock_menu)
def on_postponed(): """ Listen to break screen Postpone action and send the signal to core. """ logging.info("User postponed the break") if config['enable_screen_lock'] and context[ 'break_type'] == 'long' and context.get( 'count_down', 0) >= config['time_to_screen_lock']: # Lock the screen before closing the break screen Utility.lock_desktop(system_lock_command) core.postpone_break()
def __set_next_break_info(self): formatted_time = Utility.format_time(self.dateTime) message = self.language['messages']['next_break_at'].format( formatted_time) # Update the tray icon label if self.config.get('show_time_in_tray', False): self.indicator.set_label(formatted_time, '') else: self.indicator.set_label('', '') # Update the menu item label Utility.execute_main_thread(self.item_info.set_label, message)
def __confirmation_dialog_response(widget, response_id): if response_id == Gtk.ResponseType.OK: Utility.reset_config() self.config = Config() # Remove breaks from the container self.box_short_breaks.foreach(lambda element: self.box_short_breaks.remove(element)) self.box_long_breaks.foreach(lambda element: self.box_long_breaks.remove(element)) # Remove plugins from the container self.box_plugins.foreach(lambda element: self.box_plugins.remove(element)) # Initialize again self.__initialize(self.config) widget.destroy()
def close_alert(audible_alert_on): """ Receive the stop break signal from core and pass it to the break screen. """ logging.info("Close the break screen") if config['enable_screen_lock'] and context['break_type'] == 'long': # Lock the screen before closing the break screen Utility.lock_desktop(system_lock_command) break_screen.close() if audible_alert_on: Utility.play_notification() plugins.post_break(context)
def __init__(self, system_locale, config): self.active = False self.break_screen = None self.safe_eyes_core = None self.config = config self.context = {} self.plugins_manager = None self.settings_dialog_active = False self.rpc_server = None self._status = '' # Initialize the Safe Eyes Context self.context['version'] = SAFE_EYES_VERSION self.context['desktop'] = Utility.desktop_environment() self.context['is_wayland'] = Utility.is_wayland() self.context['locale'] = system_locale self.context['api'] = {} self.context['api'][ 'show_settings'] = lambda: Utility.execute_main_thread( self.show_settings) self.context['api'][ 'show_about'] = lambda: Utility.execute_main_thread(self.show_about ) self.context['api']['enable_safeeyes'] = lambda next_break_time=- \ 1: Utility.execute_main_thread(self.enable_safeeyes, next_break_time) self.context['api'][ 'disable_safeeyes'] = lambda status: Utility.execute_main_thread( self.disable_safeeyes, status) self.context['api']['status'] = self.status self.context['api']['quit'] = lambda: Utility.execute_main_thread(self. quit) if self.config.get('persist_state'): self.context['session'] = Utility.open_session() else: self.context['session'] = {'plugin': {}} self.break_screen = BreakScreen(self.context, self.on_skipped, self.on_postponed, Utility.STYLE_SHEET_PATH) self.break_screen.initialize(self.config) self.plugins_manager = PluginManager(self.context, self.config) self.safe_eyes_core = SafeEyesCore(self.context) self.safe_eyes_core.on_pre_break += self.plugins_manager.pre_break self.safe_eyes_core.on_start_break += self.on_start_break self.safe_eyes_core.start_break += self.start_break self.safe_eyes_core.on_count_down += self.countdown self.safe_eyes_core.on_stop_break += self.stop_break self.safe_eyes_core.on_update_next_break += self.update_next_break self.safe_eyes_core.initialize(self.config) self.context['api'][ 'take_break'] = lambda: Utility.execute_main_thread( self.safe_eyes_core.take_break) self.context['api']['has_breaks'] = self.safe_eyes_core.has_breaks self.context['api']['postpone'] = self.safe_eyes_core.postpone self.plugins_manager.init(self.context, self.config) atexit.register(self.persist_session) self.rpc_server = RPCServer(self.config.get('rpc_port'), self.context) self.rpc_server.start()
def __show_notification(self): # Show the notification self.show_notification() logging.info("Wait for {} seconds which is the time to prepare".format(self.pre_break_warning_time)) # Wait for the pre break warning period self.notification_condition.acquire() self.notification_condition.wait(self.pre_break_warning_time) self.notification_condition.release() self.is_before_break = True Utility.execute_main_thread(self.__check_active_window)
def start(self): """ Start Safe Eyes is it is not running already. """ with self.lock: if not self.active: logging.info("Scheduling next break") self.active = True self.running = True Utility.start_thread(self.__scheduler_job) if self.context['idle_pause_enabled']: Utility.start_thread(self.__start_idle_monitor)
def start(self, next_break_time=-1): """ Start Safe Eyes is it is not running already. """ if not self.has_breaks(): return with self.lock: if not self.running: logging.info("Start Safe Eyes core") self.running = True self.scheduled_next_break_timestamp = int(next_break_time) Utility.start_thread(self.__scheduler_job)
def __start_break(self): """ Start the break screen. """ # User can disable SafeEyes during notification if self.__is_running(): message = "" image = None seconds = 0 audible_alert = None if self.__is_long_break(): logging.info("Count is {}; get a long beak message".format(self.break_count)) self.long_break_message_index = (self.long_break_message_index + 1) % len(self.long_break_exercises) message = self.long_break_exercises[self.long_break_message_index][0] seconds = self.long_break_exercises[self.long_break_message_index][1] audible_alert = self.long_break_exercises[self.long_break_message_index][2] image = self.long_break_exercises[self.long_break_message_index][3] else: logging.info("Count is {}; get a short beak message".format(self.break_count)) self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises) message = self.short_break_exercises[self.short_break_message_index][0] seconds = self.short_break_exercises[self.short_break_message_index][1] audible_alert = self.short_break_exercises[self.short_break_message_index][2] image = self.short_break_exercises[self.short_break_message_index][3] self.context['break_length'] = seconds self.context['audible_alert'] = audible_alert total_break_time = seconds # Show the break screen self.start_break(message, image) # Use self.active instead of self.__is_running to avoid idle pause interrupting the break while seconds and self.active and not self.context['skipped'] and not self.context['postponed']: count_down = total_break_time - seconds self.context['count_down'] = count_down self.on_countdown(count_down, seconds) time.sleep(1) # Sleep for 1 second seconds -= 1 # Loop terminated because of timeout (not skipped) -> Close the break alert if not self.context['skipped'] and not self.context['postponed']: logging.info("Break is terminated automatically") self.end_break(audible_alert) # Reset the skipped flag self.context['skipped'] = False # Resume if self.__is_running(): # Schedule the break again Utility.start_thread(self.__scheduler_job)
def __set_next_break_info(self): """ A private method to be called within this class to update the next break information using self.dateTime. """ formatted_time = Utility.format_time(self.date_time) message = _('Next break at %s') % (formatted_time) # Update the menu item label Utility.execute_main_thread(self.item_info.set_label, message) # Update the tray icon label if self.plugin_config.get('show_time_in_tray', False): self.indicator.set_label(formatted_time, '') else: self.indicator.set_label('', '')
def main(): """ Start the Safe Eyes. """ system_locale = gettext.translation('safeeyes', localedir=Utility.LOCALE_PATH, languages=[Utility.system_locale(), 'en_US'], fallback=True) system_locale.install() # locale.bindtextdomain is required for Glade files # gettext.bindtextdomain(gettext.textdomain(), Utility.LOCALE_PATH) locale.bindtextdomain('safeeyes', Utility.LOCALE_PATH) parser = argparse.ArgumentParser(prog='safeeyes', description=_('description')) group = parser.add_mutually_exclusive_group() group.add_argument('-a', '--about', help=_('show the about dialog'), action='store_true') group.add_argument('-d', '--disable', help=_('disable the currently running safeeyes instance'), action='store_true') group.add_argument('-e', '--enable', help=_('enable the currently running safeeyes instance'), action='store_true') group.add_argument('-q', '--quit', help=_('quit the running safeeyes instance and exit'), action='store_true') group.add_argument('-s', '--settings', help=_('show the settings dialog'), action='store_true') group.add_argument('-t', '--take-break', help=_('Take a break now').lower(), action='store_true') parser.add_argument('--debug', help=_('start safeeyes in debug mode'), action='store_true') parser.add_argument('--version', action='version', version='%(prog)s ' + SAFE_EYES_VERSION) args = parser.parse_args() # Initialize the logging Utility.intialize_logging(args.debug) config = Config() if __running(): logging.info("Safe Eyes is already running") rpc_client = RPCClient(config.get('rpc_port')) if args.about: rpc_client.show_about() elif args.disable: rpc_client.disable_safeeyes() elif args.enable: rpc_client.enable_safeeyes() elif args.settings: rpc_client.show_settings() elif args.take_break: rpc_client.take_break() elif args.quit: rpc_client.quit() else: # Default behavior is opening settings rpc_client.show_settings() sys.exit(0) elif not args.quit: logging.info("Starting Safe Eyes") safeeyes = SafeEyes(system_locale, config) safeeyes.start() Timer(1.0, lambda: __evaluate_arguments(args, safeeyes)).start() Gtk.main()
def __init__(self, init=True): # Read the config files self.__user_config = Utility.load_json(Utility.CONFIG_FILE_PATH) self.__system_config = Utility.load_json( Utility.SYSTEM_CONFIG_FILE_PATH) self.__force_upgrade = ['long_breaks', 'short_breaks'] if init: if self.__user_config is None: Utility.initialize_safeeyes() self.__user_config = self.__system_config self.save() else: system_config_version = self.__system_config['meta'][ 'config_version'] meta_obj = self.__user_config.get('meta', None) if meta_obj is None: # Corrupted user config self.__user_config = self.__system_config else: user_config_version = str( meta_obj.get('config_version', '0.0.0')) if LooseVersion(user_config_version) != LooseVersion( system_config_version): # Update the user config self.__merge_dictionary(self.__user_config, self.__system_config) self.__user_config = self.__system_config # Update the style sheet Utility.replace_style_sheet() Utility.merge_plugins(self.__user_config) self.save()
def __evaluate_arguments(args, safe_eyes): """ Evaluate the arguments and execute the operations. """ if args.about: Utility.execute_main_thread(safe_eyes.show_about) elif args.disable: Utility.execute_main_thread(safe_eyes.disable_safeeyes) elif args.enable: Utility.execute_main_thread(safe_eyes.enable_safeeyes) elif args.settings: Utility.execute_main_thread(safe_eyes.show_settings) elif args.take_break: Utility.execute_main_thread(safe_eyes.take_break)
def __scheduler_job(self): """ Scheduler task to execute during every interval """ if not self.running: return self.context['state'] = State.WAITING # Convert to seconds time_to_wait = self.break_queue.get_break().time * 60 current_time = datetime.datetime.now() current_timestamp = current_time.timestamp() if self.context['postponed']: # Previous break was postponed logging.info('Prepare for postponed break') time_to_wait = self.postpone_duration self.context['postponed'] = False elif self.paused_time > -1 and self.break_queue.is_long_break(): # Safe Eyes was paused earlier and next break is long paused_duration = int(current_timestamp - self.paused_time) self.paused_time = -1 if paused_duration > self.break_queue.get_break().duration: logging.info( 'Skip next long break due to the pause longer than break duration' ) # Skip the next long break self.break_queue.next() if current_timestamp < self.scheduled_next_break_timestamp: time_to_wait = round(self.scheduled_next_break_timestamp - current_timestamp) self.scheduled_next_break_timestamp = -1 self.scheduled_next_break_time = current_time + datetime.timedelta( seconds=time_to_wait) Utility.execute_main_thread(self.__fire_on_update_next_break, self.scheduled_next_break_time) # Wait for the pre break warning period logging.info("Waiting for %d minutes until next break", (time_to_wait / 60)) self.__wait_for(time_to_wait) logging.info("Pre-break waiting is over") if not self.running: return Utility.execute_main_thread(self.__fire_pre_break)
def main(): """ Start the Safe Eyes. """ # Initialize the logging Utility.intialize_logging() logging.info("Starting Safe Eyes") # Import the dependencies Utility.import_dependencies() if not running(): global break_screen global core global config global notification global tray_icon global language global context global plugins global system_lock_command config = Utility.read_config() context = {} language = Utility.load_language(config['language']) # Get the lock command only one time if config['lock_screen_command']: system_lock_command = config['lock_screen_command'] else: system_lock_command = Utility.lock_screen_command() # Initialize the Safe Eyes Context context['version'] = SAFE_EYES_VERSION context['desktop'] = Utility.desktop_environment() tray_icon = TrayIcon(config, language, show_settings, show_about, enable_safeeyes, disable_safeeyes, on_quit) break_screen = BreakScreen(context, on_skipped, on_postponed, break_screen_glade, Utility.style_sheet_path) break_screen.initialize(config, language) notification = Notification(context, language) plugins = Plugins(config) core = SafeEyesCore(context, show_notification, show_alert, close_alert, break_screen.show_count_down, tray_icon.next_break_time) core.initialize(config, language) plugins.start(context) # Call the start method of all plugins core.start() handle_system_suspend() Gtk.main() else: logging.info('Another instance of safeeyes is already running') sys.exit(0)
def __scheduler_job(self): """ Scheduler task to execute during every interval """ if not self.__is_running(): return time_to_wait = self.break_interval # In minutes if self.context['postponed']: # Reduce the break count by 1 to show the same break again if self.break_count == 0: self.break_count = -1 else: self.break_count = ((self.break_count - 1) % self.no_of_short_breaks_per_long_break) if self.__is_long_break(): self.long_break_message_index = (self.long_break_message_index - 1) % len(self.long_break_exercises) else: self.short_break_message_index = (self.short_break_message_index - 1) % len(self.short_break_exercises) # Wait until the postpone time time_to_wait = self.postpone_duration self.context['postponed'] = False next_break_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait) self.update_next_break_info(next_break_time) self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break) if self.__is_long_break(): self.context['break_type'] = 'long' else: self.context['break_type'] = 'short' # Wait for the pre break warning period logging.info("Pre-break waiting for {} minutes".format(time_to_wait)) self.notification_condition.acquire() self.notification_condition.wait(time_to_wait * 60) # Convert to seconds self.notification_condition.release() logging.info("Pre-break waiting is over") if not self.__is_running(): return logging.info("Ready to show the break") self.is_before_break = False Utility.execute_main_thread(self.__check_active_window)
def __start_break(self): """ Start the break screen. """ self.context['state'] = State.BREAK break_obj = self.breaks[self.next_break_index] countdown = break_obj.time total_break_time = countdown while countdown and self.running and not self.context[ 'skipped'] and not self.context['postponed']: seconds = total_break_time - countdown self.on_count_down.fire(countdown, seconds) time.sleep(1) # Sleep for 1 second countdown -= 1 Utility.execute_main_thread(self.__fire_stop_break)
def __initialize(self, config): # Don't show infobar for changes made internally self.infobar_long_break_shown = True for short_break in config.get('short_breaks'): self.__create_break_item(short_break, True) for long_break in config.get('long_breaks'): self.__create_break_item(long_break, False) for plugin_config in Utility.load_plugins_config(config): self.box_plugins.pack_start( self.__create_plugin_item(plugin_config), False, False, 0) self.spin_short_break_duration.set_value( config.get('short_break_duration')) self.spin_long_break_duration.set_value( config.get('long_break_duration')) self.spin_short_break_interval.set_value( config.get('short_break_interval')) self.spin_long_break_interval.set_value( config.get('long_break_interval')) self.spin_time_to_prepare.set_value( config.get('pre_break_warning_time')) self.spin_postpone_duration.set_value(config.get('postpone_duration')) self.spin_disable_keyboard_shortcut.set_value( config.get('shortcut_disable_time')) self.switch_strict_break.set_active(config.get('strict_break')) self.switch_postpone.set_active( config.get('allow_postpone') and not config.get('strict_break')) self.switch_persist.set_active(config.get('persist_state')) self.infobar_long_break_shown = False
def __init__(self, config): self.config = config self.property_controls = [] builder = Utility.create_gtk_builder(SETTINGS_DIALOG_PLUGIN_GLADE) builder.connect_signals(self) self.window = builder.get_object('dialog_settings_plugin') box_settings = builder.get_object('box_settings') self.window.set_title(_('Plugin Settings')) for setting in config.get('settings'): if setting['type'].upper() == 'INT': box_settings.pack_start( self.__load_int_item(setting['label'], setting['id'], setting['safeeyes_config'], setting.get('min', 0), setting.get('max', 120)), False, False, 0) elif setting['type'].upper() == 'TEXT': box_settings.pack_start( self.__load_text_item(setting['label'], setting['id'], setting['safeeyes_config']), False, False, 0) elif setting['type'].upper() == 'BOOL': box_settings.pack_start( self.__load_bool_item(setting['label'], setting['id'], setting['safeeyes_config']), False, False, 0)
def play_sound(resource_name): """Play the audio resource. Arguments: resource_name {string} -- name of the wav file resource """ logging.info('Playing audible alert %s', resource_name) try: # Open the sound file path = Utility.get_resource_path(resource_name) if path is None: return Utility.execute_command('aplay', ['-q', path]) except BaseException: logging.error('Failed to play audible alert %s', resource_name)
def set_labels(self, language): """ Update the text of menu items based on the selected language. """ self.language = language for entry in self.sub_menu_items: entry[0].set_label(self.language['ui_controls'][entry[1]].format( entry[2])) self.sub_menu_item_until_restart.set_label( self.language['ui_controls']['until_restart']) self.item_enable.set_label(self.language['ui_controls']['enable']) self.item_disable.set_label(self.language['ui_controls']['disable']) if self.active: if self.dateTime: self.__set_next_break_info() else: if self.wakeup_time: self.item_info.set_label( self.language['messages']['disabled_until_x'].format( Utility.format_time(self.wakeup_time))) else: self.item_info.set_label( self.language['messages']['disabled_until_restart']) self.item_settings.set_label(self.language['ui_controls']['settings']) self.item_about.set_label(self.language['ui_controls']['about']) self.item_quit.set_label(self.language['ui_controls']['quit'])
def __create_plugin_item(self, plugin_config): """ Create an entry for plugin to be listed in the plugin tab. """ builder = Utility.create_gtk_builder(SETTINGS_PLUGIN_ITEM_GLADE) lbl_plugin_name = builder.get_object('lbl_plugin_name') lbl_plugin_description = builder.get_object('lbl_plugin_description') switch_enable = builder.get_object('switch_enable') btn_properties = builder.get_object('btn_properties') lbl_plugin_name.set_label(_(plugin_config['meta']['name'])) switch_enable.set_active(plugin_config['enabled']) if plugin_config['error']: lbl_plugin_description.set_label(_(plugin_config['meta']['description'])) lbl_plugin_name.set_sensitive(False) lbl_plugin_description.set_sensitive(False) switch_enable.set_sensitive(False) else: lbl_plugin_description.set_label(_(plugin_config['meta']['description'])) self.plugin_switches[plugin_config['id']] = switch_enable if plugin_config.get('break_override_allowed', False): self.plugin_map[plugin_config['id']] = plugin_config['meta']['name'] if plugin_config['icon']: builder.get_object('img_plugin_icon').set_from_file(plugin_config['icon']) if plugin_config['settings']: btn_properties.set_sensitive(True) btn_properties.connect('clicked', lambda button: self.__show_plugins_properties_dialog(plugin_config)) else: btn_properties.set_sensitive(False) box = builder.get_object('box') box.set_visible(True) return box
def get_icon(self): if self.system_icon: return self.__icon else: image = Utility.load_and_scale_image(self.__icon, 16, 16) image.show() return image
def __create_break_item(self, break_config, is_short): """ Create an entry for break to be listed in the break tab. """ parent_box = self.box_long_breaks if is_short: parent_box = self.box_short_breaks builder = Utility.create_gtk_builder(SETTINGS_BREAK_ITEM_GLADE) box = builder.get_object('box') lbl_name = builder.get_object('lbl_name') lbl_name.set_label(_(break_config['name'])) btn_properties = builder.get_object('btn_properties') btn_properties.connect( 'clicked', lambda button: self.__show_break_properties_dialog( break_config, is_short, self.config, lambda cfg: lbl_name.set_label(_(cfg['name'])), lambda is_short, break_config: self.__create_break_item(break_config, is_short), lambda: parent_box.remove(box) ) ) btn_delete = builder.get_object('btn_delete') btn_delete.connect( 'clicked', lambda button: self.__delete_break( break_config, is_short, lambda: parent_box.remove(box), ) ) box.set_visible(True) parent_box.pack_start(box, False, False, 0) return box
def show_settings(): logging.info("Show Settings dialog") able_to_lock_screen = False if system_lock_command: able_to_lock_screen = True settings_dialog = SettingsDialog(config, language, Utility.read_lang_files(), able_to_lock_screen, save_settings, settings_dialog_glade) settings_dialog.show()
def save_settings(config): global language logging.info("Saving settings to safeeyes.json") # Stop the Safe Eyes core if is_active: core.stop() # Write the configuration to file with open(Utility.config_file_path, 'w') as config_file: json.dump(config, config_file, indent=4, sort_keys=True) # Reload the language translation language = Utility.load_language(config['language']) tray_icon.initialize(config) tray_icon.set_labels(language) logging.info("Initialize SafeEyesCore with modified settings") # Restart the core and intialize the components core.initialize(config, language) break_screen.initialize(config, language) if is_active: # 1 sec delay is required to give enough time for core to be stopped Timer(1.0, core.start).start()
def initialize(self, config, language): logging.info("Initialize the core") self.short_break_exercises = [] #language['exercises']['short_break_exercises'] self.long_break_exercises = [] #language['exercises']['long_break_exercises'] self.no_of_short_breaks_per_long_break = config['no_of_short_breaks_per_long_break'] self.pre_break_warning_time = config['pre_break_warning_time'] self.long_break_duration = config['long_break_duration'] self.short_break_duration = config['short_break_duration'] self.break_interval = config['break_interval'] self.idle_time = config['idle_time'] self.postpone_duration = config['postpone_duration'] self.skip_break_window_classes = [x.lower() for x in config['active_window_class']['skip_break']] self.take_break_window_classes = [x.lower() for x in config['active_window_class']['take_break']] self.custom_exercises = config['custom_exercises'] # Enable idle time pause only if xprintidle is available self.context['idle_pause_enabled'] = Utility.command_exist('xprintidle') exercises = language['exercises'] for short_break_config in config['short_breaks']: exercise_name = short_break_config['name'] name = None if exercise_name in self.custom_exercises: name = self.custom_exercises[exercise_name] else: name = exercises[exercise_name] break_time = short_break_config.get('time', self.short_break_duration) audible_alert = short_break_config.get('audible_alert', config['audible_alert']) image = short_break_config.get('image') # Validate time value if not isinstance(break_time, int) or break_time <= 0: logging.error('Invalid time in short break: ' + str(short_break_config)) continue self.short_break_exercises.append([name, break_time, audible_alert, image]) for long_break_config in config['long_breaks']: exercise_name = long_break_config['name'] name = None if exercise_name in self.custom_exercises: name = self.custom_exercises[exercise_name] else: name = exercises[exercise_name] break_time = long_break_config.get('time', self.long_break_duration) audible_alert = long_break_config.get('audible_alert', config['audible_alert']) image = long_break_config.get('image') # Validate time value if not isinstance(break_time, int) or break_time <= 0: logging.error('Invalid time in long break: ' + str(long_break_config)) continue self.long_break_exercises.append([name, break_time, audible_alert, image])
def __fire_start_break(self): # Show the break screen if not self.on_start_break.fire(self.breaks[self.next_break_index]): # Plugins want to ignore this break self.__start_next_break() return if self.context['postponed']: # Plugins want to postpone this break self.context['postponed'] = False # Update the next break time self.scheduled_next_break_time = self.scheduled_next_break_time + datetime.timedelta( seconds=self.postpone_duration) self.__fire_on_update_next_break(self.scheduled_next_break_time) # Wait in user thread Utility.start_thread(self.__postpone_break) else: self.start_break.fire(self.breaks[self.next_break_index]) Utility.start_thread(self.__start_break)