diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..8b079ee4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 + +[*.{py}] +trim_trailing_whitespace = true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..eb7d934b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,12 @@ +name: Lint + +on: [push, pull_request] + +jobs: + lint-ruff: + runs-on: ubuntu-latest + name: ruff + steps: + - name: Check out source repository + uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..4724771d --- /dev/null +++ b/ruff.toml @@ -0,0 +1,17 @@ +target-version = "py310" + +# gettext +builtins = ["_"] + +[lint] +select = ["E", "W", "F", "D2", "D3", "D4"] +ignore = [ + # PyGObject needs require_version before imports + "E402", + + # these are disabled for now, but should probably be cleaned up at some point + "D205", "D401", "D404", +] + +[lint.pydocstyle] +convention = "numpy" diff --git a/safeeyes/__main__.py b/safeeyes/__main__.py index 71234544..6b269a83 100755 --- a/safeeyes/__main__.py +++ b/safeeyes/__main__.py @@ -16,9 +16,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +"""Safe Eyes is a utility to remind you to take break frequently to protect +your eyes from eye strain. """ -Safe Eyes is a utility to remind you to take break frequently to protect your eyes from eye strain. -""" + import argparse import gettext import locale @@ -33,13 +34,11 @@ from safeeyes.safeeyes import SAFE_EYES_VERSION from safeeyes.rpc import RPCClient -gettext.install('safeeyes', utility.LOCALE_PATH) +gettext.install("safeeyes", utility.LOCALE_PATH) def __running(): - """ - Check if SafeEyes is already running. - """ + """Check if SafeEyes is already running.""" process_count = 0 for proc in psutil.process_iter(): if not proc.cmdline: @@ -52,7 +51,9 @@ def __running(): else: # In older versions cmdline was a list object cmd_line = proc.cmdline - if ('python3' in cmd_line[0] or 'python' in cmd_line[0]) and ('safeeyes' in cmd_line[1] or 'safeeyes' in cmd_line): + if ("python3" in cmd_line[0] or "python" in cmd_line[0]) and ( + "safeeyes" in cmd_line[1] or "safeeyes" in cmd_line + ): process_count += 1 if process_count > 1: return True @@ -64,29 +65,63 @@ def __running(): def main(): - """ - Start the Safe Eyes. - """ - system_locale = gettext.translation('safeeyes', localedir=utility.LOCALE_PATH, languages=[utility.system_locale(), 'en_US'], fallback=True) + """Start the Safe Eyes.""" + system_locale = gettext.translation( + "safeeyes", + localedir=utility.LOCALE_PATH, + languages=[utility.system_locale(), "en_US"], + fallback=True, + ) system_locale.install() try: # locale.bindtextdomain is required for Glade files - locale.bindtextdomain('safeeyes', utility.LOCALE_PATH) + locale.bindtextdomain("safeeyes", utility.LOCALE_PATH) except AttributeError: - logging.warning('installed python\'s gettext module does not support locale.bindtextdomain. locale.bindtextdomain is required for Glade files') - + logging.warning( + "installed python's gettext module does not support locale.bindtextdomain." + " locale.bindtextdomain is required for Glade files" + ) - parser = argparse.ArgumentParser(prog='safeeyes') + parser = argparse.ArgumentParser(prog="safeeyes") 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('--status', help=_('print the status of running safeeyes instance and exit'), action='store_true') - parser.add_argument('--version', action='version', version='%(prog)s ' + SAFE_EYES_VERSION) + 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( + "--status", + help=_("print the status of running safeeyes instance and exit"), + action="store_true", + ) + parser.add_argument( + "--version", action="version", version="%(prog)s " + SAFE_EYES_VERSION + ) args = parser.parse_args() # Initialize the logging @@ -99,10 +134,15 @@ def main(): logging.info("Safe Eyes is already running") if not config.get("use_rpc_server", True): # RPC sever is disabled - print(_('Safe Eyes is running without an RPC server. Turn it on to use command-line arguments.')) + print( + _( + "Safe Eyes is running without an RPC server. Turn it on to use" + " command-line arguments." + ) + ) sys.exit(0) return - rpc_client = RPCClient(config.get('rpc_port')) + rpc_client = RPCClient(config.get("rpc_port")) if args.about: rpc_client.show_about() elif args.disable: @@ -123,7 +163,7 @@ def main(): sys.exit(0) else: if args.status: - print(_('Safe Eyes is not running')) + print(_("Safe Eyes is not running")) sys.exit(0) elif not args.quit: logging.info("Starting Safe Eyes") @@ -131,6 +171,6 @@ def main(): safe_eyes.start() -if __name__ == '__main__': - signal.signal(signal.SIGINT, signal.SIG_DFL) # Handle Ctrl + C +if __name__ == "__main__": + signal.signal(signal.SIGINT, signal.SIG_DFL) # Handle Ctrl + C main() diff --git a/safeeyes/core.py b/safeeyes/core.py index e0fcbee5..44a68efb 100644 --- a/safeeyes/core.py +++ b/safeeyes/core.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -SafeEyesCore provides the core functionalities of Safe Eyes. -""" +"""SafeEyesCore provides the core functionalities of Safe Eyes.""" import datetime import logging @@ -26,7 +24,6 @@ import time from safeeyes import utility -from safeeyes.model import Break from safeeyes.model import BreakType from safeeyes.model import BreakQueue from safeeyes.model import EventHook @@ -34,14 +31,10 @@ class SafeEyesCore: - """ - Core of Safe Eyes runs the scheduler and notifies the breaks. - """ + """Core of Safe Eyes runs the scheduler and notifies the breaks.""" def __init__(self, context): - """ - Create an instance of SafeEyesCore and initialize the variables. - """ + """Create an instance of SafeEyesCore and initialize the variables.""" self.break_queue = None self.postpone_duration = 0 self.default_postpone_duration = 0 @@ -65,26 +58,24 @@ def __init__(self, context): self.waiting_condition = threading.Condition() self.lock = threading.Lock() self.context = context - self.context['skipped'] = False - self.context['postponed'] = False - self.context['skip_button_disabled'] = False - self.context['postpone_button_disabled'] = False - self.context['state'] = State.WAITING + self.context["skipped"] = False + self.context["postponed"] = False + self.context["skip_button_disabled"] = False + self.context["postpone_button_disabled"] = False + self.context["state"] = State.WAITING def initialize(self, config): - """ - Initialize the internal properties from configuration - """ + """Initialize the internal properties from configuration.""" logging.info("Initialize the core") - self.pre_break_warning_time = config.get('pre_break_warning_time') + self.pre_break_warning_time = config.get("pre_break_warning_time") self.break_queue = BreakQueue(config, self.context) - self.default_postpone_duration = config.get('postpone_duration') * 60 # Convert to seconds + self.default_postpone_duration = ( + config.get("postpone_duration") * 60 + ) # Convert to seconds self.postpone_duration = self.default_postpone_duration def start(self, next_break_time=-1, reset_breaks=False): - """ - Start Safe Eyes is it is not running already. - """ + """Start Safe Eyes is it is not running already.""" if self.break_queue.is_empty(): return with self.lock: @@ -99,9 +90,7 @@ def start(self, next_break_time=-1, reset_breaks=False): utility.start_thread(self.__scheduler_job) def stop(self, is_resting=False): - """ - Stop Safe Eyes if it is running. - """ + """Stop Safe Eyes if it is running.""" with self.lock: if not self.running: return @@ -111,59 +100,54 @@ def stop(self, is_resting=False): # Stop the break thread self.waiting_condition.acquire() self.running = False - if self.context['state'] != State.QUIT: - self.context['state'] = State.RESTING if (is_resting) else State.STOPPED + if self.context["state"] != State.QUIT: + self.context["state"] = State.RESTING if (is_resting) else State.STOPPED self.waiting_condition.notify_all() self.waiting_condition.release() def skip(self): - """ - User skipped the break using Skip button - """ - self.context['skipped'] = True + """User skipped the break using Skip button.""" + self.context["skipped"] = True def postpone(self, duration=-1): - """ - User postponed the break using Postpone button - """ + """User postponed the break using Postpone button.""" if duration > 0: self.postpone_duration = duration else: self.postpone_duration = self.default_postpone_duration logging.debug("Postpone the break for %d seconds", self.postpone_duration) - self.context['postponed'] = True + self.context["postponed"] = True - def get_break_time(self, break_type = None): - """ - Returns the next break time - """ + def get_break_time(self, break_type=None): + """Returns the next break time.""" break_obj = self.break_queue.get_break(break_type) if not break_obj: return False - time = self.scheduled_next_break_time + datetime.timedelta(minutes=break_obj.time - self.break_queue.get_break().time) + time = self.scheduled_next_break_time + datetime.timedelta( + minutes=break_obj.time - self.break_queue.get_break().time + ) return time - def take_break(self, break_type = None): - """ - Calling this method stops the scheduler and show the next break screen + def take_break(self, break_type=None): + """Calling this method stops the scheduler and show the next break + screen. """ if self.break_queue.is_empty(): return - if not self.context['state'] == State.WAITING: + if not self.context["state"] == State.WAITING: return utility.start_thread(self.__take_break, break_type=break_type) - def has_breaks(self, break_type = None): - """ - Check whether Safe Eyes has breaks or not. Use the break_type to check for either short or long break. + def has_breaks(self, break_type=None): + """Check whether Safe Eyes has breaks or not. + + Use the break_type to check for either short or long break. """ return not self.break_queue.is_empty(break_type) - def __take_break(self, break_type = None): - """ - Show the next break screen - """ - logging.info('Take a break due to external request') + def __take_break(self, break_type=None): + """Show the next break screen.""" + logging.info("Take a break due to external request") with self.lock: if not self.running: @@ -184,40 +168,51 @@ def __take_break(self, break_type = None): utility.execute_main_thread(self.__fire_start_break) def __scheduler_job(self): - """ - Scheduler task to execute during every interval - """ + """Scheduler task to execute during every interval.""" if not self.running: return current_time = datetime.datetime.now() current_timestamp = current_time.timestamp() - if self.context['state'] == State.RESTING and self.paused_time > -1: + if self.context["state"] == State.RESTING and self.paused_time > -1: # Safe Eyes was resting paused_duration = int(current_timestamp - self.paused_time) self.paused_time = -1 - if paused_duration > self.break_queue.get_break(BreakType.LONG_BREAK).duration: - logging.info('Skip next long break due to the pause %ds longer than break duration', paused_duration) + if ( + paused_duration + > self.break_queue.get_break(BreakType.LONG_BREAK).duration + ): + logging.info( + "Skip next long break due to the pause %ds longer than break" + " duration", + paused_duration, + ) # Skip the next long break self.break_queue.reset() - if self.context['postponed']: + if self.context["postponed"]: # Previous break was postponed - logging.info('Prepare for postponed break') + logging.info("Prepare for postponed break") time_to_wait = self.postpone_duration - self.context['postponed'] = False + self.context["postponed"] = False elif current_timestamp < self.scheduled_next_break_timestamp: # Non-standard break was set. - time_to_wait = round(self.scheduled_next_break_timestamp - current_timestamp) + time_to_wait = round( + self.scheduled_next_break_timestamp - current_timestamp + ) self.scheduled_next_break_timestamp = -1 else: # Use next break, convert to seconds time_to_wait = self.break_queue.get_break().time * 60 - self.scheduled_next_break_time = current_time + datetime.timedelta(seconds=time_to_wait) - self.context['state'] = State.WAITING - utility.execute_main_thread(self.__fire_on_update_next_break, self.scheduled_next_break_time) + self.scheduled_next_break_time = current_time + datetime.timedelta( + seconds=time_to_wait + ) + self.context["state"] = State.WAITING + 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)) @@ -230,16 +225,12 @@ def __scheduler_job(self): utility.execute_main_thread(self.__fire_pre_break) def __fire_on_update_next_break(self, next_break_time): - """ - Pass the next break information to the registered listeners. - """ + """Pass the next break information to the registered listeners.""" self.on_update_next_break.fire(self.break_queue.get_break(), next_break_time) def __fire_pre_break(self): - """ - Show the notification and start the break after the notification. - """ - self.context['state'] = State.PRE_BREAK + """Show the notification and start the break after the notification.""" + self.context["state"] = State.PRE_BREAK if not self.on_pre_break.fire(self.break_queue.get_break()): # Plugins wanted to ignore this break self.__start_next_break() @@ -247,7 +238,9 @@ def __fire_pre_break(self): utility.start_thread(self.__wait_until_prepare) def __wait_until_prepare(self): - logging.info("Wait for %d seconds before the break", self.pre_break_warning_time) + logging.info( + "Wait for %d seconds before the break", self.pre_break_warning_time + ) # Wait for the pre break warning period self.__wait_for(self.pre_break_warning_time) if not self.running: @@ -265,11 +258,14 @@ def __fire_start_break(self): # Plugins want to ignore this break self.__start_next_break() return - if self.context['postponed']: + if self.context["postponed"]: # Plugins want to postpone this break - self.context['postponed'] = False + 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.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) @@ -278,43 +274,44 @@ def __fire_start_break(self): utility.start_thread(self.__start_break) def __start_break(self): - """ - Start the break screen. - """ - self.context['state'] = State.BREAK + """Start the break screen.""" + self.context["state"] = State.BREAK break_obj = self.break_queue.get_break() countdown = break_obj.duration total_break_time = countdown - while countdown and self.running and not self.context['skipped'] and not self.context['postponed']: + 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 + time.sleep(1) # Sleep for 1 second countdown -= 1 utility.execute_main_thread(self.__fire_stop_break) def __fire_stop_break(self): # Loop terminated because of timeout (not skipped) -> Close the break alert - if not self.context['skipped'] and not self.context['postponed']: + if not self.context["skipped"] and not self.context["postponed"]: logging.info("Break is terminated automatically") self.on_stop_break.fire() # Reset the skipped flag - self.context['skipped'] = False - self.context['skip_button_disabled'] = False - self.context['postpone_button_disabled'] = False + self.context["skipped"] = False + self.context["skip_button_disabled"] = False + self.context["postpone_button_disabled"] = False self.__start_next_break() def __wait_for(self, duration): - """ - Wait until someone wake up or the timeout happens. - """ + """Wait until someone wake up or the timeout happens.""" self.waiting_condition.acquire() self.waiting_condition.wait(duration) self.waiting_condition.release() def __start_next_break(self): - if not self.context['postponed']: + if not self.context["postponed"]: self.break_queue.next() if self.running: diff --git a/safeeyes/model.py b/safeeyes/model.py index 2f8f9b88..30871236 100644 --- a/safeeyes/model.py +++ b/safeeyes/model.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -This module contains the entity classes used by Safe Eyes and its plugins. +"""This module contains the entity classes used by Safe Eyes and its +plugins. """ import logging @@ -31,9 +31,7 @@ class Break: - """ - An entity class which represents a break. - """ + """An entity class which represents a break.""" def __init__(self, break_type, name, time, duration, image, plugins): self.type = break_type @@ -45,27 +43,23 @@ def __init__(self, break_type, name, time, duration, image, plugins): self.next = None def __str__(self): - return 'Break: {{name: "{}", type: {}, duration: {}}}\n'.format(self.name, self.type, self.duration) + return 'Break: {{name: "{}", type: {}, duration: {}}}\n'.format( + self.name, self.type, self.duration + ) def __repr__(self): return str(self) def is_long_break(self): - """ - Check whether this break is a long break. - """ + """Check whether this break is a long break.""" return self.type == BreakType.LONG_BREAK def is_short_break(self): - """ - Check whether this break is a short break. - """ + """Check whether this break is a short break.""" return self.type == BreakType.SHORT_BREAK def plugin_enabled(self, plugin_id, is_plugin_enabled): - """ - Check whether this break supports the given plugin. - """ + """Check whether this break supports the given plugin.""" if self.plugins: return plugin_id in self.plugins else: @@ -73,9 +67,8 @@ def plugin_enabled(self, plugin_id, is_plugin_enabled): class BreakType(Enum): - """ - Type of Safe Eyes breaks. - """ + """Type of Safe Eyes breaks.""" + SHORT_BREAK = 1 LONG_BREAK = 2 @@ -86,19 +79,18 @@ def __init__(self, config, context): self.__current_break = None self.__current_long = 0 self.__current_short = 0 - self.__short_break_time = config.get('short_break_interval') - self.__long_break_time = config.get('long_break_interval') - self.__is_random_order = config.get('random_order') + self.__short_break_time = config.get("short_break_interval") + self.__long_break_time = config.get("long_break_interval") + self.__is_random_order = config.get("random_order") self.__config = config self.__build_longs() self.__build_shorts() - # Interface guarantees that short_interval >= 1 # And that long_interval is a multiple of short_interval - short_interval = config.get('short_break_interval') - long_interval = config.get('long_break_interval') + short_interval = config.get("short_break_interval") + long_interval = config.get("long_break_interval") self.__cycle_len = int(long_interval / short_interval) # To count every long break as a cycle in .next() if there are no short breaks if self.__short_queue is None: @@ -106,7 +98,7 @@ def __init__(self, config, context): # Restore the last break from session if not self.is_empty(): - last_break = context['session'].get('break') + last_break = context["session"].get("break") if last_break is not None: current_break = self.get_break() if last_break != current_break.name: @@ -114,7 +106,7 @@ def __init__(self, config, context): while brk != current_break and brk.name != last_break: brk = self.next() - def get_break(self, break_type = None): + def get_break(self, break_type=None): if self.__current_break is None: self.__current_break = self.next() @@ -123,20 +115,23 @@ def get_break(self, break_type = None): if break_type == BreakType.LONG_BREAK: if self.__long_queue is None: - return None; + return None return self.__long_queue[self.__current_long] if self.__short_queue is None: - return None; + return None return self.__short_queue[self.__current_short] def is_long_break(self): - return self.__current_break is not None and self.__current_break.type == BreakType.LONG_BREAK + return ( + self.__current_break is not None + and self.__current_break.type == BreakType.LONG_BREAK + ) - def next(self, break_type = None): + def next(self, break_type=None): break_obj = None shorts = self.__short_queue - longs = self.__long_queue + longs = self.__long_queue # Reset break that has just ended if self.is_long_break(): @@ -158,13 +153,16 @@ def next(self, break_type = None): break_obj = self.__next_long() elif longs is None: break_obj = self.__next_short() - elif break_type == BreakType.LONG_BREAK or longs[self.__current_long].time <= shorts[self.__current_short].time: + elif ( + break_type == BreakType.LONG_BREAK + or longs[self.__current_long].time <= shorts[self.__current_short].time + ): break_obj = self.__next_long() else: break_obj = self.__next_short() self.__current_break = break_obj - self.context['session']['break'] = self.__current_break.name + self.context["session"]["break"] = self.__current_break.name return break_obj @@ -175,9 +173,10 @@ def reset(self): for break_object in self.__long_queue: break_object.time = self.__long_break_time - def is_empty(self, break_type = None): - """ - Check if the given break type is empty or not. If the break_type is None, check for both short and long breaks. + def is_empty(self, break_type=None): + """Check if the given break type is empty or not. + + If the break_type is None, check for both short and long breaks. """ if break_type == BreakType.SHORT_BREAK: return self.__short_queue is None @@ -187,10 +186,9 @@ def is_empty(self, break_type = None): return self.__short_queue is None and self.__long_queue is None def __next_short(self): - longs = self.__long_queue shorts = self.__short_queue break_obj = shorts[self.__current_short] - self.context['break_type'] = 'short' + self.context["break_type"] = "short" # Update the index to next self.__current_short = (self.__current_short + 1) % len(shorts) @@ -198,9 +196,9 @@ def __next_short(self): return break_obj def __next_long(self): - longs = self.__long_queue + longs = self.__long_queue break_obj = longs[self.__current_long] - self.context['break_type'] = 'long' + self.context["break_type"] = "long" # Update the index to next self.__current_long = (self.__current_long + 1) % len(longs) @@ -208,9 +206,7 @@ def __next_long(self): return break_obj def __build_queue(self, break_type, break_configs, break_time, break_duration): - """ - Build a queue of breaks. - """ + """Build a queue of breaks.""" size = len(break_configs) if 0 == size: @@ -224,55 +220,53 @@ def __build_queue(self, break_type, break_configs, break_time, break_duration): queue = [None] * size for i, break_config in enumerate(breaks_order): - name = _(break_config['name']) - duration = break_config.get('duration', break_duration) - image = break_config.get('image') - plugins = break_config.get('plugins', None) - interval = break_config.get('interval', break_time) + name = _(break_config["name"]) + duration = break_config.get("duration", break_duration) + image = break_config.get("image") + plugins = break_config.get("plugins", None) + interval = break_config.get("interval", break_time) # Validate time value if not isinstance(duration, int) or duration <= 0: - logging.error('Invalid break duration in: ' + - str(break_config)) + logging.error("Invalid break duration in: " + str(break_config)) continue - break_obj = Break(break_type, name, interval, - duration, image, plugins) + break_obj = Break(break_type, name, interval, duration, image, plugins) queue[i] = break_obj return queue def __build_shorts(self): - self.__short_queue = self.__build_queue(BreakType.SHORT_BREAK, - self.__config.get('short_breaks'), - self.__short_break_time, - self.__config.get('short_break_duration')) + self.__short_queue = self.__build_queue( + BreakType.SHORT_BREAK, + self.__config.get("short_breaks"), + self.__short_break_time, + self.__config.get("short_break_duration"), + ) def __build_longs(self): - self.__long_queue = self.__build_queue(BreakType.LONG_BREAK, - self.__config.get('long_breaks'), - self.__long_break_time, - self.__config.get('long_break_duration')) - + self.__long_queue = self.__build_queue( + BreakType.LONG_BREAK, + self.__config.get("long_breaks"), + self.__long_break_time, + self.__config.get("long_break_duration"), + ) class State(Enum): - """ - Possible states of Safe Eyes. - """ - START = 0, # Starting scheduler - WAITING = 1, # User is working (waiting for next break) - PRE_BREAK = 2, # Preparing for break - BREAK = 3, # Break - STOPPED = 4, # Disabled - QUIT = 5, # Quitting - RESTING = 6 # Resting (natural break) + """Possible states of Safe Eyes.""" + + START = (0,) # Starting scheduler + WAITING = (1,) # User is working (waiting for next break) + PRE_BREAK = (2,) # Preparing for break + BREAK = (3,) # Break + STOPPED = (4,) # Disabled + QUIT = (5,) # Quitting + RESTING = 6 # Resting (natural break) class EventHook: - """ - Hook to attach and detach listeners to system events. - """ + """Hook to attach and detach listeners to system events.""" def __init__(self): self.__handlers = [] @@ -286,9 +280,7 @@ def __isub__(self, handler): return self def fire(self, *args, **keywargs): - """ - Fire all listeners attached with. - """ + """Fire all listeners attached with.""" for handler in self.__handlers: if not handler(*args, **keywargs): return False @@ -296,39 +288,38 @@ def fire(self, *args, **keywargs): class Config: - """ - The configuration of Safe Eyes. - """ + """The configuration of Safe Eyes.""" 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) - # If there any breaking changes in long_breaks, short_breaks or any other keys, use the __force_upgrade list + self.__system_config = utility.load_json(utility.SYSTEM_CONFIG_FILE_PATH) + # If there any breaking changes in long_breaks, short_breaks or any other keys, + # use the __force_upgrade list self.__force_upgrade = [] # self.__force_upgrade = ['long_breaks', 'short_breaks'] if init: - # if create_startup_entry finds a broken autostart symlink, it will repair it + # if create_startup_entry finds a broken autostart symlink, it will repair + # it utility.create_startup_entry(force=False) 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) + 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')) + user_config_version = str(meta_obj.get("config_version", "0.0.0")) if parse(user_config_version) != parse(system_config_version): # Update the user config self.__merge_dictionary( - self.__user_config, self.__system_config) + self.__user_config, self.__system_config + ) self.__user_config = self.__system_config # Update the style sheet utility.replace_style_sheet() @@ -337,9 +328,7 @@ def __init__(self, init=True): self.save() def __merge_dictionary(self, old_dict, new_dict): - """ - Merge the dictionaries. - """ + """Merge the dictionaries.""" for key in new_dict: if key == "meta" or key in self.__force_upgrade: continue @@ -358,24 +347,18 @@ def clone(self): return config def save(self): - """ - Save the configuration to file. - """ + """Save the configuration to file.""" utility.write_json(utility.CONFIG_FILE_PATH, self.__user_config) def get(self, key, default_value=None): - """ - Get the value. - """ + """Get the value.""" value = self.__user_config.get(key, default_value) if value is None: value = self.__system_config.get(key, None) return value def set(self, key, value): - """ - Set the value. - """ + """Set the value.""" self.__user_config[key] = value def __eq__(self, config): @@ -386,9 +369,7 @@ def __ne__(self, config): class TrayAction: - """ - Data object wrapping name, icon and action. - """ + """Data object wrapping name, icon and action.""" def __init__(self, name, icon, action, system_icon): self.name = name @@ -421,14 +402,16 @@ def build(cls, name, icon_path, icon_id, action): else: return TrayAction(name, icon_path, action, False) + @dataclass class PluginDependency: message: str - link: str|None = None + link: str | None = None retryable: bool = False + class RequiredPluginException(Exception): - def __init__(self, plugin_id, plugin_name: str, message: str|PluginDependency): + def __init__(self, plugin_id, plugin_name: str, message: str | PluginDependency): if isinstance(message, PluginDependency): msg = message.message else: diff --git a/safeeyes/plugin_manager.py b/safeeyes/plugin_manager.py index 41ea4130..35cc4107 100644 --- a/safeeyes/plugin_manager.py +++ b/safeeyes/plugin_manager.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -PluginManager loads all enabled plugins and call their lifecycle methods. +"""PluginManager loads all enabled plugins and call their lifecycle methods. + A plugin must have the following directory structure: |- config.json @@ -28,7 +28,8 @@ - description() If a custom description has to be displayed, use this function - init(context, safeeyes_config, plugin_config) - Initialize the plugin. Will be called after loading and after every changes in configuration + Initialize the plugin. Will be called after loading and after every changes in + configuration - on_start() Executes when Safe Eyes is enabled - on_stop() @@ -64,34 +65,36 @@ HORIZONTAL_LINE_LENGTH = 64 + class PluginManager: - """ - Imports the Safe Eyes plugins and calls the methods defined in those plugins. - """ + """Imports the Safe Eyes plugins and calls the methods defined in those plugins.""" def __init__(self): - logging.info('Load all the plugins') + logging.info("Load all the plugins") self.__plugins = {} self.last_break = None - self.horizontal_line = '─' * HORIZONTAL_LINE_LENGTH + self.horizontal_line = "─" * HORIZONTAL_LINE_LENGTH def init(self, context, config): - """ - Initialize all the plugins with init(context, safe_eyes_config, plugin_config) function. + """Initialize all the plugins with init(context, safe_eyes_config, + plugin_config) function. """ # Load the plugins - for plugin in config.get('plugins'): + for plugin in config.get("plugins"): try: loaded_plugin = LoadedPlugin(plugin) self.__plugins[loaded_plugin.id] = loaded_plugin except RequiredPluginException as e: raise e except BaseException as e: - traceback_wanted = logging.getLogger().getEffectiveLevel() == logging.DEBUG + traceback_wanted = ( + logging.getLogger().getEffectiveLevel() == logging.DEBUG + ) if traceback_wanted: import traceback + traceback.print_exc() - logging.error('Error in loading the plugin %s: %s', plugin['id'], e) + logging.error("Error in loading the plugin %s: %s", plugin["id"], e) continue # Initialize the plugins for plugin in self.__plugins.values(): @@ -104,11 +107,12 @@ def needs_retry(self): def get_retryable_error(self): for plugin in self.__plugins.values(): if plugin.required_plugin and plugin.errored and plugin.enabled: - if isinstance(plugin.last_error, PluginDependency) and plugin.last_error.retryable: + if ( + isinstance(plugin.last_error, PluginDependency) + and plugin.last_error.retryable + ): return RequiredPluginException( - plugin.id, - plugin.get_name(), - plugin.last_error + plugin.id, plugin.get_name(), plugin.last_error ) return None @@ -116,46 +120,39 @@ def get_retryable_error(self): def retry_errored_plugins(self): for plugin in self.__plugins.values(): if plugin.required_plugin and plugin.errored and plugin.enabled: - if isinstance(plugin.last_error, PluginDependency) and plugin.last_error.retryable: + if ( + isinstance(plugin.last_error, PluginDependency) + and plugin.last_error.retryable + ): plugin.reload_errored() def start(self): - """ - Execute the on_start() function of plugins. - """ + """Execute the on_start() function of plugins.""" for plugin in self.__plugins.values(): plugin.call_plugin_method("on_start") return True def stop(self): - """ - Execute the on_stop() function of plugins. - """ + """Execute the on_stop() function of plugins.""" for plugin in self.__plugins.values(): plugin.call_plugin_method("on_stop") return True def exit(self): - """ - Execute the on_exit() function of plugins. - """ + """Execute the on_exit() function of plugins.""" for plugin in self.__plugins.values(): plugin.call_plugin_method("on_exit") return True def pre_break(self, break_obj): - """ - Execute the on_pre_break(break_obj) function of plugins. - """ + """Execute the on_pre_break(break_obj) function of plugins.""" for plugin in self.__plugins.values(): if plugin.call_plugin_method_break_obj("on_pre_break", 1, break_obj): return False return True def start_break(self, break_obj): - """ - Execute the start_break(break_obj) function of plugins. - """ + """Execute the start_break(break_obj) function of plugins.""" self.last_break = break_obj for plugin in self.__plugins.values(): if plugin.call_plugin_method_break_obj("on_start_break", 1, break_obj): @@ -164,56 +161,59 @@ def start_break(self, break_obj): return True def stop_break(self): - """ - Execute the stop_break() function of plugins. - """ + """Execute the stop_break() function of plugins.""" for plugin in self.__plugins.values(): plugin.call_plugin_method("on_stop_break") def countdown(self, countdown, seconds): - """ - Execute the on_countdown(countdown, seconds) function of plugins. - """ + """Execute the on_countdown(countdown, seconds) function of plugins.""" for plugin in self.__plugins.values(): plugin.call_plugin_method("on_countdown", 2, countdown, seconds) def update_next_break(self, break_obj, break_time): - """ - Execute the update_next_break(break_time) function of plugins. - """ + """Execute the update_next_break(break_time) function of plugins.""" for plugin in self.__plugins.values(): - plugin.call_plugin_method_break_obj("update_next_break", 2, break_obj, break_time) + plugin.call_plugin_method_break_obj( + "update_next_break", 2, break_obj, break_time + ) return True def get_break_screen_widgets(self, break_obj): + """Return the HTML widget generated by the plugins. + + The widget is generated by calling the get_widget_title and + get_widget_content functions of plugins. """ - Return the HTML widget generated by the plugins. - The widget is generated by calling the get_widget_title and get_widget_content functions of plugins. - """ - widget = '' + widget = "" for plugin in self.__plugins.values(): try: - title = plugin.call_plugin_method_break_obj("get_widget_title", 1, break_obj) - if title is None or not isinstance(title, str) or title == '': + title = plugin.call_plugin_method_break_obj( + "get_widget_title", 1, break_obj + ) + if title is None or not isinstance(title, str) or title == "": continue - content = plugin.call_plugin_method_break_obj("get_widget_content", 1, break_obj) - if content is None or not isinstance(content, str) or content == '': + content = plugin.call_plugin_method_break_obj( + "get_widget_content", 1, break_obj + ) + if content is None or not isinstance(content, str) or content == "": continue title = title.upper().strip() - if title == '': + if title == "": continue - widget += '{}\n{}\n{}\n\n\n'.format(title, self.horizontal_line, content) + widget += "{}\n{}\n{}\n\n\n".format( + title, self.horizontal_line, content + ) except BaseException: continue return widget.strip() def get_break_screen_tray_actions(self, break_obj): - """ - Return Tray Actions. - """ + """Return Tray Actions.""" actions = [] for plugin in self.__plugins.values(): - action = plugin.call_plugin_method_break_obj("get_tray_action", 1, break_obj) + action = plugin.call_plugin_method_break_obj( + "get_tray_action", 1, break_obj + ) if action: actions.append(action) @@ -228,7 +228,8 @@ class LoadedPlugin: required_plugin: bool = False # misc data - # FIXME: rename to plugin_config to plugin_json? plugin_config and config are easy to confuse + # FIXME: rename to plugin_config to plugin_json? plugin_config and config are easy + # to confuse config = None plugin_config = None plugin_dir = None @@ -237,56 +238,54 @@ class LoadedPlugin: id = None def __init__(self, plugin): - (plugin_config, plugin_dir) = self._load_config_json(plugin['id']) + (plugin_config, plugin_dir) = self._load_config_json(plugin["id"]) - self.id = plugin['id'] + self.id = plugin["id"] self.plugin_config = plugin_config self.plugin_dir = plugin_dir self.enabled = plugin["enabled"] - self.break_override_allowed = plugin_config.get('break_override_allowed', False) + self.break_override_allowed = plugin_config.get("break_override_allowed", False) self.required_plugin = plugin_config.get("required_plugin", False) - self.config = dict(plugin.get('settings', {})) - self.config['path'] = os.path.join(plugin_dir, plugin['id']) + self.config = dict(plugin.get("settings", {})) + self.config["path"] = os.path.join(plugin_dir, plugin["id"]) if self.enabled or self.break_override_allowed: plugin_path = os.path.join(plugin_dir, self.id) - message = utility.check_plugin_dependencies(plugin['id'], plugin_config, plugin.get('settings', {}), plugin_path) + message = utility.check_plugin_dependencies( + plugin["id"], plugin_config, plugin.get("settings", {}), plugin_path + ) if message: self.errored = True self.last_error = message - if self.required_plugin and not (isinstance(message, PluginDependency) and message.retryable): + if self.required_plugin and not ( + isinstance(message, PluginDependency) and message.retryable + ): raise RequiredPluginException( - plugin['id'], - plugin_config['meta']['name'], - message + plugin["id"], plugin_config["meta"]["name"], message ) return self._import_plugin() - def reload_config(self, plugin): if self.enabled and not plugin["enabled"]: self.enabled = False - if not self.errored and utility.has_method(self.module, 'disable'): + if not self.errored and utility.has_method(self.module, "disable"): self.module.disable() if not self.enabled and plugin["enabled"]: self.enabled = True # Update the config - self.config = dict(plugin.get('settings', {})) - self.config['path'] = os.path.join(self.plugin_dir, plugin['id']) + self.config = dict(plugin.get("settings", {})) + self.config["path"] = os.path.join(self.plugin_dir, plugin["id"]) if self.enabled or self.break_override_allowed: plugin_path = os.path.join(self.plugin_dir, self.id) message = utility.check_plugin_dependencies( - self.id, - self.plugin_config, - self.config, - plugin_path + self.id, self.plugin_config, self.config, plugin_path ) if message: @@ -300,7 +299,6 @@ def reload_config(self, plugin): # No longer errored, import the module now self._import_plugin() - def reload_errored(self): if not self.errored: return @@ -308,10 +306,7 @@ def reload_errored(self): if self.enabled or self.break_override_allowed: plugin_path = os.path.join(self.plugin_dir, self.id) message = utility.check_plugin_dependencies( - self.id, - self.plugin_config, - self.config, - plugin_path + self.id, self.plugin_config, self.config, plugin_path ) if message: @@ -325,37 +320,40 @@ def reload_errored(self): # No longer errored, import the module now self._import_plugin() - def get_name(self): - return self.plugin_config['meta']['name'] + return self.plugin_config["meta"]["name"] def _import_plugin(self): if self.errored: # do not try to import errored plugin return - self.module = importlib.import_module((self.id + '.plugin')) + self.module = importlib.import_module((self.id + ".plugin")) logging.info("Successfully loaded %s", str(self.module)) - if utility.has_method(self.module, 'enable'): + if utility.has_method(self.module, "enable"): self.module.enable() def _load_config_json(self, plugin_id): # Look for plugin.py - if os.path.isfile(os.path.join(utility.SYSTEM_PLUGINS_DIR, plugin_id, 'plugin.py')): + if os.path.isfile( + os.path.join(utility.SYSTEM_PLUGINS_DIR, plugin_id, "plugin.py") + ): plugin_dir = utility.SYSTEM_PLUGINS_DIR - elif os.path.isfile(os.path.join(utility.USER_PLUGINS_DIR, plugin_id, 'plugin.py')): + elif os.path.isfile( + os.path.join(utility.USER_PLUGINS_DIR, plugin_id, "plugin.py") + ): plugin_dir = utility.USER_PLUGINS_DIR else: - raise Exception('plugin.py not found for the plugin: %s', plugin_id) + raise Exception("plugin.py not found for the plugin: %s", plugin_id) # Look for config.json plugin_path = os.path.join(plugin_dir, plugin_id) - plugin_config_path = os.path.join(plugin_path, 'config.json') + plugin_config_path = os.path.join(plugin_path, "config.json") if not os.path.isfile(plugin_config_path): - raise Exception('config.json not found for the plugin: %s', plugin_id) + raise Exception("config.json not found for the plugin: %s", plugin_id) plugin_config = utility.load_json(plugin_config_path) if plugin_config is None: - raise Exception('config.json empty/invalid for the plugin: %s', plugin_id) + raise Exception("config.json empty/invalid for the plugin: %s", plugin_id) return (plugin_config, plugin_dir) @@ -363,10 +361,12 @@ def init_plugin(self, context, safeeyes_config): if self.errored: return if self.break_override_allowed or self.enabled: - if utility.has_method(self.module, 'init', 3): + if utility.has_method(self.module, "init", 3): self.module.init(context, safeeyes_config, self.config) - def call_plugin_method_break_obj(self, method_name: str, num_args, break_obj, *args, **kwargs): + def call_plugin_method_break_obj( + self, method_name: str, num_args, break_obj, *args, **kwargs + ): if self.errored: return None @@ -377,7 +377,9 @@ def call_plugin_method_break_obj(self, method_name: str, num_args, break_obj, *a enabled = self.enabled if enabled: - return self._call_plugin_method_internal(method_name, num_args, break_obj, *args, **kwargs) + return self._call_plugin_method_internal( + method_name, num_args, break_obj, *args, **kwargs + ) return None @@ -386,11 +388,15 @@ def call_plugin_method(self, method_name: str, num_args=0, *args, **kwargs): return None if self.enabled: - return self._call_plugin_method_internal(method_name, num_args, *args, **kwargs) + return self._call_plugin_method_internal( + method_name, num_args, *args, **kwargs + ) return None - def _call_plugin_method_internal(self, method_name: str, num_args=0, *args, **kwargs): + def _call_plugin_method_internal( + self, method_name: str, num_args=0, *args, **kwargs + ): # FIXME: cache if method exists if utility.has_method(self.module, method_name, num_args): return getattr(self.module, method_name)(*args, **kwargs) diff --git a/safeeyes/plugins/audiblealert/plugin.py b/safeeyes/plugins/audiblealert/plugin.py index 0d9d4c1b..23002e10 100644 --- a/safeeyes/plugins/audiblealert/plugin.py +++ b/safeeyes/plugins/audiblealert/plugin.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Audible Alert plugin plays a sound after each breaks to notify the user that the break has end. +"""Audible Alert plugin plays a sound after each breaks to notify the user that +the break has end. """ import logging @@ -32,48 +32,48 @@ 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) + 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]) + utility.execute_command("aplay", ["-q", path]) except BaseException: - logging.error('Failed to play audible alert %s', resource_name) + logging.error("Failed to play audible alert %s", resource_name) def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the plugin. - """ + """Initialize the plugin.""" global context global pre_break_alert global post_break_alert - logging.debug('Initialize Audible Alert plugin') + logging.debug("Initialize Audible Alert plugin") context = ctx - pre_break_alert = plugin_config['pre_break_alert'] - post_break_alert = plugin_config['post_break_alert'] + pre_break_alert = plugin_config["pre_break_alert"] + post_break_alert = plugin_config["post_break_alert"] def on_pre_break(break_obj): """Play the pre_break sound if the option is enabled. Arguments: + --------- break_obj {safeeyes.model.Break} -- the break object + """ if pre_break_alert: - play_sound('on_pre_break.wav') + play_sound("on_pre_break.wav") def on_stop_break(): - """ - After the break, play the alert sound - """ + """After the break, play the alert sound.""" # Do not play if the break is skipped or postponed - if context['skipped'] or context['postponed'] or not post_break_alert: + if context["skipped"] or context["postponed"] or not post_break_alert: return - play_sound('on_stop_break.wav') + play_sound("on_stop_break.wav") diff --git a/safeeyes/plugins/donotdisturb/plugin.py b/safeeyes/plugins/donotdisturb/plugin.py index 0fdefa5c..0aa4f9f5 100644 --- a/safeeyes/plugins/donotdisturb/plugin.py +++ b/safeeyes/plugins/donotdisturb/plugin.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Skip Fullscreen plugin skips the break if the active window is fullscreen. +"""Skip Fullscreen plugin skips the break if the active window is fullscreen. + NOTE: Do not remove the unused import 'GdkX11' because it is required in Ubuntu 14.04 """ @@ -27,7 +27,8 @@ import subprocess import gi -gi.require_version('Gdk', '3.0') + +gi.require_version("Gdk", "3.0") from gi.repository import Gdk from gi.repository import GdkX11 # noqa F401 from gi.repository import Gio @@ -41,7 +42,7 @@ def is_active_window_skipped_wayland(pre_break): - cmdlist = ['wlrctl', 'toplevel', 'find', 'state:fullscreen'] + cmdlist = ["wlrctl", "toplevel", "find", "state:fullscreen"] try: process = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) process.communicate()[0] @@ -50,34 +51,44 @@ def is_active_window_skipped_wayland(pre_break): elif process.returncode == 1: return False elif process.returncode == 127: - logging.warning('Could not find wlrctl needed to detect fullscreen under wayland') + logging.warning( + "Could not find wlrctl needed to detect fullscreen under wayland" + ) return False except subprocess.CalledProcessError: - logging.warning('Error in finding full-screen application') + logging.warning("Error in finding full-screen application") return False def is_active_window_skipped_xorg(pre_break): + """Check for full-screen applications. + + This method must be executed by the main thread. If not, it will + cause random failure. """ - Check for full-screen applications. - This method must be executed by the main thread. If not, it will cause random failure. - """ - logging.info('Searching for full-screen application') + logging.info("Searching for full-screen application") screen = Gdk.Screen.get_default() active_window = screen.get_active_window() if active_window: active_xid = str(active_window.get_xid()) - cmdlist = ['xprop', '-root', '-notype', '-id', - active_xid, 'WM_CLASS', '_NET_WM_STATE'] + cmdlist = [ + "xprop", + "-root", + "-notype", + "-id", + active_xid, + "WM_CLASS", + "_NET_WM_STATE", + ] try: - stdout = subprocess.check_output(cmdlist).decode('utf-8') + stdout = subprocess.check_output(cmdlist).decode("utf-8") except subprocess.CalledProcessError: - logging.warning('Error in finding full-screen application') + logging.warning("Error in finding full-screen application") else: if stdout: - is_fullscreen = 'FULLSCREEN' in stdout + is_fullscreen = "FULLSCREEN" in stdout # Extract the process name process_names = re.findall('"(.+?)"', stdout) if process_names: @@ -89,7 +100,10 @@ def is_active_window_skipped_xorg(pre_break): try: active_window.unfullscreen() except BaseException as e: - logging.error('Error in unfullscreen the window ' + process_name, exc_info=e) + logging.error( + "Error in unfullscreen the window " + process_name, + exc_info=e, + ) return False return is_fullscreen @@ -98,23 +112,21 @@ def is_active_window_skipped_xorg(pre_break): def is_idle_inhibited_gnome(): - """ - GNOME Shell doesn't work with wlrctl, and there is no way to enumerate + """GNOME Shell doesn't work with wlrctl, and there is no way to enumerate fullscreen windows, but GNOME does expose whether idle actions like - starting a screensaver are inhibited, which is a close approximation if - not a better metric. + starting a screensaver are inhibited, which is a close approximation if not + a better metric. """ - dbus_proxy = Gio.DBusProxy.new_for_bus_sync( bus_type=Gio.BusType.SESSION, flags=Gio.DBusProxyFlags.NONE, info=None, - name='org.gnome.SessionManager', - object_path='/org/gnome/SessionManager', - interface_name='org.gnome.SessionManager', + name="org.gnome.SessionManager", + object_path="/org/gnome/SessionManager", + interface_name="org.gnome.SessionManager", cancellable=None, ) - result = dbus_proxy.get_cached_property('InhibitedActions').unpack() + result = dbus_proxy.get_cached_property("InhibitedActions").unpack() # The result is a bitfield, documented here: # https://gitlab.gnome.org/GNOME/gnome-session/-/blob/9aa419397b7f6d42bee6e66cc5c5aad12902fba0/gnome-session/org.gnome.SessionManager.xml#L155 @@ -127,27 +139,28 @@ def _window_class_matches(window_class: str, classes: list) -> bool: def is_on_battery(): - """ - Check if the computer is running on battery. - """ + """Check if the computer is running on battery.""" on_battery = False - available_power_sources = os.listdir('/sys/class/power_supply') - logging.info('Looking for battery status in available power sources: %s' % str( - available_power_sources)) + available_power_sources = os.listdir("/sys/class/power_supply") + logging.info( + "Looking for battery status in available power sources: %s" + % str(available_power_sources) + ) for power_source in available_power_sources: - if 'BAT' in power_source: + if "BAT" in power_source: # Found battery battery_status = os.path.join( - '/sys/class/power_supply', power_source, 'status') + "/sys/class/power_supply", power_source, "status" + ) if os.path.isfile(battery_status): # Additional check to confirm that the status file exists try: - with open(battery_status, 'r') as status_file: + with open(battery_status, "r") as status_file: status = status_file.read() if status: - on_battery = 'discharging' in status.lower() + on_battery = "discharging" in status.lower() except BaseException: - logging.error('Failed to read %s' % battery_status) + logging.error("Failed to read %s" % battery_status) break return on_battery @@ -158,12 +171,16 @@ def init(ctx, safeeyes_config, plugin_config): global take_break_window_classes global unfullscreen_allowed global dnd_while_on_battery - logging.debug('Initialize Skip Fullscreen plugin') + logging.debug("Initialize Skip Fullscreen plugin") context = ctx - skip_break_window_classes = _normalize_window_classes(plugin_config['skip_break_windows']) - take_break_window_classes = _normalize_window_classes(plugin_config['take_break_windows']) - unfullscreen_allowed = plugin_config['unfullscreen'] - dnd_while_on_battery = plugin_config['while_on_battery'] + skip_break_window_classes = _normalize_window_classes( + plugin_config["skip_break_windows"] + ) + take_break_window_classes = _normalize_window_classes( + plugin_config["take_break_windows"] + ) + unfullscreen_allowed = plugin_config["unfullscreen"] + dnd_while_on_battery = plugin_config["while_on_battery"] def _normalize_window_classes(classes_as_str: str): @@ -171,11 +188,9 @@ def _normalize_window_classes(classes_as_str: str): def on_pre_break(break_obj): - """ - Lifecycle method executes before the pre-break period. - """ + """Lifecycle method executes before the pre-break period.""" if utility.IS_WAYLAND: - if utility.DESKTOP_ENVIRONMENT == 'gnome': + if utility.DESKTOP_ENVIRONMENT == "gnome": skip_break = is_idle_inhibited_gnome() else: skip_break = is_active_window_skipped_wayland(True) @@ -187,11 +202,9 @@ def on_pre_break(break_obj): def on_start_break(break_obj): - """ - Lifecycle method executes just before the break. - """ + """Lifecycle method executes just before the break.""" if utility.IS_WAYLAND: - if utility.DESKTOP_ENVIRONMENT == 'gnome': + if utility.DESKTOP_ENVIRONMENT == "gnome": skip_break = is_idle_inhibited_gnome() else: skip_break = is_active_window_skipped_wayland(True) diff --git a/safeeyes/plugins/healthstats/dependency_checker.py b/safeeyes/plugins/healthstats/dependency_checker.py index 4453819e..daa3c942 100644 --- a/safeeyes/plugins/healthstats/dependency_checker.py +++ b/safeeyes/plugins/healthstats/dependency_checker.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import datetime from safeeyes import utility + def validate(plugin_config, plugin_settings): if not utility.module_exist("croniter"): return _("Please install the Python module '%s'") % "croniter" diff --git a/safeeyes/plugins/healthstats/plugin.py b/safeeyes/plugins/healthstats/plugin.py index 126c1c86..b90da138 100644 --- a/safeeyes/plugins/healthstats/plugin.py +++ b/safeeyes/plugins/healthstats/plugin.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Show health statistics on the break screen. -""" +"""Show health statistics on the break screen.""" import croniter import datetime @@ -27,39 +25,40 @@ context = None session = None statistics_reset_cron = None -default_statistics_reset_cron = '0 0 * * *' # Every midnight +default_statistics_reset_cron = "0 0 * * *" # Every midnight next_reset_time = None start_time = None + def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the plugin. - """ + """Initialize the plugin.""" global context global session global statistics_reset_cron - logging.debug('Initialize Health Stats plugin') + logging.debug("Initialize Health Stats plugin") context = ctx - statistics_reset_cron = plugin_config.get('statistics_reset_cron', default_statistics_reset_cron) + statistics_reset_cron = plugin_config.get( + "statistics_reset_cron", default_statistics_reset_cron + ) if session is None: # Read the session defaults = { - 'breaks': 0, - 'skipped_breaks': 0, - 'screen_time': 0, - 'total_breaks': 0, - 'total_skipped_breaks': 0, - 'total_screen_time': 0, - 'total_resets': 0, + "breaks": 0, + "skipped_breaks": 0, + "screen_time": 0, + "total_breaks": 0, + "total_skipped_breaks": 0, + "total_screen_time": 0, + "total_resets": 0, } - session = context['session']['plugin'].get('healthstats', {}) | defaults - if 'no_of_breaks' in session: + session = context["session"]["plugin"].get("healthstats", {}) | defaults + if "no_of_breaks" in session: # Ignore old format session. session = defaults - context['session']['plugin']['healthstats'] = session + context["session"]["plugin"]["healthstats"] = session _get_next_reset_time() @@ -67,8 +66,8 @@ def init(ctx, safeeyes_config, plugin_config): def on_stop_break(): # Check if break was skipped. global session - if context['skipped']: - session['skipped_breaks'] += 1 + if context["skipped"]: + session["skipped_breaks"] += 1 # Screen time is starting again. on_start() @@ -76,7 +75,7 @@ def on_stop_break(): def on_start_break(break_obj): global session - session['breaks'] += 1 + session["breaks"] += 1 # Screen time has stopped. on_stop() @@ -87,15 +86,13 @@ def on_stop(): _reset_stats() if start_time: screen_time = datetime.datetime.now() - start_time - session['screen_time'] += round(screen_time.total_seconds()) + session["screen_time"] += round(screen_time.total_seconds()) start_time = None def get_widget_title(break_obj): - """ - Return the widget title. - """ - return _('Health Statistics') + """Return the widget title.""" + return _("Health Statistics") def _reset_stats(): @@ -109,33 +106,34 @@ def _reset_stats(): _get_next_reset_time() # Reset statistics - session['total_breaks'] += session['breaks'] - session['total_skipped_breaks'] += session['skipped_breaks'] - session['total_screen_time'] += session['screen_time'] - session['total_resets'] += 1 - session['breaks'] = 0 - session['skipped_breaks'] = 0 - session['screen_time'] = 0 + session["total_breaks"] += session["breaks"] + session["total_skipped_breaks"] += session["skipped_breaks"] + session["total_screen_time"] += session["screen_time"] + session["total_resets"] += 1 + session["breaks"] = 0 + session["skipped_breaks"] = 0 + session["screen_time"] = 0 def get_widget_content(break_obj): - """ - Return the statistics. - """ + """Return the statistics.""" global next_reset_time - resets = session['total_resets'] - if session['screen_time'] > 21600 or (session['breaks'] and session['skipped_breaks'] / session['breaks']) >= 0.2: + resets = session["total_resets"] + if ( + session["screen_time"] > 21600 + or (session["breaks"] and session["skipped_breaks"] / session["breaks"]) >= 0.2 + ): # Unhealthy behavior -> Red broken heart - heart = '💔️' + heart = "💔️" else: # Healthy behavior -> Green heart - heart = '💚' + heart = "💚" content = [ - heart, - f"BREAKS: {session['breaks']}", - f"SKIPPED: {session['skipped_breaks']}", - f"SCREEN TIME: {_format_interval(session['screen_time'])}", + heart, + f"BREAKS: {session['breaks']}", + f"SKIPPED: {session['skipped_breaks']}", + f"SCREEN TIME: {_format_interval(session['screen_time'])}", ] if resets: @@ -147,14 +145,14 @@ def get_widget_content(break_obj): if resets: content += f"\n\t[] = average of {resets} reset(s)" if next_reset_time is None: - content += f"\n\tSettings error in statistics reset interval: {statistics_reset_cron}" + content += ( + f"\n\tSettings error in statistics reset interval: {statistics_reset_cron}" + ) return content def on_start(): - """ - Track the start time. - """ + """Track the start time.""" global start_time _reset_stats() start_time = datetime.datetime.now() @@ -167,9 +165,10 @@ def _get_next_reset_time(): try: cron = croniter.croniter(statistics_reset_cron, datetime.datetime.now()) next_reset_time = cron.get_next(datetime.datetime) - session['next_reset_time'] = next_reset_time.strftime("%Y-%m-%d %H:%M:%S") - logging.debug("Health stats will be reset at " + session['next_reset_time']) - except: + session["next_reset_time"] = next_reset_time.strftime("%Y-%m-%d %H:%M:%S") + logging.debug("Health stats will be reset at " + session["next_reset_time"]) + except: # noqa E722 + # TODO: consider catching Exception here instead of bare except logging.error("Error in statistics reset expression: " + statistics_reset_cron) next_reset_time = None @@ -177,4 +176,4 @@ def _get_next_reset_time(): def _format_interval(seconds): screen_time = round(seconds / 60) hours, minutes = divmod(screen_time, 60) - return '{:02d}:{:02d}'.format(hours, minutes) + return "{:02d}:{:02d}".format(hours, minutes) diff --git a/safeeyes/plugins/limitconsecutiveskipping/plugin.py b/safeeyes/plugins/limitconsecutiveskipping/plugin.py index 1a55183e..864b609e 100644 --- a/safeeyes/plugins/limitconsecutiveskipping/plugin.py +++ b/safeeyes/plugins/limitconsecutiveskipping/plugin.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Limit how many breaks can be skipped or postponed in a row. -""" +"""Limit how many breaks can be skipped or postponed in a row.""" import logging @@ -27,76 +25,70 @@ session = None enabled = True + def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the plugin. - """ + """Initialize the plugin.""" global enabled global context global session global no_of_skipped_breaks global no_allowed_skips - logging.debug('Initialize Limit consecutive skipping plugin') + logging.debug("Initialize Limit consecutive skipping plugin") context = ctx - no_allowed_skips = plugin_config.get('number_of_allowed_skips_in_a_row', 2) + no_allowed_skips = plugin_config.get("number_of_allowed_skips_in_a_row", 2) if session is None: # Read the session - session = context['session']['plugin'].get('limitconsecutiveskipping', None) + session = context["session"]["plugin"].get("limitconsecutiveskipping", None) if session is None: - session = {'no_of_skipped_breaks': 0} - context['session']['plugin']['limitconsecutiveskipping'] = session - no_of_skipped_breaks = session.get('no_of_skipped_breaks', 0) + session = {"no_of_skipped_breaks": 0} + context["session"]["plugin"]["limitconsecutiveskipping"] = session + no_of_skipped_breaks = session.get("no_of_skipped_breaks", 0) def on_stop_break(): - """ - After the break, check if it is skipped. - """ + """After the break, check if it is skipped.""" # Check if the plugin is enabled if not enabled: return global no_of_skipped_breaks - if context['skipped'] or context['postponed']: + if context["skipped"] or context["postponed"]: no_of_skipped_breaks += 1 - session['no_of_skipped_breaks'] = no_of_skipped_breaks + session["no_of_skipped_breaks"] = no_of_skipped_breaks else: no_of_skipped_breaks = 0 - session['no_of_skipped_breaks'] = no_of_skipped_breaks + session["no_of_skipped_breaks"] = no_of_skipped_breaks def on_start_break(break_obj): - logging.debug('Skipped / allowed = {} / {}'.format(no_of_skipped_breaks, no_allowed_skips)) + logging.debug( + "Skipped / allowed = {} / {}".format(no_of_skipped_breaks, no_allowed_skips) + ) if no_of_skipped_breaks >= no_allowed_skips: - context['postpone_button_disabled'] = True - context['skip_button_disabled'] = True + context["postpone_button_disabled"] = True + context["skip_button_disabled"] = True def get_widget_title(break_obj): - """ - Return the widget title. - """ + """Return the widget title.""" # Check if the plugin is enabled if not enabled: return "" - return _('Limit Consecutive Skipping') + return _("Limit Consecutive Skipping") def get_widget_content(break_obj): - """ - Return the statistics. - """ + """Return the statistics.""" # Check if the plugin is enabled if not enabled: return "" - return _('Skipped or postponed %(num)d/%(allowed)d breaks in a row') % { - 'num': no_of_skipped_breaks, - 'allowed': no_allowed_skips + return _("Skipped or postponed %(num)d/%(allowed)d breaks in a row") % { + "num": no_of_skipped_breaks, + "allowed": no_allowed_skips, } - diff --git a/safeeyes/plugins/mediacontrol/plugin.py b/safeeyes/plugins/mediacontrol/plugin.py index 5c43ac33..ae847107 100644 --- a/safeeyes/plugins/mediacontrol/plugin.py +++ b/safeeyes/plugins/mediacontrol/plugin.py @@ -16,50 +16,48 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Media Control plugin lets users to pause currently playing media player from the break screen. +"""Media Control plugin lets users to pause currently playing media player from +the break screen. """ -import logging import os import re import gi from safeeyes.model import TrayAction -gi.require_version('Gtk', '3.0') + +gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gio tray_icon_path = None def __active_players(): - """ - List of all media players which are playing now. - """ + """List of all media players which are playing now.""" players = [] dbus_proxy = Gio.DBusProxy.new_for_bus_sync( bus_type=Gio.BusType.SESSION, flags=Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, info=None, - name='org.freedesktop.DBus', - object_path='/org/freedesktop/DBus', - interface_name='org.freedesktop.DBus', + name="org.freedesktop.DBus", + object_path="/org/freedesktop/DBus", + interface_name="org.freedesktop.DBus", cancellable=None, ) for service in dbus_proxy.ListNames(): - if re.match('org.mpris.MediaPlayer2.', service): + if re.match("org.mpris.MediaPlayer2.", service): player = Gio.DBusProxy.new_for_bus_sync( bus_type=Gio.BusType.SESSION, flags=Gio.DBusProxyFlags.NONE, info=None, name=service, - object_path='/org/mpris/MediaPlayer2', - interface_name='org.mpris.MediaPlayer2.Player', + object_path="/org/mpris/MediaPlayer2", + interface_name="org.mpris.MediaPlayer2.Player", cancellable=None, ) - status = player.get_cached_property('PlaybackStatus').unpack().lower() + status = player.get_cached_property("PlaybackStatus").unpack().lower() if status == "playing": players.append(player) @@ -67,28 +65,24 @@ def __active_players(): def __pause_players(players): - """ - Pause all playing media players using dbus. - """ + """Pause all playing media players using dbus.""" for player in players: player.Pause() def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the screensaver plugin. - """ + """Initialize the screensaver plugin.""" global tray_icon_path - tray_icon_path = os.path.join(plugin_config['path'], "resource/pause.png") + tray_icon_path = os.path.join(plugin_config["path"], "resource/pause.png") def get_tray_action(break_obj): - """ - Return TrayAction only if there is a media player currently playing. - """ + """Return TrayAction only if there is a media player currently playing.""" players = __active_players() if players: - return TrayAction.build("Pause media", - tray_icon_path, - Gtk.STOCK_MEDIA_PAUSE, - lambda: __pause_players(players)) + return TrayAction.build( + "Pause media", + tray_icon_path, + Gtk.STOCK_MEDIA_PAUSE, + lambda: __pause_players(players), + ) diff --git a/safeeyes/plugins/notification/plugin.py b/safeeyes/plugins/notification/plugin.py index b7920fbb..5dd4d51d 100644 --- a/safeeyes/plugins/notification/plugin.py +++ b/safeeyes/plugins/notification/plugin.py @@ -21,14 +21,14 @@ import gi from safeeyes.model import BreakType -gi.require_version('Notify', '0.7') +gi.require_version("Notify", "0.7") from gi.repository import Notify """ Safe Eyes Notification plugin """ -APPINDICATOR_ID = 'safeeyes' +APPINDICATOR_ID = "safeeyes" notification = None context = None warning_time = 10 @@ -37,42 +37,38 @@ def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the plugin. - """ + """Initialize the plugin.""" global context global warning_time - logging.debug('Initialize Notification plugin') + logging.debug("Initialize Notification plugin") context = ctx - warning_time = safeeyes_config.get('pre_break_warning_time') + warning_time = safeeyes_config.get("pre_break_warning_time") def on_pre_break(break_obj): - """ - Show the notification - """ + """Show the notification.""" # Construct the message based on the type of the next break global notification - logging.info('Show the notification') - message = '\n' + logging.info("Show the notification") + message = "\n" if break_obj.type == BreakType.SHORT_BREAK: - message += (_('Ready for a short break in %s seconds') % warning_time) + message += _("Ready for a short break in %s seconds") % warning_time else: - message += (_('Ready for a long break in %s seconds') % warning_time) + message += _("Ready for a long break in %s seconds") % warning_time - notification = Notify.Notification.new('Safe Eyes', message, icon='io.github.slgobinath.SafeEyes-enabled') + notification = Notify.Notification.new( + "Safe Eyes", message, icon="io.github.slgobinath.SafeEyes-enabled" + ) try: notification.show() except BaseException: - logging.error('Failed to show the notification') + logging.error("Failed to show the notification") def on_start_break(break_obj): - """ - Close the notification. - """ + """Close the notification.""" global notification - logging.info('Close pre-break notification') + logging.info("Close pre-break notification") if notification: try: notification.close() @@ -83,8 +79,6 @@ def on_start_break(break_obj): def on_exit(): - """ - Uninitialize the registered notification. - """ - logging.debug('Stop Notification plugin') + """Uninitialize the registered notification.""" + logging.debug("Stop Notification plugin") Notify.uninit() diff --git a/safeeyes/plugins/screensaver/plugin.py b/safeeyes/plugins/screensaver/plugin.py index e8e5e3b9..0f1240a8 100644 --- a/safeeyes/plugins/screensaver/plugin.py +++ b/safeeyes/plugins/screensaver/plugin.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Screensaver plugin locks the desktop using native screensaver application, after long breaks. +"""Screensaver plugin locks the desktop using native screensaver application, +after long breaks. """ import gi @@ -26,7 +26,8 @@ from safeeyes import utility from safeeyes.model import TrayAction -gi.require_version('Gtk', '3.0') + +gi.require_version("Gtk", "3.0") from gi.repository import Gtk context = None @@ -39,40 +40,69 @@ def __lock_screen_command(): - """ - Function tries to detect the screensaver command based on the current envinroment + """Function tries to detect the screensaver command based on the current + envinroment. + Possible results: Gnome, Unity, Budgie: ['gnome-screensaver-command', '--lock'] Cinnamon: ['cinnamon-screensaver-command', '--lock'] Pantheon, LXDE: ['light-locker-command', '--lock'] Mate: ['mate-screensaver-command', '--lock'] - KDE: ['qdbus', 'org.freedesktop.ScreenSaver', '/ScreenSaver', 'Lock'] + KDE: ['qdbus', 'org.freedesktop.ScreenSaver', + '/ScreenSaver', 'Lock'] XFCE: ['xflock4'] Otherwise: None """ - desktop_session = os.environ.get('DESKTOP_SESSION') - current_desktop = os.environ.get('XDG_CURRENT_DESKTOP') + desktop_session = os.environ.get("DESKTOP_SESSION") + current_desktop = os.environ.get("XDG_CURRENT_DESKTOP") if desktop_session is not None: desktop_session = desktop_session.lower() - if ('xfce' in desktop_session or desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop)) and utility.command_exist('xflock4'): - return ['xflock4'] - elif desktop_session == 'cinnamon' and utility.command_exist('cinnamon-screensaver-command'): - return ['cinnamon-screensaver-command', '--lock'] - elif (desktop_session == 'pantheon' or desktop_session.startswith('lubuntu')) and utility.command_exist('light-locker-command'): - return ['light-locker-command', '--lock'] - elif desktop_session == 'mate' and utility.command_exist('mate-screensaver-command'): - return ['mate-screensaver-command', '--lock'] - elif desktop_session == 'kde' or 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true': - return ['qdbus', 'org.freedesktop.ScreenSaver', '/ScreenSaver', 'Lock'] - elif desktop_session in ['gnome', 'unity', 'budgie-desktop'] or desktop_session.startswith('ubuntu') or desktop_session.startswith('gnome'): - if utility.command_exist('gnome-screensaver-command'): - return ['gnome-screensaver-command', '--lock'] + if ( + "xfce" in desktop_session + or desktop_session.startswith("xubuntu") + or (current_desktop is not None and "xfce" in current_desktop) + ) and utility.command_exist("xflock4"): + return ["xflock4"] + elif desktop_session == "cinnamon" and utility.command_exist( + "cinnamon-screensaver-command" + ): + return ["cinnamon-screensaver-command", "--lock"] + elif ( + desktop_session == "pantheon" or desktop_session.startswith("lubuntu") + ) and utility.command_exist("light-locker-command"): + return ["light-locker-command", "--lock"] + elif desktop_session == "mate" and utility.command_exist( + "mate-screensaver-command" + ): + return ["mate-screensaver-command", "--lock"] + elif ( + desktop_session == "kde" + or "plasma" in desktop_session + or desktop_session.startswith("kubuntu") + or os.environ.get("KDE_FULL_SESSION") == "true" + ): + return ["qdbus", "org.freedesktop.ScreenSaver", "/ScreenSaver", "Lock"] + elif ( + desktop_session in ["gnome", "unity", "budgie-desktop"] + or desktop_session.startswith("ubuntu") + or desktop_session.startswith("gnome") + ): + if utility.command_exist("gnome-screensaver-command"): + return ["gnome-screensaver-command", "--lock"] # From Gnome 3.8 no gnome-screensaver-command - return ['dbus-send', '--type=method_call', '--dest=org.gnome.ScreenSaver', '/org/gnome/ScreenSaver', 'org.gnome.ScreenSaver.Lock'] - elif os.environ.get('GNOME_DESKTOP_SESSION_ID'): - if 'deprecated' not in os.environ.get('GNOME_DESKTOP_SESSION_ID') and utility.command_exist('gnome-screensaver-command'): + return [ + "dbus-send", + "--type=method_call", + "--dest=org.gnome.ScreenSaver", + "/org/gnome/ScreenSaver", + "org.gnome.ScreenSaver.Lock", + ] + elif os.environ.get("GNOME_DESKTOP_SESSION_ID"): + if "deprecated" not in os.environ.get( + "GNOME_DESKTOP_SESSION_ID" + ) and utility.command_exist("gnome-screensaver-command"): # Gnome 2 - return ['gnome-screensaver-command', '--lock'] + return ["gnome-screensaver-command", "--lock"] return None @@ -82,26 +112,24 @@ def __lock_screen(): def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the screensaver plugin. - """ + """Initialize the screensaver plugin.""" global context global lock_screen_command global min_seconds global tray_icon_path - logging.debug('Initialize Screensaver plugin') + logging.debug("Initialize Screensaver plugin") context = ctx - min_seconds = plugin_config['min_seconds'] - tray_icon_path = os.path.join(plugin_config['path'], "resource/lock.png") - if plugin_config['command']: - lock_screen_command = plugin_config['command'].split() + min_seconds = plugin_config["min_seconds"] + tray_icon_path = os.path.join(plugin_config["path"], "resource/lock.png") + if plugin_config["command"]: + lock_screen_command = plugin_config["command"].split() else: lock_screen_command = __lock_screen_command() def on_start_break(break_obj): - """ - Determine the break type and only if it is a long break, enable the lock_screen flag. + """Determine the break type and only if it is a long break, enable the + lock_screen flag. """ global lock_screen global seconds_passed @@ -113,23 +141,20 @@ def on_start_break(break_obj): def on_countdown(countdown, seconds): - """ - Keep track of seconds passed from the beginning of long break. - """ + """Keep track of seconds passed from the beginning of long break.""" global seconds_passed seconds_passed = seconds def on_stop_break(): - """ - Lock the screen after a long break if the user has not skipped within min_seconds. + """Lock the screen after a long break if the user has not skipped within + min_seconds. """ if user_locked_screen or (lock_screen and seconds_passed >= min_seconds): utility.execute_command(lock_screen_command) def get_tray_action(break_obj): - return TrayAction.build("Lock screen", - tray_icon_path, - Gtk.STOCK_DIALOG_AUTHENTICATION, - __lock_screen) + return TrayAction.build( + "Lock screen", tray_icon_path, Gtk.STOCK_DIALOG_AUTHENTICATION, __lock_screen + ) diff --git a/safeeyes/plugins/smartpause/plugin.py b/safeeyes/plugins/smartpause/plugin.py index 296fe516..8c9e8663 100644 --- a/safeeyes/plugins/smartpause/plugin.py +++ b/safeeyes/plugins/smartpause/plugin.py @@ -21,7 +21,6 @@ import subprocess import threading import re -import os from safeeyes import utility from safeeyes.model import State @@ -51,33 +50,40 @@ swayidle_idle = 0 swayidle_active = 0 + def __swayidle_running(): - return (swayidle_process is not None and - swayidle_process.poll() is None) + return swayidle_process is not None and swayidle_process.poll() is None + def __start_swayidle_monitor(): global swayidle_process global swayidle_start global swayidle_idle global swayidle_active - logging.debug('Starting swayidle subprocess') - swayidle_process = subprocess.Popen([ - "swayidle", "timeout", "1", "date +S%s", "resume", "date +R%s" - ], stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') + logging.debug("Starting swayidle subprocess") + swayidle_process = subprocess.Popen( + ["swayidle", "timeout", "1", "date +S%s", "resume", "date +R%s"], + stdout=subprocess.PIPE, + bufsize=1, + universal_newlines=True, + encoding="utf-8", + ) for line in swayidle_process.stdout: with swayidle_lock: typ = line[0] timestamp = int(line[1:]) - if typ == 'S': + if typ == "S": swayidle_idle = timestamp - elif typ == 'R': + elif typ == "R": swayidle_active = timestamp + def __stop_swayidle_monitor(): if __swayidle_running(): - logging.debug('Stopping swayidle subprocess') + logging.debug("Stopping swayidle subprocess") swayidle_process.terminate() + def __swayidle_idle_time(): with swayidle_lock: if not __swayidle_running(): @@ -88,21 +94,25 @@ def __swayidle_idle_time(): return idle_time return 0 + def __gnome_wayland_idle_time(): - """ - Determine system idle time in seconds, specifically for gnome with wayland. + """Determine system idle time in seconds, specifically for gnome with + wayland. + If there's a failure, return 0. https://unix.stackexchange.com/a/492328/222290 """ try: - output = subprocess.check_output([ - 'dbus-send', - '--print-reply', - '--dest=org.gnome.Mutter.IdleMonitor', - '/org/gnome/Mutter/IdleMonitor/Core', - 'org.gnome.Mutter.IdleMonitor.GetIdletime' - ]) - return int(re.search(rb'\d+$', output).group(0)) / 1000 + output = subprocess.check_output( + [ + "dbus-send", + "--print-reply", + "--dest=org.gnome.Mutter.IdleMonitor", + "/org/gnome/Mutter/IdleMonitor/Core", + "org.gnome.Mutter.IdleMonitor.GetIdletime", + ] + ) + return int(re.search(rb"\d+$", output).group(0)) / 1000 except BaseException as e: logging.warning("Failed to get system idle time for gnome/wayland.") logging.warning(str(e)) @@ -110,8 +120,8 @@ def __gnome_wayland_idle_time(): def __system_idle_time(): - """ - Get system idle time in minutes. + """Get system idle time in minutes. + Return the idle time if xprintidle is available, otherwise return 0. """ try: @@ -120,15 +130,13 @@ def __system_idle_time(): elif use_swayidle: return __swayidle_idle_time() # Convert to seconds - return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 1000 + return int(subprocess.check_output(["xprintidle"]).decode("utf-8")) / 1000 except BaseException: return 0 def __is_active(): - """ - Thread safe function to see if this plugin is active or not. - """ + """Thread safe function to see if this plugin is active or not.""" is_active = False with lock: is_active = active @@ -136,18 +144,14 @@ def __is_active(): def __set_active(is_active): - """ - Thread safe function to change the state of the plugin. - """ + """Thread safe function to change the state of the plugin.""" global active with lock: active = is_active def init(ctx, safeeyes_config, plugin_config): - """ - Initialize the plugin. - """ + """Initialize the plugin.""" global context global enable_safeeyes global disable_safeeyes @@ -159,24 +163,25 @@ def init(ctx, safeeyes_config, plugin_config): global postpone_if_active global is_wayland_and_gnome global use_swayidle - logging.debug('Initialize Smart Pause plugin') + logging.debug("Initialize Smart Pause plugin") context = ctx - enable_safeeyes = context['api']['enable_safeeyes'] - disable_safeeyes = context['api']['disable_safeeyes'] - postpone = context['api']['postpone'] - idle_time = plugin_config['idle_time'] - postpone_if_active = plugin_config['postpone_if_active'] - short_break_interval = safeeyes_config.get( - 'short_break_interval') * 60 # Convert to seconds - long_break_duration = safeeyes_config.get('long_break_duration') + enable_safeeyes = context["api"]["enable_safeeyes"] + disable_safeeyes = context["api"]["disable_safeeyes"] + postpone = context["api"]["postpone"] + idle_time = plugin_config["idle_time"] + postpone_if_active = plugin_config["postpone_if_active"] + short_break_interval = ( + safeeyes_config.get("short_break_interval") * 60 + ) # Convert to seconds + long_break_duration = safeeyes_config.get("long_break_duration") waiting_time = min(2, idle_time) # If idle time is 1 sec, wait only 1 sec - is_wayland_and_gnome = context['desktop'] == 'gnome' and context['is_wayland'] - use_swayidle = context['desktop'] == 'sway' + is_wayland_and_gnome = context["desktop"] == "gnome" and context["is_wayland"] + use_swayidle = context["desktop"] == "sway" def __start_idle_monitor(): - """ - Continuously check the system idle time and pause/resume Safe Eyes based on it. + """Continuously check the system idle time and pause/resume Safe Eyes based + on it. """ global smart_pause_activated global idle_start_time @@ -190,22 +195,29 @@ def __start_idle_monitor(): if __is_active(): # Get the system idle time system_idle_time = __system_idle_time() - if system_idle_time >= idle_time and context['state'] == State.WAITING: + if system_idle_time >= idle_time and context["state"] == State.WAITING: smart_pause_activated = True - idle_start_time = datetime.datetime.now() - datetime.timedelta(seconds=system_idle_time) - logging.info('Pause Safe Eyes due to system idle') + idle_start_time = datetime.datetime.now() - datetime.timedelta( + seconds=system_idle_time + ) + logging.info("Pause Safe Eyes due to system idle") disable_safeeyes(None, True) - elif system_idle_time < idle_time and context['state'] == State.RESTING and idle_start_time is not None: - logging.info('Resume Safe Eyes due to user activity') + elif ( + system_idle_time < idle_time + and context["state"] == State.RESTING + and idle_start_time is not None + ): + logging.info("Resume Safe Eyes due to user activity") smart_pause_activated = False - idle_period = (datetime.datetime.now() - idle_start_time) + idle_period = datetime.datetime.now() - idle_start_time idle_seconds = idle_period.total_seconds() - context['idle_period'] = idle_seconds + context["idle_period"] = idle_seconds if idle_seconds < short_break_interval: # Credit back the idle time if next_break_time is not None: # This method runs in a thread since the start. - # It may run before next_break is initialized in the update_next_break method + # It may run before next_break is initialized in the + # update_next_break method next_break = next_break_time + idle_period enable_safeeyes(next_break.timestamp()) else: @@ -216,28 +228,24 @@ def __start_idle_monitor(): def on_start(): - """ - Start a thread to continuously call xprintidle. - """ + """Start a thread to continuously call xprintidle.""" global active if not __is_active(): # If SmartPause is already started, do not start it again - logging.debug('Start Smart Pause plugin') + logging.debug("Start Smart Pause plugin") __set_active(True) utility.start_thread(__start_idle_monitor) def on_stop(): - """ - Stop the thread from continuously calling xprintidle. - """ + """Stop the thread from continuously calling xprintidle.""" global active global smart_pause_activated if smart_pause_activated: # Safe Eyes is stopped due to system idle smart_pause_activated = False return - logging.debug('Stop Smart Pause plugin') + logging.debug("Stop Smart Pause plugin") if use_swayidle: __stop_swayidle_monitor() __set_active(False) @@ -247,9 +255,7 @@ def on_stop(): def update_next_break(break_obj, dateTime): - """ - Update the next break time. - """ + """Update the next break time.""" global next_break_time global next_break_duration next_break_time = dateTime @@ -257,9 +263,7 @@ def update_next_break(break_obj, dateTime): def on_start_break(break_obj): - """ - Lifecycle method executes just before the break. - """ + """Lifecycle method executes just before the break.""" if postpone_if_active: # Postpone this break if the user is active system_idle_time = __system_idle_time() @@ -268,8 +272,6 @@ def on_start_break(break_obj): def disable(): - """ - SmartPause plugin was active earlier but now user has disabled it. - """ + """SmartPause plugin was active earlier but now user has disabled it.""" # Remove the idle_period - context.pop('idle_period', None) + context.pop("idle_period", None) diff --git a/safeeyes/plugins/trayicon/dependency_checker.py b/safeeyes/plugins/trayicon/dependency_checker.py index 574433ad..2ea5d217 100644 --- a/safeeyes/plugins/trayicon/dependency_checker.py +++ b/safeeyes/plugins/trayicon/dependency_checker.py @@ -20,27 +20,32 @@ from safeeyes.model import PluginDependency import gi -gi.require_version('Gio', '2.0') + +gi.require_version("Gio", "2.0") from gi.repository import Gio + def validate(plugin_config, plugin_settings): dbus_proxy = Gio.DBusProxy.new_for_bus_sync( bus_type=Gio.BusType.SESSION, flags=Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, info=None, - name='org.freedesktop.DBus', - object_path='/org/freedesktop/DBus', - interface_name='org.freedesktop.DBus', + name="org.freedesktop.DBus", + object_path="/org/freedesktop/DBus", + interface_name="org.freedesktop.DBus", cancellable=None, ) - if dbus_proxy.NameHasOwner('(s)', 'org.kde.StatusNotifierWatcher'): + if dbus_proxy.NameHasOwner("(s)", "org.kde.StatusNotifierWatcher"): return None else: return PluginDependency( - message=_("Please install service providing tray icons for your desktop environment."), + message=_( + "Please install service providing tray icons for your desktop" + " environment." + ), link="https://github.com/slgobinath/SafeEyes/wiki/How-to-install-backend-for-Safe-Eyes-tray-icon", - retryable=True + retryable=True, ) command = None diff --git a/safeeyes/plugins/trayicon/plugin.py b/safeeyes/plugins/trayicon/plugin.py index 42b309a5..7d0ab1cc 100644 --- a/safeeyes/plugins/trayicon/plugin.py +++ b/safeeyes/plugins/trayicon/plugin.py @@ -19,7 +19,8 @@ import datetime from safeeyes.model import BreakType import gi -gi.require_version('Gtk', '3.0') + +gi.require_version("Gtk", "3.0") from gi.repository import Gio, GLib import logging from safeeyes import utility @@ -34,7 +35,8 @@ tray_icon = None safeeyes_config = None -SNI_NODE_INFO = Gio.DBusNodeInfo.new_for_xml(""" +SNI_NODE_INFO = Gio.DBusNodeInfo.new_for_xml( + """ @@ -56,9 +58,11 @@ -""").interfaces[0] +""" +).interfaces[0] -MENU_NODE_INFO = Gio.DBusNodeInfo.new_for_xml(""" +MENU_NODE_INFO = Gio.DBusNodeInfo.new_for_xml( + """ @@ -70,15 +74,15 @@ - - - - + + + + - - - - + + + + @@ -86,24 +90,26 @@ - - - + + + - - - - + + + + -""").interfaces[0] +""" +).interfaces[0] + class DBusService: def __init__(self, interface_info, object_path, bus): @@ -117,7 +123,7 @@ def register(self): object_path=self.object_path, interface_info=self.interface_info, method_call_closure=self.on_method_call, - get_property_closure=self.on_get_property + get_property_closure=self.on_get_property, ) if not self.registration_id: @@ -132,7 +138,16 @@ def unregister(self): self.bus.unregister_object(self.registration_id) self.registration_id = None - def on_method_call(self, _connection, _sender, _path, _interface_name, method_name, parameters, invocation): + def on_method_call( + self, + _connection, + _sender, + _path, + _interface_name, + method_name, + parameters, + invocation, + ): method_info = self.interface_info.lookup_method(method_name) method = getattr(self, method_name) result = method(*parameters.unpack()) @@ -144,11 +159,13 @@ def on_method_call(self, _connection, _sender, _path, _interface_name, method_na invocation.return_value(return_value) - def on_get_property(self, _connection, _sender, _path, _interface_name, property_name): + def on_get_property( + self, _connection, _sender, _path, _interface_name, property_name + ): property_info = self.interface_info.lookup_property(property_name) return GLib.Variant(property_info.signature, getattr(self, property_name)) - def emit_signal(self, signal_name, args = None): + def emit_signal(self, signal_name, args=None): signal_info = self.interface_info.lookup_signal(signal_name) if len(signal_info.args) == 0: parameters = None @@ -160,11 +177,12 @@ def emit_signal(self, signal_name, args = None): object_path=self.object_path, interface_name=self.interface_info.name, signal_name=signal_name, - parameters=parameters + parameters=parameters, ) + class DBusMenuService(DBusService): - DBUS_SERVICE_PATH = '/io/github/slgobinath/SafeEyes/Menu' + DBUS_SERVICE_PATH = "/io/github/slgobinath/SafeEyes/Menu" revision = 0 @@ -175,7 +193,7 @@ def __init__(self, session_bus, context, items): super().__init__( interface_info=MENU_NODE_INFO, object_path=self.DBUS_SERVICE_PATH, - bus=session_bus + bus=session_bus, ) self.set_items(items) @@ -192,13 +210,13 @@ def set_items(self, items): @staticmethod def getItemsFlat(items, idToItems): for item in items: - if item.get('hidden', False) == True: + if item.get("hidden", False): continue - idToItems[item['id']] = item + idToItems[item["id"]] = item - if 'children' in item: - idToItems = DBusMenuService.getItemsFlat(item['children'], idToItems) + if "children" in item: + idToItems = DBusMenuService.getItemsFlat(item["children"], idToItems) return idToItems @@ -206,27 +224,27 @@ def getItemsFlat(items, idToItems): def singleItemToDbus(item): props = DBusMenuService.itemPropsToDbus(item) - return (item['id'], props) + return (item["id"], props) @staticmethod def itemPropsToDbus(item): result = {} - string_props = ['label', 'icon-name', 'type', 'children-display'] + string_props = ["label", "icon-name", "type", "children-display"] for key in string_props: if key in item: - result[key] = GLib.Variant('s', item[key]) + result[key] = GLib.Variant("s", item[key]) - bool_props = ['enabled'] + bool_props = ["enabled"] for key in bool_props: if key in item: - result[key] = GLib.Variant('b', item[key]) + result[key] = GLib.Variant("b", item[key]) return result @staticmethod def itemToDbus(item, recursion_depth): - if item.get('hidden', False) == True: + if item.get("hidden", False): return None props = DBusMenuService.itemPropsToDbus(item) @@ -234,20 +252,23 @@ def itemToDbus(item, recursion_depth): children = [] if recursion_depth > 1 or recursion_depth == -1: if "children" in item: - children = [DBusMenuService.itemToDbus(item, recursion_depth - 1) for item in item['children']] + children = [ + DBusMenuService.itemToDbus(item, recursion_depth - 1) + for item in item["children"] + ] children = [i for i in children if i is not None] - return GLib.Variant("(ia{sv}av)", (item['id'], props, children)) + return GLib.Variant("(ia{sv}av)", (item["id"], props, children)) def findItemsWithParent(self, parent_id, items): for item in items: - if item.get('hidden', False) == True: + if item.get("hidden", False): continue - if 'children' in item: - if item['id'] == parent_id: - return item['children'] + if "children" in item: + if item["id"] == parent_id: + return item["children"] else: - ret = self.findItemsWithParent(parent_id, item['children']) + ret = self.findItemsWithParent(parent_id, item["children"]) if ret is not None: return ret return None @@ -267,11 +288,7 @@ def GetLayout(self, parent_id, recursion_depth, property_names): ret = ( self.revision, - ( - 0, - { 'children-display': GLib.Variant('s', 'submenu') }, - children - ) + (0, {"children-display": GLib.Variant("s", "submenu")}, children), ) return ret @@ -303,13 +320,13 @@ def Event(self, idx, event_id, data, timestamp): if idx in self.idToItems: item = self.idToItems[idx] - if 'callback' in item: - item['callback']() + if "callback" in item: + item["callback"]() def EventGroup(self, events): not_found = [] - for (idx, event_id, data, timestamp) in events: + for idx, event_id, data, timestamp in events: if idx not in self.idToItems: not_found.append(idx) continue @@ -318,8 +335,8 @@ def EventGroup(self, events): continue item = self.idToItems[idx] - if 'callback' in item: - item['callback']() + if "callback" in item: + item["callback"]() return not_found @@ -337,21 +354,19 @@ def AboutToShowGroup(self, ids): return ([], not_found) def LayoutUpdated(self, revision, parent): - self.emit_signal( - 'LayoutUpdated', - (revision, parent) - ) + self.emit_signal("LayoutUpdated", (revision, parent)) + class StatusNotifierItemService(DBusService): - DBUS_SERVICE_PATH = '/org/ayatana/NotificationItem/io_github_slgobinath_SafeEyes' - - Category = 'ApplicationStatus' - Id = 'io.github.slgobinath.SafeEyes' - Title = 'Safe Eyes' - Status = 'Active' - IconName = 'io.github.slgobinath.SafeEyes-enabled' - IconThemePath = '' - ToolTip = ('', [], 'Safe Eyes', '') + DBUS_SERVICE_PATH = "/org/ayatana/NotificationItem/io_github_slgobinath_SafeEyes" + + Category = "ApplicationStatus" + Id = "io.github.slgobinath.SafeEyes" + Title = "Safe Eyes" + Status = "Active" + IconName = "io.github.slgobinath.SafeEyes-enabled" + IconThemePath = "" + ToolTip = ("", [], "Safe Eyes", "") XAyatanaLabel = "" ItemIsMenu = True Menu = None @@ -360,7 +375,7 @@ def __init__(self, session_bus, context, menu_items): super().__init__( interface_info=SNI_NODE_INFO, object_path=self.DBUS_SERVICE_PATH, - bus=session_bus + bus=session_bus, ) self.bus = session_bus @@ -376,13 +391,13 @@ def register(self): connection=self.bus, flags=Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, info=None, - name='org.kde.StatusNotifierWatcher', - object_path='/StatusNotifierWatcher', - interface_name='org.kde.StatusNotifierWatcher', + name="org.kde.StatusNotifierWatcher", + object_path="/StatusNotifierWatcher", + interface_name="org.kde.StatusNotifierWatcher", cancellable=None, ) - watcher.RegisterStatusNotifierItem('(s)', self.DBUS_SERVICE_PATH) + watcher.RegisterStatusNotifierItem("(s)", self.DBUS_SERVICE_PATH) def unregister(self): super().unregister() @@ -394,67 +409,55 @@ def set_items(self, items): def set_icon(self, icon): self.IconName = icon - self.emit_signal( - 'NewIcon' - ) + self.emit_signal("NewIcon") def set_tooltip(self, title, description): - self.ToolTip = ('', [], title, description) + self.ToolTip = ("", [], title, description) - self.emit_signal( - 'NewTooltip' - ) + self.emit_signal("NewTooltip") def set_xayatanalabel(self, label): self.XAyatanaLabel = label - self.emit_signal( - "XAyatanaNewLabel", - (label, "") - ) + self.emit_signal("XAyatanaNewLabel", (label, "")) + class TrayIcon: - """ - Create and show the tray icon along with the tray menu. - """ + """Create and show the tray icon along with the tray menu.""" def __init__(self, context, plugin_config): self.context = context - self.on_show_settings = context['api']['show_settings'] - self.on_show_about = context['api']['show_about'] - self.quit = context['api']['quit'] - self.enable_safeeyes = context['api']['enable_safeeyes'] - self.disable_safeeyes = context['api']['disable_safeeyes'] - self.take_break = context['api']['take_break'] - self.has_breaks = context['api']['has_breaks'] - self.get_break_time = context['api']['get_break_time'] + self.on_show_settings = context["api"]["show_settings"] + self.on_show_about = context["api"]["show_about"] + self.quit = context["api"]["quit"] + self.enable_safeeyes = context["api"]["enable_safeeyes"] + self.disable_safeeyes = context["api"]["disable_safeeyes"] + self.take_break = context["api"]["take_break"] + self.has_breaks = context["api"]["has_breaks"] + self.get_break_time = context["api"]["get_break_time"] self.plugin_config = plugin_config self.date_time = None self.active = True self.wakeup_time = None self.idle_condition = threading.Condition() self.lock = threading.Lock() - self.allow_disabling = plugin_config['allow_disabling'] + self.allow_disabling = plugin_config["allow_disabling"] self.animate = False self.menu_locked = False session_bus = Gio.bus_get_sync(Gio.BusType.SESSION) self.sni_service = StatusNotifierItemService( - session_bus, - context, - menu_items = self.get_items() + session_bus, context, menu_items=self.get_items() ) self.sni_service.register() self.update_tooltip() def initialize(self, plugin_config): - """ - Initialize the tray icon by setting the config. - """ + """Initialize the tray icon by setting the config.""" self.plugin_config = plugin_config - self.allow_disabling = plugin_config['allow_disabling'] + self.allow_disabling = plugin_config["allow_disabling"] self.update_menu() self.update_tooltip() @@ -462,7 +465,7 @@ def initialize(self, plugin_config): def get_items(self): breaks_found = self.has_breaks() - info_message = _('No Breaks Available') + info_message = _("No Breaks Available") if breaks_found: if self.active: @@ -473,139 +476,148 @@ def get_items(self): if next_long_time: if next_is_long: - info_message = _('Next long break at %s') % (next_long_time) + info_message = _("Next long break at %s") % (next_long_time) else: - info_message = _('Next breaks at %(short)s/%(long)s') % { - 'short': next_time, - 'long': next_long_time + info_message = _("Next breaks at %(short)s/%(long)s") % { + "short": next_time, + "long": next_long_time, } else: - info_message = _('Next break at %s') % (next_time) + info_message = _("Next break at %s") % (next_time) else: if self.wakeup_time: - info_message = _('Disabled until %s') % utility.format_time(self.wakeup_time) + info_message = _("Disabled until %s") % utility.format_time( + self.wakeup_time + ) else: - info_message = _('Disabled until restart') + info_message = _("Disabled until restart") disable_items = [] if self.allow_disabling: disable_option_dynamic_id = 13 - for disable_option in self.plugin_config['disable_options']: - time_in_minutes = time_in_x = disable_option['time'] + for disable_option in self.plugin_config["disable_options"]: + time_in_minutes = time_in_x = disable_option["time"] # Validate time value if not isinstance(time_in_minutes, int) or time_in_minutes <= 0: - logging.error('Invalid time in disable option: ' + str(time_in_minutes)) + logging.error( + "Invalid time in disable option: " + str(time_in_minutes) + ) continue - time_unit = disable_option['unit'].lower() - if time_unit == 'seconds' or time_unit == 'second': + time_unit = disable_option["unit"].lower() + if time_unit == "seconds" or time_unit == "second": time_in_minutes = int(time_in_minutes / 60) - label = self.context['locale'].ngettext( - 'For %(num)d Second', - 'For %(num)d Seconds', - time_in_x - ) % {'num': time_in_x} - elif time_unit == 'minutes' or time_unit == 'minute': + label = self.context["locale"].ngettext( + "For %(num)d Second", "For %(num)d Seconds", time_in_x + ) % {"num": time_in_x} + elif time_unit == "minutes" or time_unit == "minute": time_in_minutes = int(time_in_minutes * 1) - label = self.context['locale'].ngettext( - 'For %(num)d Minute', - 'For %(num)d Minutes', - time_in_x - ) % {'num': time_in_x} - elif time_unit == 'hours' or time_unit == 'hour': + label = self.context["locale"].ngettext( + "For %(num)d Minute", "For %(num)d Minutes", time_in_x + ) % {"num": time_in_x} + elif time_unit == "hours" or time_unit == "hour": time_in_minutes = int(time_in_minutes * 60) - label = self.context['locale'].ngettext( - 'For %(num)d Hour', - 'For %(num)d Hours', - time_in_x - ) % {'num': time_in_x} + label = self.context["locale"].ngettext( + "For %(num)d Hour", "For %(num)d Hours", time_in_x + ) % {"num": time_in_x} else: # Invalid unit - logging.error('Invalid unit in disable option: ' + str(disable_option)) + logging.error( + "Invalid unit in disable option: " + str(disable_option) + ) continue - disable_items.append({ - 'id': disable_option_dynamic_id, - 'label': label, - 'callback': lambda time_in_minutes=time_in_minutes: self.on_disable_clicked(time_in_minutes), - }) + ttw = time_in_minutes + disable_items.append( + { + "id": disable_option_dynamic_id, + "label": label, + "callback": lambda ttw=ttw: self.on_disable_clicked(ttw), + } + ) disable_option_dynamic_id += 1 - disable_items.append({ - 'id': 12, - 'label': _('Until restart'), - 'callback': lambda: self.on_disable_clicked(-1), - }) + disable_items.append( + { + "id": 12, + "label": _("Until restart"), + "callback": lambda: self.on_disable_clicked(-1), + } + ) return [ { - 'id': 1, - 'label': info_message, - 'icon-name': "io.github.slgobinath.SafeEyes-timer", - 'enabled': breaks_found and self.active, + "id": 1, + "label": info_message, + "icon-name": "io.github.slgobinath.SafeEyes-timer", + "enabled": breaks_found and self.active, }, { - 'id': 2, - 'type': "separator", + "id": 2, + "type": "separator", }, { - 'id': 3, - 'label': _("Enable Safe Eyes"), - 'enabled': breaks_found and not self.active, - 'callback': self.on_enable_clicked, - 'hidden': not self.allow_disabling, + "id": 3, + "label": _("Enable Safe Eyes"), + "enabled": breaks_found and not self.active, + "callback": self.on_enable_clicked, + "hidden": not self.allow_disabling, }, { - 'id': 4, - 'label': _("Disable Safe Eyes"), - 'enabled': breaks_found and self.active and not self.menu_locked, - 'children-display': 'submenu', - 'children': disable_items, - 'hidden': not self.allow_disabling, + "id": 4, + "label": _("Disable Safe Eyes"), + "enabled": breaks_found and self.active and not self.menu_locked, + "children-display": "submenu", + "children": disable_items, + "hidden": not self.allow_disabling, }, { - 'id': 5, - 'label': _('Take a break now'), - 'enabled': breaks_found and self.active and not self.menu_locked, - 'children-display': 'submenu', - 'children': [ + "id": 5, + "label": _("Take a break now"), + "enabled": breaks_found and self.active and not self.menu_locked, + "children-display": "submenu", + "children": [ { - 'id': 9, - 'label': _('Any break'), - 'callback': lambda: self.on_manual_break_clicked(None), + "id": 9, + "label": _("Any break"), + "callback": lambda: self.on_manual_break_clicked(None), }, { - 'id': 10, - 'label': _('Short break'), - 'callback': lambda: self.on_manual_break_clicked(BreakType.SHORT_BREAK), + "id": 10, + "label": _("Short break"), + "callback": lambda: self.on_manual_break_clicked( + BreakType.SHORT_BREAK + ), }, { - 'id': 11, - 'label': _('Long break'), - 'callback': lambda: self.on_manual_break_clicked(BreakType.LONG_BREAK), + "id": 11, + "label": _("Long break"), + "callback": lambda: self.on_manual_break_clicked( + BreakType.LONG_BREAK + ), }, - ] + ], }, { - 'id': 6, - 'label': _('Settings'), - 'enabled': not self.menu_locked, - 'callback': self.show_settings, + "id": 6, + "label": _("Settings"), + "enabled": not self.menu_locked, + "callback": self.show_settings, }, { - 'id': 7, - 'label': _('About'), - 'callback': self.show_about, + "id": 7, + "label": _("About"), + "callback": self.show_about, }, { - 'id': 8, - 'label': _('Quit'), - 'enabled': not self.menu_locked, - 'callback': self.quit_safe_eyes, - 'hidden': not self.allow_disabling, + "id": 8, + "label": _("Quit"), + "enabled": not self.menu_locked, + "callback": self.quit_safe_eyes, + "hidden": not self.allow_disabling, }, ] @@ -615,22 +627,26 @@ def update_menu(self): def update_tooltip(self): next_break = self.get_next_break_time() - if next_break is not None and self.plugin_config.get('show_time_in_tray', False): + if next_break is not None and self.plugin_config.get( + "show_time_in_tray", False + ): (next_time, next_long_time, _next_is_long) = next_break - if next_long_time and self.plugin_config.get('show_long_time_in_tray', False): + if next_long_time and self.plugin_config.get( + "show_long_time_in_tray", False + ): description = next_long_time else: description = next_time else: - description = '' + description = "" - self.sni_service.set_tooltip('Safe Eyes', description) + self.sni_service.set_tooltip("Safe Eyes", description) self.sni_service.set_xayatanalabel(description) def quit_safe_eyes(self): - """ - Handle Quit menu action. + """Handle Quit menu action. + This action terminates the application. """ with self.lock: @@ -642,22 +658,22 @@ def quit_safe_eyes(self): self.quit() def show_settings(self): - """ - Handle Settings menu action. + """Handle Settings menu action. + This action shows the Settings dialog. """ self.on_show_settings() def show_about(self): - """ - Handle About menu action. + """Handle About menu action. + This action shows the About dialog. """ self.on_show_about() def next_break_time(self, dateTime): - """ - Update the next break time to be displayed in the menu and optionally in the tray icon. + """Update the next break time to be displayed in the menu and + optionally in the tray icon. """ logging.info("Update next break information") self.date_time = dateTime @@ -680,16 +696,13 @@ def get_next_break_time(self): return (formatted_time, None, False) - def on_manual_break_clicked(self, break_type): - """ - Trigger a break manually. - """ + """Trigger a break manually.""" self.take_break(break_type) def on_enable_clicked(self): - """ - Handle 'Enable Safe Eyes' menu action. + """Handle 'Enable Safe Eyes' menu action. + This action enables the application if it is currently disabled. """ if not self.active: @@ -702,68 +715,66 @@ def on_enable_clicked(self): self.idle_condition.release() def on_disable_clicked(self, time_to_wait): - """ - Handle the menu actions of all the sub menus of 'Disable Safe Eyes'. + """Handle the menu actions of all the sub menus of 'Disable Safe Eyes'. + This action disables the application if it is currently active. """ if self.active: self.disable_ui() if time_to_wait <= 0: - info = _('Disabled until restart') + info = _("Disabled until restart") self.disable_safeeyes(info) self.wakeup_time = None else: - self.wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait) - info = _('Disabled until %s') % utility.format_time(self.wakeup_time) + self.wakeup_time = datetime.datetime.now() + datetime.timedelta( + minutes=time_to_wait + ) + info = _("Disabled until %s") % utility.format_time(self.wakeup_time) self.disable_safeeyes(info) utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait) self.update_menu() def lock_menu(self): - """ - This method is called by the core to prevent user from disabling Safe Eyes after the notification. + """This method is called by the core to prevent user from disabling + Safe Eyes after the notification. """ if self.active: self.menu_locked = True self.update_menu() def unlock_menu(self): - """ - This method is called by the core to activate the menu after the the break. + """This method is called by the core to activate the menu after the the + break. """ if self.active: self.menu_locked = False self.update_menu() def disable_ui(self): - """ - Change the UI to disabled state. - """ + """Change the UI to disabled state.""" if self.active: - logging.info('Disable Safe Eyes') + logging.info("Disable Safe Eyes") self.active = False self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled") self.update_menu() def enable_ui(self): - """ - Change the UI to enabled state. - """ + """Change the UI to enabled state.""" if not self.active: - logging.info('Enable Safe Eyes') + logging.info("Enable Safe Eyes") self.active = True self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled") self.update_menu() def __schedule_resume(self, time_minutes): - """ - Schedule a local timer to enable Safe Eyes after the given timeout. + """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.wait(time_minutes * 60) # Convert to seconds self.idle_condition.release() with self.lock: @@ -773,9 +784,13 @@ def __schedule_resume(self, time_minutes): def start_animation(self): if not self.active or not self.animate: return - utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled")) + utility.execute_main_thread( + lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled") + ) time.sleep(0.5) - utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled")) + utility.execute_main_thread( + lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled") + ) if self.animate and self.active: time.sleep(0.5) if self.animate and self.active: @@ -784,18 +799,25 @@ def start_animation(self): def stop_animation(self): self.animate = False if self.active: - utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled")) + utility.execute_main_thread( + lambda: self.sni_service.set_icon( + "io.github.slgobinath.SafeEyes-enabled" + ) + ) else: - utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled")) + utility.execute_main_thread( + lambda: self.sni_service.set_icon( + "io.github.slgobinath.SafeEyes-disabled" + ) + ) + def init(ctx, safeeyes_cfg, plugin_config): - """ - Initialize the tray icon. - """ + """Initialize the tray icon.""" global context global tray_icon global safeeyes_config - logging.debug('Initialize Tray Icon plugin') + logging.debug("Initialize Tray Icon plugin") context = ctx safeeyes_config = safeeyes_cfg if not tray_icon: @@ -805,17 +827,13 @@ def init(ctx, safeeyes_cfg, plugin_config): def update_next_break(break_obj, next_break_time): - """ - Update the next break time. - """ + """Update the next break time.""" tray_icon.next_break_time(next_break_time) def on_pre_break(break_obj): - """ - Disable the menu if strict_break is enabled - """ - if safeeyes_config.get('strict_break'): + """Disable the menu if strict_break is enabled.""" + if safeeyes_config.get("strict_break"): tray_icon.lock_menu() tray_icon.animate = True tray_icon.start_animation() @@ -830,14 +848,10 @@ def on_stop_break(): def on_start(): - """ - Enable the tray icon. - """ + """Enable the tray icon.""" tray_icon.enable_ui() def on_stop(): - """ - Disable the tray icon. - """ + """Disable the tray icon.""" tray_icon.disable_ui() diff --git a/safeeyes/rpc.py b/safeeyes/rpc.py index abe36295..e1398b5e 100644 --- a/safeeyes/rpc.py +++ b/safeeyes/rpc.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -RPC server and client implementation. -""" +"""RPC server and client implementation.""" import logging from threading import Thread @@ -27,87 +25,75 @@ class RPCServer: - """ - An asynchronous RPC server. - """ + """An asynchronous RPC server.""" + def __init__(self, port, context): self.__running = False - logging.info('Setting up an RPC server on port %d', port) - self.__server = SimpleXMLRPCServer(("localhost", port), logRequests=False, allow_none=True) - self.__server.register_function(context['api']['show_settings'], 'show_settings') - self.__server.register_function(context['api']['show_about'], 'show_about') - self.__server.register_function(context['api']['enable_safeeyes'], 'enable_safeeyes') - self.__server.register_function(context['api']['disable_safeeyes'], 'disable_safeeyes') - self.__server.register_function(context['api']['take_break'], 'take_break') - self.__server.register_function(context['api']['status'], 'status') - self.__server.register_function(context['api']['quit'], 'quit') + logging.info("Setting up an RPC server on port %d", port) + self.__server = SimpleXMLRPCServer( + ("localhost", port), logRequests=False, allow_none=True + ) + self.__server.register_function( + context["api"]["show_settings"], "show_settings" + ) + self.__server.register_function(context["api"]["show_about"], "show_about") + self.__server.register_function( + context["api"]["enable_safeeyes"], "enable_safeeyes" + ) + self.__server.register_function( + context["api"]["disable_safeeyes"], "disable_safeeyes" + ) + self.__server.register_function(context["api"]["take_break"], "take_break") + self.__server.register_function(context["api"]["status"], "status") + self.__server.register_function(context["api"]["quit"], "quit") def start(self): - """ - Start the RPC server. - """ + """Start the RPC server.""" if not self.__running: self.__running = True - logging.info('Start the RPC server') + logging.info("Start the RPC server") server_thread = Thread(target=self.__server.serve_forever) server_thread.start() def stop(self): - """ - Stop the server. - """ + """Stop the server.""" if self.__running: - logging.info('Stop the RPC server') + logging.info("Stop the RPC server") self.__running = False self.__server.shutdown() class RPCClient: - """ - An RPC client to communicate with the RPC server. - """ + """An RPC client to communicate with the RPC server.""" + def __init__(self, port): self.port = port - self.proxy = ServerProxy('http://localhost:%d/' % self.port, allow_none=True) + self.proxy = ServerProxy("http://localhost:%d/" % self.port, allow_none=True) def show_settings(self): - """ - Show the settings dialog. - """ + """Show the settings dialog.""" self.proxy.show_settings() def show_about(self): - """ - Show the about dialog. - """ + """Show the about dialog.""" self.proxy.show_about() def enable_safeeyes(self): - """ - Enable Safe Eyes. - """ + """Enable Safe Eyes.""" self.proxy.enable_safeeyes() def disable_safeeyes(self): - """ - Disable Safe Eyes. - """ + """Disable Safe Eyes.""" self.proxy.disable_safeeyes(None) def take_break(self): - """ - Take a break now. - """ + """Take a break now.""" self.proxy.take_break() def status(self): - """ - Return the status of Safe Eyes - """ + """Return the status of Safe Eyes.""" return self.proxy.status() def quit(self): - """ - Quit Safe Eyes. - """ + """Quit Safe Eyes.""" self.proxy.quit() diff --git a/safeeyes/safeeyes.py b/safeeyes/safeeyes.py index 0fb9a9b2..d99c7c2d 100644 --- a/safeeyes/safeeyes.py +++ b/safeeyes/safeeyes.py @@ -16,13 +16,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -SafeEyes connects all the individual components and provide the complete application. +"""SafeEyes connects all the individual components and provide the complete +application. """ import atexit import logging -import os from threading import Timer import gi @@ -36,16 +35,14 @@ from safeeyes.core import SafeEyesCore from safeeyes.ui.settings_dialog import SettingsDialog -gi.require_version('Gtk', '3.0') +gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gio, GLib SAFE_EYES_VERSION = "2.2.2" class SafeEyes(Gtk.Application): - """ - This class represents a runnable Safe Eyes instance. - """ + """This class represents a runnable Safe Eyes instance.""" required_plugin_dialog_active = False retry_errored_plugins_count = 0 @@ -53,7 +50,8 @@ class SafeEyes(Gtk.Application): def __init__(self, system_locale, config, cli_args): super().__init__( application_id="io.github.slgobinath.SafeEyes", - flags=Gio.ApplicationFlags.FLAGS_NONE ## This is necessary for compatibility with Ubuntu 22.04. + # This is necessary for compatibility with Ubuntu 22.04. + flags=Gio.ApplicationFlags.FLAGS_NONE, ) self.active = False self.break_screen = None @@ -63,45 +61,51 @@ def __init__(self, system_locale, config, cli_args): self.plugins_manager = None self.settings_dialog_active = False self.rpc_server = None - self._status = '' + self._status = "" self.cli_args = cli_args self.system_locale = system_locale def start(self): - """ - Start Safe Eyes - """ + """Start Safe Eyes.""" self.run() def do_startup(self): Gtk.Application.do_startup(self) - logging.info('Starting up Application') + logging.info("Starting up Application") # 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'] = self.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, reset_breaks=False: \ - utility.execute_main_thread(self.enable_safeeyes, next_break_time, reset_breaks) - self.context['api']['disable_safeeyes'] = lambda status=None, is_resting=False: utility.execute_main_thread( - self.disable_safeeyes, status, is_resting) - 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() + self.context["version"] = SAFE_EYES_VERSION + self.context["desktop"] = utility.desktop_environment() + self.context["is_wayland"] = utility.is_wayland() + self.context["locale"] = self.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, reset_breaks=False: utility.execute_main_thread( + self.enable_safeeyes, next_break_time, reset_breaks + ) + ) + self.context["api"]["disable_safeeyes"] = ( + lambda status=None, is_resting=False: utility.execute_main_thread( + self.disable_safeeyes, status, is_resting + ) + ) + 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.context["session"] = {"plugin": {}} self.break_screen = BreakScreen( - self.context, self.on_skipped, self.on_postponed, utility.STYLE_SHEET_PATH) + self.context, self.on_skipped, self.on_postponed, utility.STYLE_SHEET_PATH + ) self.break_screen.initialize(self.config) self.plugins_manager = PluginManager() self.safe_eyes_core = SafeEyesCore(self.context) @@ -112,10 +116,10 @@ def do_startup(self): 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'] = self.take_break - self.context['api']['has_breaks'] = self.safe_eyes_core.has_breaks - self.context['api']['postpone'] = self.safe_eyes_core.postpone - self.context['api']['get_break_time'] = self.safe_eyes_core.get_break_time + self.context["api"]["take_break"] = self.take_break + self.context["api"]["has_breaks"] = self.safe_eyes_core.has_breaks + self.context["api"]["postpone"] = self.safe_eyes_core.postpone + self.context["api"]["get_break_time"] = self.safe_eyes_core.get_break_time try: self.plugins_manager.init(self.context, self.config) @@ -126,18 +130,22 @@ def do_startup(self): atexit.register(self.persist_session) - if self.config.get('use_rpc_server', True): + if self.config.get("use_rpc_server", True): self.__start_rpc_server() - if not self.plugins_manager.needs_retry() and not self.required_plugin_dialog_active and self.safe_eyes_core.has_breaks(): + if ( + not self.plugins_manager.needs_retry() + and not self.required_plugin_dialog_active + and self.safe_eyes_core.has_breaks() + ): self.active = True - self.context['state'] = State.START - self.plugins_manager.start() # Call the start method of all plugins + self.context["state"] = State.START + self.plugins_manager.start() # Call the start method of all plugins self.safe_eyes_core.start() self.handle_system_suspend() def do_activate(self): - logging.info('Application activated') + logging.info("Application activated") if self.plugins_manager.needs_retry(): GLib.timeout_add_seconds(1, self._retry_errored_plugins) @@ -153,12 +161,11 @@ def do_activate(self): elif self.cli_args.take_break: self.take_break() - def _retry_errored_plugins(self): if not self.plugins_manager.needs_retry(): return - logging.info(f"Retry loading errored plugin") + logging.info("Retry loading errored plugin") self.plugins_manager.retry_errored_plugins() error = self.plugins_manager.get_retryable_error() @@ -178,60 +185,58 @@ def _retry_errored_plugins(self): GLib.timeout_add_seconds(timeout, self._retry_errored_plugins) - def show_settings(self): - """ - Listen to tray icon Settings action and send the signal to Settings dialog. + """Listen to tray icon Settings action and send the signal to Settings + dialog. """ if not self.settings_dialog_active: logging.info("Show Settings dialog") self.settings_dialog_active = True - settings_dialog = SettingsDialog( - self.config.clone(), self.save_settings) + settings_dialog = SettingsDialog(self.config.clone(), self.save_settings) settings_dialog.show() def show_required_plugin_dialog(self, error: RequiredPluginException): self.required_plugin_dialog_active = True logging.info("Show RequiredPlugin dialog") + plugin_id = error.get_plugin_id() + dialog = RequiredPluginDialog( error.get_plugin_id(), error.get_plugin_name(), error.get_message(), self.quit, - lambda: self.disable_plugin(plugin_id) + lambda: self.disable_plugin(plugin_id), ) dialog.show() def disable_plugin(self, plugin_id): - """ - Temporarily disable plugin, and restart SafeEyes. - """ + """Temporarily disable plugin, and restart SafeEyes.""" config = self.config.clone() - for plugin in config.get('plugins'): - if plugin['id'] == plugin_id: - plugin['enabled'] = False + for plugin in config.get("plugins"): + if plugin["id"] == plugin_id: + plugin["enabled"] = False self.required_plugin_dialog_active = False self.restart(config, set_active=True) def show_about(self): - """ - Listen to tray icon About action and send the signal to About dialog. + """Listen to tray icon About action and send the signal to About + dialog. """ logging.info("Show About dialog") about_dialog = AboutDialog(SAFE_EYES_VERSION) about_dialog.show() def quit(self): - """ - Listen to the tray menu quit action and stop the core, notification and the app itself. + """Listen to the tray menu quit action and stop the core, notification + and the app itself. """ logging.info("Quit Safe Eyes") self.break_screen.close() - self.context['state'] = State.QUIT + self.context["state"] = State.QUIT self.plugins_manager.stop() self.safe_eyes_core.stop() self.plugins_manager.exit() @@ -241,8 +246,9 @@ def quit(self): super().quit() def handle_suspend_callback(self, sleeping): - """ - If the system goes to sleep, Safe Eyes stop the core if it is already active. + """If the system goes to sleep, Safe Eyes stop the core if it is + already active. + If it was active, Safe Eyes will become active after wake up. """ if sleeping: @@ -262,44 +268,40 @@ def handle_suspend_signal(self, proxy, sender, signal, parameters): if signal != "PrepareForSleep": return - (sleeping, ) = parameters + (sleeping,) = parameters self.handle_suspend_callback(sleeping) def handle_system_suspend(self): - """ - Setup system suspend listener. - """ + """Setup system suspend listener.""" self.suspend_proxy = Gio.DBusProxy.new_for_bus_sync( bus_type=Gio.BusType.SYSTEM, flags=Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, info=None, - name='org.freedesktop.login1', - object_path='/org/freedesktop/login1', - interface_name='org.freedesktop.login1.Manager', + name="org.freedesktop.login1", + object_path="/org/freedesktop/login1", + interface_name="org.freedesktop.login1.Manager", cancellable=None, ) - self.suspend_proxy.connect('g-signal', self.handle_suspend_signal) + self.suspend_proxy.connect("g-signal", self.handle_suspend_signal) def on_skipped(self): - """ - Listen to break screen Skip action and send the signal to core. - """ + """Listen to break screen Skip action and send the signal to core.""" logging.info("User skipped the break") self.safe_eyes_core.skip() self.plugins_manager.stop_break() def on_postponed(self): - """ - Listen to break screen Postpone action and send the signal to core. + """Listen to break screen Postpone action and send the signal to + core. """ logging.info("User postponed the break") self.safe_eyes_core.postpone() self.plugins_manager.stop_break() def save_settings(self, config): - """ - Listen to Settings dialog Save action and write to the config file. + """Listen to Settings dialog Save action and write to the config + file. """ self.settings_dialog_active = False @@ -322,10 +324,10 @@ def save_settings(self, config): def restart(self, config, set_active=False): logging.info("Initialize SafeEyesCore with modified settings") - if self.rpc_server is None and config.get('use_rpc_server'): + if self.rpc_server is None and config.get("use_rpc_server"): # RPC server wasn't running but now enabled self.__start_rpc_server() - elif self.rpc_server is not None and not config.get('use_rpc_server'): + elif self.rpc_server is not None and not config.get("use_rpc_server"): # RPC server was running but now disabled self.__stop_rpc_server() @@ -349,95 +351,76 @@ def restart(self, config, set_active=False): self.plugins_manager.start() def enable_safeeyes(self, scheduled_next_break_time=-1, reset_breaks=False): - """ - Listen to tray icon enable action and send the signal to core. - """ - if not self.required_plugin_dialog_active and not self.active and self.safe_eyes_core.has_breaks(): + """Listen to tray icon enable action and send the signal to core.""" + if ( + not self.required_plugin_dialog_active + and not self.active + and self.safe_eyes_core.has_breaks() + ): self.active = True self.safe_eyes_core.start(scheduled_next_break_time, reset_breaks) self.plugins_manager.start() - def disable_safeeyes(self, status=None, is_resting = False): - """ - Listen to tray icon disable action and send the signal to core. - """ + def disable_safeeyes(self, status=None, is_resting=False): + """Listen to tray icon disable action and send the signal to core.""" if self.active: self.active = False self.plugins_manager.stop() self.safe_eyes_core.stop(is_resting) if status is None: - status = _('Disabled until restart') + status = _("Disabled until restart") self._status = status def on_start_break(self, break_obj): - """ - Pass the break information to plugins. - """ + """Pass the break information to plugins.""" if not self.plugins_manager.start_break(break_obj): return False return True def start_break(self, break_obj): - """ - Pass the break information to break screen. - """ + """Pass the break information to break screen.""" # Get the HTML widgets content from plugins widget = self.plugins_manager.get_break_screen_widgets(break_obj) actions = self.plugins_manager.get_break_screen_tray_actions(break_obj) self.break_screen.show_message(break_obj, widget, actions) def countdown(self, countdown, seconds): - """ - Pass the countdown to plugins and break screen. - """ + """Pass the countdown to plugins and break screen.""" self.break_screen.show_count_down(countdown, seconds) self.plugins_manager.countdown(countdown, seconds) return True def update_next_break(self, break_obj, break_time): - """ - Update the next break to plugins and save the session. - """ + """Update the next break to plugins and save the session.""" self.plugins_manager.update_next_break(break_obj, break_time) - self._status = _('Next break at %s') % ( - utility.format_time(break_time)) - if self.config.get('persist_state'): - utility.write_json(utility.SESSION_FILE_PATH, - self.context['session']) + self._status = _("Next break at %s") % (utility.format_time(break_time)) + if self.config.get("persist_state"): + utility.write_json(utility.SESSION_FILE_PATH, self.context["session"]) def stop_break(self): - """ - Stop the current break. - """ + """Stop the current break.""" self.break_screen.close() self.plugins_manager.stop_break() return True - def take_break(self, break_type = None): - """ - Take a break now. - """ + def take_break(self, break_type=None): + """Take a break now.""" utility.execute_main_thread(self.safe_eyes_core.take_break, break_type) def status(self): - """ - Return the status of Safe Eyes. - """ + """Return the status of Safe Eyes.""" return self._status def persist_session(self): - """ - Save the session object to the session file. - """ - if self.config.get('persist_state'): - utility.write_json(utility.SESSION_FILE_PATH, - self.context['session']) + """Save the session object to the session file.""" + if self.config.get("persist_state"): + utility.write_json(utility.SESSION_FILE_PATH, self.context["session"]) else: utility.delete(utility.SESSION_FILE_PATH) def __start_rpc_server(self): if self.rpc_server is None: - self.rpc_server = RPCServer(self.config.get('rpc_port'), self.context) + self.rpc_server = RPCServer(self.config.get("rpc_port"), self.context) self.rpc_server.start() def __stop_rpc_server(self): diff --git a/safeeyes/ui/about_dialog.py b/safeeyes/ui/about_dialog.py index 39362b03..a766ea06 100644 --- a/safeeyes/ui/about_dialog.py +++ b/safeeyes/ui/about_dialog.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -This module creates the AboutDialog which shows the version and license. -""" +"""This module creates the AboutDialog which shows the version and license.""" import os @@ -28,35 +26,36 @@ class AboutDialog: - """ - AboutDialog reads the about_dialog.glade and build the user interface using that file. - It shows the application name with version, a small description, license and the GitHub url. + """AboutDialog reads the about_dialog.glade and build the user interface + using that file. + + It shows the application name with version, a small description, + license and the GitHub url. """ def __init__(self, version): builder = utility.create_gtk_builder(ABOUT_DIALOG_GLADE) builder.connect_signals(self) - self.window = builder.get_object('window_about') - builder.get_object('lbl_decription').set_label(_("Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer")) - builder.get_object('lbl_license').set_label(_('License') + ':') + self.window = builder.get_object("window_about") + builder.get_object("lbl_decription").set_label( + _( + "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding" + " you to take breaks while you're working long hours at the computer" + ) + ) + builder.get_object("lbl_license").set_label(_("License") + ":") # Set the version at the runtime - builder.get_object('lbl_app_name').set_label('Safe Eyes ' + version) + builder.get_object("lbl_app_name").set_label("Safe Eyes " + version) def show(self): - """ - Show the About dialog. - """ + """Show the About dialog.""" self.window.show_all() def on_window_delete(self, *args): - """ - Window close event handler. - """ + """Window close event handler.""" self.window.destroy() def on_close_clicked(self, *args): - """ - Close button click event handler. - """ + """Close button click event handler.""" self.window.destroy() diff --git a/safeeyes/ui/break_screen.py b/safeeyes/ui/break_screen.py index 7546fd83..bd642da6 100644 --- a/safeeyes/ui/break_screen.py +++ b/safeeyes/ui/break_screen.py @@ -19,7 +19,6 @@ import logging import os -import threading import time import gi @@ -27,7 +26,7 @@ from Xlib.display import Display from Xlib.display import X -gi.require_version('Gtk', '3.0') +gi.require_version("Gtk", "3.0") from gi.repository import Gdk from gi.repository import GLib from gi.repository import Gtk @@ -36,9 +35,10 @@ class BreakScreen: - """ - The fullscreen window which prevents users from using the computer. - This class reads the break_screen.glade and build the user interface. + """The fullscreen window which prevents users from using the computer. + + This class reads the break_screen.glade and build the user + interface. """ def __init__(self, context, on_skipped, on_postponed, style_sheet_path): @@ -59,76 +59,67 @@ def __init__(self, context, on_skipped, on_postponed, style_sheet_path): # Initialize the theme css_provider = Gtk.CssProvider() css_provider.load_from_path(style_sheet_path) - Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) def initialize(self, config): - """ - Initialize the internal properties from configuration - """ + """Initialize the internal properties from configuration.""" logging.info("Initialize the break screen") - self.enable_postpone = config.get('allow_postpone', False) - self.keycode_shortcut_postpone = config.get('shortcut_postpone', 65) - self.keycode_shortcut_skip = config.get('shortcut_skip', 9) - self.shortcut_disable_time = config.get('shortcut_disable_time', 2) - self.strict_break = config.get('strict_break', False) + self.enable_postpone = config.get("allow_postpone", False) + self.keycode_shortcut_postpone = config.get("shortcut_postpone", 65) + self.keycode_shortcut_skip = config.get("shortcut_skip", 9) + self.shortcut_disable_time = config.get("shortcut_disable_time", 2) + self.strict_break = config.get("strict_break", False) def skip_break(self): - """ - Skip the break from the break screen - """ + """Skip the break from the break screen.""" logging.info("User skipped the break") - # Must call on_skipped before close to lock screen before closing the break screen + # Must call on_skipped before close to lock screen before closing the break + # screen self.on_skipped() self.close() def postpone_break(self): - """ - Postpone the break from the break screen - """ + """Postpone the break from the break screen.""" logging.info("User postponed the break") self.on_postponed() self.close() def on_window_delete(self, *args): - """ - Window close event handler. - """ + """Window close event handler.""" logging.info("Closing the break screen") self.close() def on_skip_clicked(self, button): - """ - Skip button press event handler. - """ + """Skip button press event handler.""" self.skip_break() def on_postpone_clicked(self, button): - """ - Postpone button press event handler. - """ + """Postpone button press event handler.""" self.postpone_break() def show_count_down(self, countdown, seconds): - """ - Show/update the count down on all screens. - """ + """Show/update the count down on all screens.""" self.enable_shortcut = self.shortcut_disable_time <= seconds mins, secs = divmod(countdown, 60) - timeformat = '{:02d}:{:02d}'.format(mins, secs) + timeformat = "{:02d}:{:02d}".format(mins, secs) GLib.idle_add(lambda: self.__update_count_down(timeformat)) def show_message(self, break_obj, widget, tray_actions=[]): - """ - Show the break screen with the given message on all displays. - """ + """Show the break screen with the given message on all displays.""" message = break_obj.name image_path = break_obj.image self.enable_shortcut = self.shortcut_disable_time <= 0 - GLib.idle_add(lambda: self.__show_break_screen(message, image_path, widget, tray_actions)) + GLib.idle_add( + lambda: self.__show_break_screen(message, image_path, widget, tray_actions) + ) def close(self): - """ - Hide the break screen from active window and destroy all other windows + """Hide the break screen from active window and destroy all other + windows. """ logging.info("Close the break screen(s)") self.__release_keyboard() @@ -137,17 +128,16 @@ def close(self): GLib.idle_add(lambda: self.__destroy_all_screens()) def __tray_action(self, button, tray_action): - """ - Tray action handler. - Hides all toolbar buttons for this action and call the action provided by the plugin. + """Tray action handler. + + Hides all toolbar buttons for this action and call the action + provided by the plugin. """ tray_action.reset() tray_action.action() def __show_break_screen(self, message, image_path, widget, tray_actions): - """ - Show an empty break screen on all screens. - """ + """Show an empty break screen on all screens.""" # Lock the keyboard utility.start_thread(self.__lock_keyboard) @@ -156,8 +146,8 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): no_of_monitors = display.get_n_monitors() logging.info("Show break screens in %d display(s)", no_of_monitors) - skip_button_disabled = self.context.get('skip_button_disabled', False) - postpone_button_disabled = self.context.get('postpone_button_disabled', False) + skip_button_disabled = self.context.get("skip_button_disabled", False) + postpone_button_disabled = self.context.get("postpone_button_disabled", False) for monitor_num in range(no_of_monitors): monitor = display.get_monitor(monitor_num) @@ -181,11 +171,19 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): for tray_action in tray_actions: toolbar_button = None if tray_action.system_icon: - toolbar_button = Gtk.ToolButton.new_from_stock(tray_action.get_icon()) + toolbar_button = Gtk.ToolButton.new_from_stock( + tray_action.get_icon() + ) else: - toolbar_button = Gtk.ToolButton.new(tray_action.get_icon(), tray_action.name) + toolbar_button = Gtk.ToolButton.new( + tray_action.get_icon(), tray_action.name + ) tray_action.add_toolbar_button(toolbar_button) - toolbar_button.connect("clicked", lambda button, action: self.__tray_action(button, action), tray_action) + toolbar_button.connect( + "clicked", + lambda button, action: self.__tray_action(button, action), + tray_action, + ) toolbar_button.set_tooltip_text(_(tray_action.name)) toolbar.add(toolbar_button) toolbar_button.show() @@ -193,17 +191,17 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): # Add the buttons if self.enable_postpone and not postpone_button_disabled: # Add postpone button - btn_postpone = Gtk.Button.new_with_label(_('Postpone')) - btn_postpone.get_style_context().add_class('btn_postpone') - btn_postpone.connect('clicked', self.on_postpone_clicked) + btn_postpone = Gtk.Button.new_with_label(_("Postpone")) + btn_postpone.get_style_context().add_class("btn_postpone") + btn_postpone.connect("clicked", self.on_postpone_clicked) btn_postpone.set_visible(True) box_buttons.pack_start(btn_postpone, True, True, 0) if not self.strict_break and not skip_button_disabled: # Add the skip button - btn_skip = Gtk.Button.new_with_label(_('Skip')) - btn_skip.get_style_context().add_class('btn_skip') - btn_skip.connect('clicked', self.on_skip_clicked) + btn_skip = Gtk.Button.new_with_label(_("Skip")) + btn_skip.get_style_context().add_class("btn_skip") + btn_skip.connect("clicked", self.on_skip_clicked) btn_skip.set_visible(True) box_buttons.pack_start(btn_skip, True, True, 0) @@ -218,7 +216,7 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): # Set visual to apply css theme. It should be called before show method. window.set_visual(window.get_screen().get_rgba_visual()) - if self.context['desktop'] == 'kde': + if self.context["desktop"] == "kde": # Fix flickering screen in KDE by setting opacity to 1 window.set_opacity(0.9) @@ -235,15 +233,13 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): logging.info("Moved break screen to Display[%d, %d]", x, y) def __update_count_down(self, count): - """ - Update the countdown on all break screens. - """ + """Update the countdown on all break screens.""" for label in self.count_labels: label.set_text(count) def __lock_keyboard(self): - """ - Lock the keyboard to prevent the user from using keyboard shortcuts + """Lock the keyboard to prevent the user from using keyboard + shortcuts. """ logging.info("Lock the keyboard") self.lock_keyboard = True @@ -259,10 +255,16 @@ def __lock_keyboard(self): # Avoid waiting for next event by checking pending events event = self.display.next_event() if self.enable_shortcut and event.type == X.KeyPress: - if event.detail == self.keycode_shortcut_skip and not self.strict_break: + if ( + event.detail == self.keycode_shortcut_skip + and not self.strict_break + ): self.skip_break() break - elif self.enable_postpone and event.detail == self.keycode_shortcut_postpone: + elif ( + self.enable_postpone + and event.detail == self.keycode_shortcut_postpone + ): self.postpone_break() break else: @@ -270,18 +272,14 @@ def __lock_keyboard(self): time.sleep(1) def __release_keyboard(self): - """ - Release the locked keyboard. - """ + """Release the locked keyboard.""" logging.info("Unlock the keyboard") self.lock_keyboard = False self.display.ungrab_keyboard(X.CurrentTime) self.display.flush() def __destroy_all_screens(self): - """ - Close all the break screens. - """ + """Close all the break screens.""" for win in self.windows: win.destroy() del self.windows[:] diff --git a/safeeyes/ui/required_plugin_dialog.py b/safeeyes/ui/required_plugin_dialog.py index debe53b3..a345b7c3 100644 --- a/safeeyes/ui/required_plugin_dialog.py +++ b/safeeyes/ui/required_plugin_dialog.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -This module creates the RequiredPluginDialog which shows the error for a required plugin. +"""This module creates the RequiredPluginDialog which shows the error for a +required plugin. """ import os @@ -25,12 +25,14 @@ from safeeyes import utility from safeeyes.model import PluginDependency -REQUIRED_PLUGIN_DIALOG_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/required_plugin_dialog.glade") +REQUIRED_PLUGIN_DIALOG_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/required_plugin_dialog.glade" +) class RequiredPluginDialog: - """ - RequiredPluginDialog shows an error when a plugin has required dependencies. + """RequiredPluginDialog shows an error when a plugin has required + dependencies. """ def __init__(self, plugin_id, plugin_name, message, on_quit, on_disable_plugin): @@ -38,38 +40,45 @@ def __init__(self, plugin_id, plugin_name, message, on_quit, on_disable_plugin): self.on_disable_plugin = on_disable_plugin builder = utility.create_gtk_builder(REQUIRED_PLUGIN_DIALOG_GLADE) - self.window = builder.get_object('window_required_plugin') + self.window = builder.get_object("window_required_plugin") self.window.connect("delete-event", self.on_window_delete) - builder.get_object('btn_close').connect('clicked', self.on_close_clicked) - builder.get_object('btn_disable_plugin').connect('clicked', self.on_disable_plugin_clicked) - - builder.get_object('lbl_header').set_label(_("The required plugin '%s' is missing dependencies!") % _(plugin_name)) - - builder.get_object('lbl_main').set_label(_("Please install the dependencies or disable the plugin. To hide this message, you can also deactivate the plugin in the settings.")) - - builder.get_object('btn_close').set_label(_("Quit")) - builder.get_object('btn_disable_plugin').set_label(_("Disable plugin temporarily")) + builder.get_object("btn_close").connect("clicked", self.on_close_clicked) + builder.get_object("btn_disable_plugin").connect( + "clicked", self.on_disable_plugin_clicked + ) + + builder.get_object("lbl_header").set_label( + _("The required plugin '%s' is missing dependencies!") % _(plugin_name) + ) + + builder.get_object("lbl_main").set_label( + _( + "Please install the dependencies or disable the plugin. To hide this" + " message, you can also deactivate the plugin in the settings." + ) + ) + + builder.get_object("btn_close").set_label(_("Quit")) + builder.get_object("btn_disable_plugin").set_label( + _("Disable plugin temporarily") + ) if isinstance(message, PluginDependency): - builder.get_object('lbl_message').set_label(_(message.message)) - btn_extra_link = builder.get_object('btn_extra_link') + builder.get_object("lbl_message").set_label(_(message.message)) + btn_extra_link = builder.get_object("btn_extra_link") btn_extra_link.set_label(_("Click here for more information")) btn_extra_link.set_uri(message.link) btn_extra_link.set_visible(True) else: - builder.get_object('lbl_message').set_label(_(message)) + builder.get_object("lbl_message").set_label(_(message)) def show(self): - """ - Show the dialog. - """ + """Show the dialog.""" self.window.show_all() def on_window_delete(self, *args): - """ - Window close event handler. - """ + """Window close event handler.""" self.window.destroy() self.on_quit() diff --git a/safeeyes/ui/settings_dialog.py b/safeeyes/ui/settings_dialog.py index 62f54f4b..8eff861e 100644 --- a/safeeyes/ui/settings_dialog.py +++ b/safeeyes/ui/settings_dialog.py @@ -23,33 +23,43 @@ from safeeyes import utility from safeeyes.model import Config, PluginDependency -gi.require_version('Gtk', '3.0') +gi.require_version("Gtk", "3.0") from gi.repository import Gtk from gi.repository import GdkPixbuf -SETTINGS_DIALOG_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/settings_dialog.glade") -SETTINGS_DIALOG_PLUGIN_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/settings_plugin.glade") -SETTINGS_DIALOG_BREAK_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/settings_break.glade") -SETTINGS_DIALOG_NEW_BREAK_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/new_break.glade") -SETTINGS_BREAK_ITEM_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/item_break.glade") -SETTINGS_PLUGIN_ITEM_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/item_plugin.glade") +SETTINGS_DIALOG_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/settings_dialog.glade" +) +SETTINGS_DIALOG_PLUGIN_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/settings_plugin.glade" +) +SETTINGS_DIALOG_BREAK_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/settings_break.glade" +) +SETTINGS_DIALOG_NEW_BREAK_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/new_break.glade" +) +SETTINGS_BREAK_ITEM_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/item_break.glade" +) +SETTINGS_PLUGIN_ITEM_GLADE = os.path.join( + utility.BIN_DIRECTORY, "glade/item_plugin.glade" +) SETTINGS_ITEM_INT_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/item_int.glade") SETTINGS_ITEM_TEXT_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/item_text.glade") SETTINGS_ITEM_BOOL_GLADE = os.path.join(utility.BIN_DIRECTORY, "glade/item_bool.glade") class SettingsDialog: - """ - Create and initialize SettingsDialog instance. - """ + """Create and initialize SettingsDialog instance.""" def __init__(self, config, on_save_settings): self.config = config self.on_save_settings = on_save_settings self.plugin_switches = {} self.plugin_map = {} - self.last_short_break_interval = config.get('short_break_interval') + self.last_short_break_interval = config.get("short_break_interval") self.initializing = True self.infobar_long_break_shown = False self.warn_bar_rpc_server_shown = False @@ -57,24 +67,26 @@ def __init__(self, config, on_save_settings): builder = utility.create_gtk_builder(SETTINGS_DIALOG_GLADE) builder.connect_signals(self) - self.window = builder.get_object('window_settings') - self.box_short_breaks = builder.get_object('box_short_breaks') - self.box_long_breaks = builder.get_object('box_long_breaks') - self.box_plugins = builder.get_object('box_plugins') - self.popover = builder.get_object('popover') - - self.spin_short_break_duration = builder.get_object('spin_short_break_duration') - self.spin_long_break_duration = builder.get_object('spin_long_break_duration') - self.spin_short_break_interval = builder.get_object('spin_short_break_interval') - self.spin_long_break_interval = builder.get_object('spin_long_break_interval') - self.spin_time_to_prepare = builder.get_object('spin_time_to_prepare') - self.spin_postpone_duration = builder.get_object('spin_postpone_duration') - self.spin_disable_keyboard_shortcut = builder.get_object('spin_disable_keyboard_shortcut') - self.switch_strict_break = builder.get_object('switch_strict_break') - self.switch_random_order = builder.get_object('switch_random_order') - self.switch_postpone = builder.get_object('switch_postpone') - self.switch_persist = builder.get_object('switch_persist') - self.switch_rpc_server = builder.get_object('switch_rpc_server') + self.window = builder.get_object("window_settings") + self.box_short_breaks = builder.get_object("box_short_breaks") + self.box_long_breaks = builder.get_object("box_long_breaks") + self.box_plugins = builder.get_object("box_plugins") + self.popover = builder.get_object("popover") + + self.spin_short_break_duration = builder.get_object("spin_short_break_duration") + self.spin_long_break_duration = builder.get_object("spin_long_break_duration") + self.spin_short_break_interval = builder.get_object("spin_short_break_interval") + self.spin_long_break_interval = builder.get_object("spin_long_break_interval") + self.spin_time_to_prepare = builder.get_object("spin_time_to_prepare") + self.spin_postpone_duration = builder.get_object("spin_postpone_duration") + self.spin_disable_keyboard_shortcut = builder.get_object( + "spin_disable_keyboard_shortcut" + ) + self.switch_strict_break = builder.get_object("switch_strict_break") + self.switch_random_order = builder.get_object("switch_random_order") + self.switch_postpone = builder.get_object("switch_postpone") + self.switch_persist = builder.get_object("switch_persist") + self.switch_rpc_server = builder.get_object("switch_rpc_server") self.info_bar_long_break = builder.get_object("info_bar_long_break") self.warn_bar_rpc_server = builder.get_object("warn_bar_rpc_server") self.info_bar_long_break.hide() @@ -87,69 +99,79 @@ def __init__(self, config, on_save_settings): # GtkSwitch state-set signal is available only from 3.14 if Gtk.get_minor_version() >= 14: # Add event listener to postpone switch - self.switch_postpone.connect('state-set', self.on_switch_postpone_activate) - self.on_switch_postpone_activate(self.switch_postpone, self.switch_postpone.get_active()) + self.switch_postpone.connect("state-set", self.on_switch_postpone_activate) + self.on_switch_postpone_activate( + self.switch_postpone, self.switch_postpone.get_active() + ) # Add event listener to RPC server switch - self.switch_rpc_server.connect('state-set', self.on_switch_rpc_server_activate) - self.on_switch_rpc_server_activate(self.switch_rpc_server, self.switch_rpc_server.get_active()) + self.switch_rpc_server.connect( + "state-set", self.on_switch_rpc_server_activate + ) + self.on_switch_rpc_server_activate( + self.switch_rpc_server, self.switch_rpc_server.get_active() + ) self.initializing = False 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'): + for short_break in config.get("short_breaks"): self.__create_break_item(short_break, True) - for long_break in config.get('long_breaks'): + 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_random_order.set_active(config.get('random_order')) - self.switch_postpone.set_active(config.get('allow_postpone')) - self.switch_persist.set_active(config.get('persist_state')) - self.switch_rpc_server.set_active(config.get('use_rpc_server')) + 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_random_order.set_active(config.get("random_order")) + self.switch_postpone.set_active(config.get("allow_postpone")) + self.switch_persist.set_active(config.get("persist_state")) + self.switch_rpc_server.set_active(config.get("use_rpc_server")) self.infobar_long_break_shown = False def __create_break_item(self, break_config, is_short): - """ - Create an entry for break to be listed in the break tab. - """ + """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') + 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', + "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) - ) + 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 = builder.get_object("btn_delete") btn_delete.connect( - 'clicked', + "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) @@ -157,15 +179,22 @@ def __create_break_item(self, break_config, is_short): def on_reset_menu_clicked(self, button): self.popover.hide() + 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)) + 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)) + self.box_plugins.foreach( + lambda element: self.box_plugins.remove(element) + ) # Initialize again self.__initialize(self.config) widget.destroy() @@ -173,57 +202,57 @@ def __confirmation_dialog_response(widget, response_id): messagedialog = Gtk.MessageDialog() messagedialog.set_modal(True) messagedialog.set_transient_for(self.window) - messagedialog.set_property('message_type', Gtk.MessageType.WARNING) - messagedialog.set_property('text', _("Are you sure you want to reset all settings to default?")) - messagedialog.set_property('secondary-text', _("You can't undo this action.")) - messagedialog.add_button('_Cancel', Gtk.ResponseType.CANCEL) + messagedialog.set_property("message_type", Gtk.MessageType.WARNING) + messagedialog.set_property( + "text", _("Are you sure you want to reset all settings to default?") + ) + messagedialog.set_property("secondary-text", _("You can't undo this action.")) + messagedialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) messagedialog.add_button(_("Reset"), Gtk.ResponseType.OK) messagedialog.connect("response", __confirmation_dialog_response) messagedialog.show() def __delete_break(self, break_config, is_short, on_remove): - """ - Remove the break after a confirmation. - """ + """Remove the break after a confirmation.""" def __confirmation_dialog_response(widget, response_id): if response_id == Gtk.ResponseType.OK: if is_short: - self.config.get('short_breaks').remove(break_config) + self.config.get("short_breaks").remove(break_config) else: - self.config.get('long_breaks').remove(break_config) + self.config.get("long_breaks").remove(break_config) on_remove() widget.destroy() messagedialog = Gtk.MessageDialog() messagedialog.set_modal(True) messagedialog.set_transient_for(self.window) - messagedialog.set_property('message_type', Gtk.MessageType.WARNING) - messagedialog.set_property('text', _("Are you sure you want to delete this break?")) - messagedialog.set_property('secondary-text', _("You can't undo this action.")) - messagedialog.add_button('_Cancel', Gtk.ResponseType.CANCEL) + messagedialog.set_property("message_type", Gtk.MessageType.WARNING) + messagedialog.set_property( + "text", _("Are you sure you want to delete this break?") + ) + messagedialog.set_property("secondary-text", _("You can't undo this action.")) + messagedialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) messagedialog.add_button(_("Delete"), Gtk.ResponseType.OK) messagedialog.connect("response", __confirmation_dialog_response) messagedialog.show() def __create_plugin_item(self, plugin_config): - """ - Create an entry for plugin to be listed in the plugin tab. - """ + """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']: - message = plugin_config['meta']['dependency_description'] + 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"]: + message = plugin_config["meta"]["dependency_description"] if isinstance(message, PluginDependency): lbl_plugin_description.set_label(_(message.message)) - btn_plugin_extra_link = builder.get_object('btn_plugin_extra_link') + btn_plugin_extra_link = builder.get_object("btn_plugin_extra_link") btn_plugin_extra_link.set_label(_("Click here for more information")) btn_plugin_extra_link.set_uri(message.link) btn_plugin_extra_link.set_visible(True) @@ -233,93 +262,96 @@ def __create_plugin_item(self, plugin_config): lbl_plugin_description.set_sensitive(False) switch_enable.set_sensitive(False) btn_properties.set_sensitive(False) - if plugin_config['enabled']: - btn_disable_errored = builder.get_object('btn_disable_errored') + if plugin_config["enabled"]: + btn_disable_errored = builder.get_object("btn_disable_errored") btn_disable_errored.set_visible(True) - btn_disable_errored.connect('clicked', lambda button: self.__disable_errored_plugin(button, plugin_config)) + btn_disable_errored.connect( + "clicked", + lambda button: self.__disable_errored_plugin(button, plugin_config), + ) else: - lbl_plugin_description.set_label(_(plugin_config['meta']['description'])) - if plugin_config['settings']: + lbl_plugin_description.set_label(_(plugin_config["meta"]["description"])) + if plugin_config["settings"]: btn_properties.set_sensitive(True) - btn_properties.connect('clicked', lambda button: self.__show_plugins_properties_dialog(plugin_config)) + btn_properties.connect( + "clicked", + lambda button: self.__show_plugins_properties_dialog(plugin_config), + ) else: btn_properties.set_sensitive(False) - 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']) - box = builder.get_object('box') + 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"]) + box = builder.get_object("box") box.set_visible(True) return box def __show_plugins_properties_dialog(self, plugin_config): - """ - Show the PluginProperties dialog - """ + """Show the PluginProperties dialog.""" dialog = PluginSettingsDialog(plugin_config) dialog.show() def __disable_errored_plugin(self, button, plugin_config): - """ - Permanently disable errored plugin - """ + """Permanently disable errored plugin.""" button.set_sensitive(False) - self.plugin_switches[plugin_config['id']].set_active(False) - - def __show_break_properties_dialog(self, break_config, is_short, parent, on_close, on_add, on_remove): - """ - Show the BreakProperties dialog - """ - dialog = BreakSettingsDialog(break_config, is_short, parent, self.plugin_map, on_close, on_add, on_remove) + self.plugin_switches[plugin_config["id"]].set_active(False) + + def __show_break_properties_dialog( + self, break_config, is_short, parent, on_close, on_add, on_remove + ): + """Show the BreakProperties dialog.""" + dialog = BreakSettingsDialog( + break_config, is_short, parent, self.plugin_map, on_close, on_add, on_remove + ) dialog.show() def show(self): - """ - Show the SettingsDialog. - """ + """Show the SettingsDialog.""" self.window.show() def on_switch_postpone_activate(self, switch, state): - """ - Event handler to the state change of the postpone switch. - Enable or disable the self.spin_postpone_duration based on the state of the postpone switch. + """Event handler to the state change of the postpone switch. + + Enable or disable the self.spin_postpone_duration based on the + state of the postpone switch. """ self.spin_postpone_duration.set_sensitive(self.switch_postpone.get_active()) def on_spin_short_break_interval_change(self, spin_button, *value): - """ - Event handler for value change of short break interval. - """ + """Event handler for value change of short break interval.""" short_break_interval = self.spin_short_break_interval.get_value_as_int() long_break_interval = self.spin_long_break_interval.get_value_as_int() self.spin_long_break_interval.set_range(short_break_interval * 2, 120) - self.spin_long_break_interval.set_increments(short_break_interval, short_break_interval * 2) - self.spin_long_break_interval.set_value(short_break_interval * math.ceil(long_break_interval / self.last_short_break_interval)) + self.spin_long_break_interval.set_increments( + short_break_interval, short_break_interval * 2 + ) + self.spin_long_break_interval.set_value( + short_break_interval + * math.ceil(long_break_interval / self.last_short_break_interval) + ) self.last_short_break_interval = short_break_interval if not self.initializing and not self.infobar_long_break_shown: self.infobar_long_break_shown = True self.info_bar_long_break.show() def on_spin_long_break_interval_change(self, spin_button, *value): - """ - Event handler for value change of long break interval. - """ + """Event handler for value change of long break interval.""" if not self.initializing and not self.infobar_long_break_shown: self.infobar_long_break_shown = True self.info_bar_long_break.show() def on_info_bar_long_break_close(self, infobar, *user_data): - """ - Event handler for info bar close action. - """ + """Event handler for info bar close action.""" self.info_bar_long_break.hide() def on_switch_rpc_server_activate(self, switch, enabled): - """ - Event handler to the state change of the rpc server switch. - Show or hide the self.warn_bar_rpc_server based on the state of the rpc server. + """Event handler to the state change of the rpc server switch. + + Show or hide the self.warn_bar_rpc_server based on the state of + the rpc server. """ if not self.initializing and not enabled and not self.warn_bar_rpc_server_shown: self.warn_bar_rpc_server_shown = True @@ -328,46 +360,58 @@ def on_switch_rpc_server_activate(self, switch, enabled): self.warn_bar_rpc_server.hide() def on_warn_bar_rpc_server_close(self, warnbar, *user_data): - """ - Event handler for warning bar close action. - """ + """Event handler for warning bar close action.""" self.warn_bar_rpc_server.hide() def add_break(self, button): - """ - Event handler for add break button. - """ - dialog = NewBreakDialog(self.config, lambda is_short, break_config: self.__create_break_item(break_config, is_short)) + """Event handler for add break button.""" + dialog = NewBreakDialog( + self.config, + lambda is_short, break_config: self.__create_break_item( + break_config, is_short + ), + ) dialog.show() def on_window_delete(self, *args): - """ - Event handler for Settings dialog close action. - """ - self.config.set('short_break_duration', self.spin_short_break_duration.get_value_as_int()) - self.config.set('long_break_duration', self.spin_long_break_duration.get_value_as_int()) - self.config.set('short_break_interval', self.spin_short_break_interval.get_value_as_int()) - self.config.set('long_break_interval', self.spin_long_break_interval.get_value_as_int()) - self.config.set('pre_break_warning_time', self.spin_time_to_prepare.get_value_as_int()) - self.config.set('postpone_duration', self.spin_postpone_duration.get_value_as_int()) - self.config.set('shortcut_disable_time', self.spin_disable_keyboard_shortcut.get_value_as_int()) - self.config.set('strict_break', self.switch_strict_break.get_active()) - self.config.set('random_order', self.switch_random_order.get_active()) - self.config.set('allow_postpone', self.switch_postpone.get_active()) - self.config.set('persist_state', self.switch_persist.get_active()) - self.config.set('use_rpc_server', self.switch_rpc_server.get_active()) - for plugin in self.config.get('plugins'): - if plugin['id'] in self.plugin_switches: - plugin['enabled'] = self.plugin_switches[plugin['id']].get_active() - - self.on_save_settings(self.config) # Call the provided save method + """Event handler for Settings dialog close action.""" + self.config.set( + "short_break_duration", self.spin_short_break_duration.get_value_as_int() + ) + self.config.set( + "long_break_duration", self.spin_long_break_duration.get_value_as_int() + ) + self.config.set( + "short_break_interval", self.spin_short_break_interval.get_value_as_int() + ) + self.config.set( + "long_break_interval", self.spin_long_break_interval.get_value_as_int() + ) + self.config.set( + "pre_break_warning_time", self.spin_time_to_prepare.get_value_as_int() + ) + self.config.set( + "postpone_duration", self.spin_postpone_duration.get_value_as_int() + ) + self.config.set( + "shortcut_disable_time", + self.spin_disable_keyboard_shortcut.get_value_as_int(), + ) + self.config.set("strict_break", self.switch_strict_break.get_active()) + self.config.set("random_order", self.switch_random_order.get_active()) + self.config.set("allow_postpone", self.switch_postpone.get_active()) + self.config.set("persist_state", self.switch_persist.get_active()) + self.config.set("use_rpc_server", self.switch_rpc_server.get_active()) + for plugin in self.config.get("plugins"): + if plugin["id"] in self.plugin_switches: + plugin["enabled"] = self.plugin_switches[plugin["id"]].get_active() + + self.on_save_settings(self.config) # Call the provided save method self.window.destroy() class PluginSettingsDialog: - """ - Builds a settings dialog based on the configuration of a plugin. - """ + """Builds a settings dialog based on the configuration of a plugin.""" def __init__(self, config): self.config = config @@ -375,78 +419,108 @@ def __init__(self, config): 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) + 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 __load_int_item(self, name, key, settings, min_value, max_value): - """ - Load the UI control for int property. - """ + """Load the UI control for int property.""" builder = utility.create_gtk_builder(SETTINGS_ITEM_INT_GLADE) - builder.get_object('lbl_name').set_label(_(name)) - spin_value = builder.get_object('spin_value') + builder.get_object("lbl_name").set_label(_(name)) + spin_value = builder.get_object("spin_value") spin_value.set_range(min_value, max_value) spin_value.set_value(settings[key]) - box = builder.get_object('box') + box = builder.get_object("box") box.set_visible(True) - self.property_controls.append({'key': key, 'settings': settings, 'value': spin_value.get_value}) + self.property_controls.append( + {"key": key, "settings": settings, "value": spin_value.get_value} + ) return box def __load_text_item(self, name, key, settings): - """ - Load the UI control for text property. - """ + """Load the UI control for text property.""" builder = utility.create_gtk_builder(SETTINGS_ITEM_TEXT_GLADE) - builder.get_object('lbl_name').set_label(_(name)) - txt_value = builder.get_object('txt_value') + builder.get_object("lbl_name").set_label(_(name)) + txt_value = builder.get_object("txt_value") txt_value.set_text(settings[key]) - box = builder.get_object('box') + box = builder.get_object("box") box.set_visible(True) - self.property_controls.append({'key': key, 'settings': settings, 'value': txt_value.get_text}) + self.property_controls.append( + {"key": key, "settings": settings, "value": txt_value.get_text} + ) return box def __load_bool_item(self, name, key, settings): - """ - Load the UI control for boolean property. - """ + """Load the UI control for boolean property.""" builder = utility.create_gtk_builder(SETTINGS_ITEM_BOOL_GLADE) - builder.get_object('lbl_name').set_label(_(name)) - switch_value = builder.get_object('switch_value') + builder.get_object("lbl_name").set_label(_(name)) + switch_value = builder.get_object("switch_value") switch_value.set_active(settings[key]) - box = builder.get_object('box') + box = builder.get_object("box") box.set_visible(True) - self.property_controls.append({'key': key, 'settings': settings, 'value': switch_value.get_active}) + self.property_controls.append( + {"key": key, "settings": settings, "value": switch_value.get_active} + ) return box def on_window_delete(self, *args): - """ - Event handler for Properties dialog close action. - """ + """Event handler for Properties dialog close action.""" for property_control in self.property_controls: - property_control['settings'][property_control['key']] = property_control['value']() + property_control["settings"][property_control["key"]] = property_control[ + "value" + ]() self.window.destroy() def show(self): - """ - Show the Properties dialog. - """ + """Show the Properties dialog.""" self.window.show_all() class BreakSettingsDialog: - """ - Builds a settings dialog based on the configuration of a plugin. - """ - - def __init__(self, break_config, is_short, parent_config, plugin_map, on_close, on_add, on_remove): + """Builds a settings dialog based on the configuration of a plugin.""" + + def __init__( + self, + break_config, + is_short, + parent_config, + plugin_map, + on_close, + on_add, + on_remove, + ): self.break_config = break_config self.parent_config = parent_config self.plugin_check_buttons = {} @@ -457,26 +531,26 @@ def __init__(self, break_config, is_short, parent_config, plugin_map, on_close, builder = utility.create_gtk_builder(SETTINGS_DIALOG_BREAK_GLADE) builder.connect_signals(self) - self.window = builder.get_object('dialog_settings_break') - self.txt_break = builder.get_object('txt_break') - self.switch_override_interval = builder.get_object('switch_override_interval') - self.switch_override_duration = builder.get_object('switch_override_duration') - self.switch_override_plugins = builder.get_object('switch_override_plugins') - self.spin_interval = builder.get_object('spin_interval') - self.spin_duration = builder.get_object('spin_duration') - self.img_break = builder.get_object('img_break') - self.cmb_type = builder.get_object('cmb_type') - - grid_plugins = builder.get_object('grid_plugins') - list_types = builder.get_object('lst_break_types') - - interval_overriden = break_config.get('interval', None) is not None - duration_overriden = break_config.get('duration', None) is not None - plugins_overriden = break_config.get('plugins', None) is not None + self.window = builder.get_object("dialog_settings_break") + self.txt_break = builder.get_object("txt_break") + self.switch_override_interval = builder.get_object("switch_override_interval") + self.switch_override_duration = builder.get_object("switch_override_duration") + self.switch_override_plugins = builder.get_object("switch_override_plugins") + self.spin_interval = builder.get_object("spin_interval") + self.spin_duration = builder.get_object("spin_duration") + self.img_break = builder.get_object("img_break") + self.cmb_type = builder.get_object("cmb_type") + + grid_plugins = builder.get_object("grid_plugins") + list_types = builder.get_object("lst_break_types") + + interval_overriden = break_config.get("interval", None) is not None + duration_overriden = break_config.get("duration", None) is not None + plugins_overriden = break_config.get("plugins", None) is not None # Set the values - self.window.set_title(_('Break Settings')) - self.txt_break.set_text(_(break_config['name'])) + self.window.set_title(_("Break Settings")) + self.txt_break.set_text(_(break_config["name"])) self.switch_override_interval.set_active(interval_overriden) self.switch_override_duration.set_active(duration_overriden) self.switch_override_plugins.set_active(plugins_overriden) @@ -485,20 +559,20 @@ def __init__(self, break_config, is_short, parent_config, plugin_map, on_close, list_types[1][0] = _(list_types[1][0]) if interval_overriden: - self.spin_interval.set_value(break_config['interval']) + self.spin_interval.set_value(break_config["interval"]) else: if is_short: - self.spin_interval.set_value(parent_config.get('short_break_interval')) + self.spin_interval.set_value(parent_config.get("short_break_interval")) else: - self.spin_interval.set_value(parent_config.get('long_break_interval')) + self.spin_interval.set_value(parent_config.get("long_break_interval")) if duration_overriden: - self.spin_duration.set_value(break_config['duration']) + self.spin_duration.set_value(break_config["duration"]) else: if is_short: - self.spin_duration.set_value(parent_config.get('short_break_duration')) + self.spin_duration.set_value(parent_config.get("short_break_duration")) else: - self.spin_duration.set_value(parent_config.get('long_break_duration')) + self.spin_duration.set_value(parent_config.get("long_break_duration")) row = 0 col = 0 for plugin_id in plugin_map.keys(): @@ -506,7 +580,7 @@ def __init__(self, break_config, is_short, parent_config, plugin_map, on_close, self.plugin_check_buttons[plugin_id] = chk_button grid_plugins.attach(chk_button, row, col, 1, 1) if plugins_overriden: - chk_button.set_active(plugin_id in break_config['plugins']) + chk_button.set_active(plugin_id in break_config["plugins"]) else: chk_button.set_active(True) row += 1 @@ -515,37 +589,53 @@ def __init__(self, break_config, is_short, parent_config, plugin_map, on_close, row = 0 # GtkSwitch state-set signal is available only from 3.14 if Gtk.get_minor_version() >= 14: - self.switch_override_interval.connect('state-set', self.on_switch_override_interval_activate) - self.switch_override_duration.connect('state-set', self.on_switch_override_duration_activate) - self.switch_override_plugins.connect('state-set', self.on_switch_override_plugins_activate) - self.on_switch_override_interval_activate(self.switch_override_interval, self.switch_override_interval.get_active()) - self.on_switch_override_duration_activate(self.switch_override_duration, self.switch_override_duration.get_active()) - self.on_switch_override_plugins_activate(self.switch_override_plugins, self.switch_override_plugins.get_active()) + self.switch_override_interval.connect( + "state-set", self.on_switch_override_interval_activate + ) + self.switch_override_duration.connect( + "state-set", self.on_switch_override_duration_activate + ) + self.switch_override_plugins.connect( + "state-set", self.on_switch_override_plugins_activate + ) + self.on_switch_override_interval_activate( + self.switch_override_interval, + self.switch_override_interval.get_active(), + ) + self.on_switch_override_duration_activate( + self.switch_override_duration, + self.switch_override_duration.get_active(), + ) + self.on_switch_override_plugins_activate( + self.switch_override_plugins, self.switch_override_plugins.get_active() + ) def on_switch_override_interval_activate(self, switch_button, state): - """ - switch_override_interval state change event handler. - """ + """switch_override_interval state change event handler.""" self.spin_interval.set_sensitive(state) def on_switch_override_duration_activate(self, switch_button, state): - """ - switch_override_duration state change event handler. - """ + """switch_override_duration state change event handler.""" self.spin_duration.set_sensitive(state) def on_switch_override_plugins_activate(self, switch_button, state): - """ - switch_override_plugins state change event handler. - """ + """switch_override_plugins state change event handler.""" for chk_box in self.plugin_check_buttons.values(): chk_box.set_sensitive(state) def select_image(self, button): - """ - Show a file chooser dialog and let the user to select an image. - """ - dialog = Gtk.FileChooserDialog(_('Please select an image'), self.window, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) + """Show a file chooser dialog and let the user to select an image.""" + dialog = Gtk.FileChooserDialog( + _("Please select an image"), + self.window, + Gtk.FileChooserAction.OPEN, + ( + Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, + Gtk.ResponseType.OK, + ), + ) png_filter = Gtk.FileFilter() png_filter.set_name("PNG files") @@ -555,49 +645,49 @@ def select_image(self, button): response = dialog.run() if response == Gtk.ResponseType.OK: - self.break_config['image'] = dialog.get_filename() - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.break_config['image'], 16, 16, True) + self.break_config["image"] = dialog.get_filename() + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( + self.break_config["image"], 16, 16, True + ) self.img_break.set_from_pixbuf(pixbuf) elif response == Gtk.ResponseType.CANCEL: - self.break_config.pop('image', None) - self.img_break.set_from_stock('gtk-missing-image', Gtk.IconSize.BUTTON) + self.break_config.pop("image", None) + self.img_break.set_from_stock("gtk-missing-image", Gtk.IconSize.BUTTON) dialog.destroy() def on_window_delete(self, *args): - """ - Event handler for Properties dialog close action. - """ + """Event handler for Properties dialog close action.""" break_name = self.txt_break.get_text().strip() if break_name: - self.break_config['name'] = break_name + self.break_config["name"] = break_name if self.switch_override_interval.get_active(): - self.break_config['interval'] = int(self.spin_interval.get_value()) + self.break_config["interval"] = int(self.spin_interval.get_value()) else: - self.break_config.pop('interval', None) + self.break_config.pop("interval", None) if self.switch_override_duration.get_active(): - self.break_config['duration'] = int(self.spin_duration.get_value()) + self.break_config["duration"] = int(self.spin_duration.get_value()) else: - self.break_config.pop('duration', None) + self.break_config.pop("duration", None) if self.switch_override_plugins.get_active(): selected_plugins = [] for plugin_id in self.plugin_check_buttons: if self.plugin_check_buttons[plugin_id].get_active(): selected_plugins.append(plugin_id) - self.break_config['plugins'] = selected_plugins + self.break_config["plugins"] = selected_plugins else: - self.break_config.pop('plugins', None) + self.break_config.pop("plugins", None) if self.is_short and self.cmb_type.get_active() == 1: # Changed from short to long - self.parent_config.get('short_breaks').remove(self.break_config) - self.parent_config.get('long_breaks').append(self.break_config) + self.parent_config.get("short_breaks").remove(self.break_config) + self.parent_config.get("long_breaks").append(self.break_config) self.on_remove() self.on_add(not self.is_short, self.break_config) elif not self.is_short and self.cmb_type.get_active() == 0: # Changed from long to short - self.parent_config.get('long_breaks').remove(self.break_config) - self.parent_config.get('short_breaks').append(self.break_config) + self.parent_config.get("long_breaks").remove(self.break_config) + self.parent_config.get("short_breaks").append(self.break_config) self.on_remove() self.on_add(not self.is_short, self.break_config) else: @@ -605,16 +695,12 @@ def on_window_delete(self, *args): self.window.destroy() def show(self): - """ - Show the Properties dialog. - """ + """Show the Properties dialog.""" self.window.show_all() class NewBreakDialog: - """ - Builds a new break dialog. - """ + """Builds a new break dialog.""" def __init__(self, parent_config, on_add): self.parent_config = parent_config @@ -622,45 +708,37 @@ def __init__(self, parent_config, on_add): builder = utility.create_gtk_builder(SETTINGS_DIALOG_NEW_BREAK_GLADE) builder.connect_signals(self) - self.window = builder.get_object('dialog_new_break') - self.txt_break = builder.get_object('txt_break') - self.cmb_type = builder.get_object('cmb_type') - list_types = builder.get_object('lst_break_types') + self.window = builder.get_object("dialog_new_break") + self.txt_break = builder.get_object("txt_break") + self.cmb_type = builder.get_object("cmb_type") + list_types = builder.get_object("lst_break_types") list_types[0][0] = _(list_types[0][0]) list_types[1][0] = _(list_types[1][0]) # Set the values - self.window.set_title(_('New Break')) + self.window.set_title(_("New Break")) def discard(self, button): - """ - Close the dialog. - """ + """Close the dialog.""" self.window.destroy() def save(self, button): - """ - Event handler for Properties dialog close action. - """ - break_config = {'name': self.txt_break.get_text().strip()} + """Event handler for Properties dialog close action.""" + break_config = {"name": self.txt_break.get_text().strip()} if self.cmb_type.get_active() == 0: - self.parent_config.get('short_breaks').append(break_config) + self.parent_config.get("short_breaks").append(break_config) self.on_add(True, break_config) else: - self.parent_config.get('long_breaks').append(break_config) + self.parent_config.get("long_breaks").append(break_config) self.on_add(False, break_config) self.window.destroy() def on_window_delete(self, *args): - """ - Event handler for dialog close action. - """ + """Event handler for dialog close action.""" self.window.destroy() def show(self): - """ - Show the Properties dialog. - """ + """Show the Properties dialog.""" self.window.show_all() diff --git a/safeeyes/utility.py b/safeeyes/utility.py index bcb0fc26..a1d477b8 100644 --- a/safeeyes/utility.py +++ b/safeeyes/utility.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -This module contains utility functions for Safe Eyes and its plugins. -""" +"""This module contains utility functions for Safe Eyes and its plugins.""" import errno import inspect @@ -38,47 +36,52 @@ import babel.core import babel.dates import gi -gi.require_version('Gtk', '3.0') + +gi.require_version("Gtk", "3.0") from gi.repository import Gtk from gi.repository import GLib from gi.repository import GdkPixbuf from packaging.version import parse -gi.require_version('Gdk', '3.0') +gi.require_version("Gdk", "3.0") BIN_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) -HOME_DIRECTORY = os.environ.get('HOME') or os.path.expanduser('~') -CONFIG_DIRECTORY = os.path.join(os.environ.get( - 'XDG_CONFIG_HOME') or os.path.join(HOME_DIRECTORY, '.config'), 'safeeyes') -STYLE_SHEET_DIRECTORY = os.path.join(CONFIG_DIRECTORY, 'style') -CONFIG_FILE_PATH = os.path.join(CONFIG_DIRECTORY, 'safeeyes.json') -CONFIG_RESOURCE = os.path.join(CONFIG_DIRECTORY, 'resource') -SESSION_FILE_PATH = os.path.join(CONFIG_DIRECTORY, 'session.json') -STYLE_SHEET_PATH = os.path.join(STYLE_SHEET_DIRECTORY, 'safeeyes_style.css') +HOME_DIRECTORY = os.environ.get("HOME") or os.path.expanduser("~") +CONFIG_DIRECTORY = os.path.join( + os.environ.get("XDG_CONFIG_HOME") or os.path.join(HOME_DIRECTORY, ".config"), + "safeeyes", +) +STYLE_SHEET_DIRECTORY = os.path.join(CONFIG_DIRECTORY, "style") +CONFIG_FILE_PATH = os.path.join(CONFIG_DIRECTORY, "safeeyes.json") +CONFIG_RESOURCE = os.path.join(CONFIG_DIRECTORY, "resource") +SESSION_FILE_PATH = os.path.join(CONFIG_DIRECTORY, "session.json") +STYLE_SHEET_PATH = os.path.join(STYLE_SHEET_DIRECTORY, "safeeyes_style.css") SYSTEM_CONFIG_FILE_PATH = os.path.join(BIN_DIRECTORY, "config/safeeyes.json") -SYSTEM_STYLE_SHEET_PATH = os.path.join( - BIN_DIRECTORY, "config/style/safeeyes_style.css") -LOG_FILE_PATH = os.path.join(HOME_DIRECTORY, 'safeeyes.log') -SYSTEM_PLUGINS_DIR = os.path.join(BIN_DIRECTORY, 'plugins') -USER_PLUGINS_DIR = os.path.join(CONFIG_DIRECTORY, 'plugins') -LOCALE_PATH = os.path.join(BIN_DIRECTORY, 'config/locale') -SYSTEM_DESKTOP_FILE = os.path.join(BIN_DIRECTORY, "platform/io.github.slgobinath.SafeEyes.desktop") +SYSTEM_STYLE_SHEET_PATH = os.path.join(BIN_DIRECTORY, "config/style/safeeyes_style.css") +LOG_FILE_PATH = os.path.join(HOME_DIRECTORY, "safeeyes.log") +SYSTEM_PLUGINS_DIR = os.path.join(BIN_DIRECTORY, "plugins") +USER_PLUGINS_DIR = os.path.join(CONFIG_DIRECTORY, "plugins") +LOCALE_PATH = os.path.join(BIN_DIRECTORY, "config/locale") +SYSTEM_DESKTOP_FILE = os.path.join( + BIN_DIRECTORY, "platform/io.github.slgobinath.SafeEyes.desktop" +) SYSTEM_ICONS = os.path.join(BIN_DIRECTORY, "platform/icons") DESKTOP_ENVIRONMENT = None IS_WAYLAND = False def get_resource_path(resource_name): - """ - Return the user-defined resource if a system resource is overridden by the user. - Otherwise, return the system resource. Return None if the specified resource does not exist. + """Return the user-defined resource if a system resource is overridden by + the user. + + Otherwise, return the system resource. Return None if the specified + resource does not exist. """ if resource_name is None: return None resource_location = os.path.join(CONFIG_RESOURCE, resource_name) if not os.path.isfile(resource_location): - resource_location = os.path.join( - BIN_DIRECTORY, 'resource', resource_name) + resource_location = os.path.join(BIN_DIRECTORY, "resource", resource_name) if not os.path.isfile(resource_location): # Resource not found resource_location = None @@ -87,70 +90,59 @@ def get_resource_path(resource_name): def start_thread(target_function, **args): - """ - Execute the function in a separate thread. - """ - thread = threading.Thread(target=target_function, name="WorkThread", daemon=False, kwargs=args) + """Execute the function in a separate thread.""" + thread = threading.Thread( + target=target_function, name="WorkThread", daemon=False, kwargs=args + ) thread.start() -# def execute_main_thread(target_function, *args, **kwargs): -# """ -# Execute the given function in main thread, forwarding positional and keyword arguments. -# """ - def execute_main_thread(target_function, *args, **kwargs): - """ - Execute the given function in main thread. - """ + """Execute the given function in main thread.""" GLib.idle_add(lambda: target_function(*args, **kwargs)) + def system_locale(category=locale.LC_MESSAGES): - """ - Return the system locale. If not available, return en_US.UTF-8. + """Return the system locale. + + If not available, return en_US.UTF-8. """ try: - locale.setlocale(locale.LC_ALL, '') + locale.setlocale(locale.LC_ALL, "") sys_locale = locale.getlocale(category)[0] if not sys_locale: - sys_locale = 'en_US.UTF-8' + sys_locale = "en_US.UTF-8" return sys_locale except BaseException: # Some systems does not return proper locale - return 'en_US.UTF-8' + return "en_US.UTF-8" def format_time(time): - """ - Format time based on the system time. - """ + """Format time based on the system time.""" sys_locale = system_locale(locale.LC_TIME) try: - return babel.dates.format_time(time, format='short', locale=sys_locale) + return babel.dates.format_time(time, format="short", locale=sys_locale) except babel.core.UnknownLocaleError: # Some locale types are not supported by the babel library. # Use 'en' locale format if the system locale is not supported. - return babel.dates.format_time(time, format='short', locale='en') + return babel.dates.format_time(time, format="short", locale="en") def mkdir(path): - """ - Create directory if not exists. - """ + """Create directory if not exists.""" try: os.makedirs(path) except OSError as exc: if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: - logging.error('Error while creating ' + str(path)) + logging.error("Error while creating " + str(path)) raise def load_json(json_path): - """ - Load the JSON file from the given path. - """ + """Load the JSON file from the given path.""" json_obj = None if os.path.isfile(json_path): try: @@ -162,20 +154,16 @@ def load_json(json_path): def write_json(json_path, json_obj): - """ - Write the JSON object at the given path - """ + """Write the JSON object at the given path.""" try: - with open(json_path, 'w') as json_file: + with open(json_path, "w") as json_file: json.dump(json_obj, json_file, indent=4, sort_keys=True) except BaseException: pass def delete(file_path): - """ - Delete the given file or directory - """ + """Delete the given file or directory.""" try: os.remove(file_path) except OSError: @@ -183,33 +171,44 @@ def delete(file_path): def check_plugin_dependencies(plugin_id, plugin_config, plugin_settings, plugin_path): - """ - Check the plugin dependencies. - """ + """Check the plugin dependencies.""" # Check the desktop environment - if plugin_config['dependencies']['desktop_environments']: + if plugin_config["dependencies"]["desktop_environments"]: # Plugin has restrictions on desktop environments - if DESKTOP_ENVIRONMENT not in plugin_config['dependencies']['desktop_environments']: - return _('Plugin does not support %s desktop environment') % DESKTOP_ENVIRONMENT + if ( + DESKTOP_ENVIRONMENT + not in plugin_config["dependencies"]["desktop_environments"] + ): + return ( + _("Plugin does not support %s desktop environment") + % DESKTOP_ENVIRONMENT + ) # Check the Python modules - for module in plugin_config['dependencies']['python_modules']: + for module in plugin_config["dependencies"]["python_modules"]: if not module_exist(module): return _("Please install the Python module '%s'") % module # Check the shell commands - for command in plugin_config['dependencies']['shell_commands']: + for command in plugin_config["dependencies"]["shell_commands"]: if not command_exist(command): return _("Please install the command-line tool '%s'") % command # Check the resources - for resource in plugin_config['dependencies']['resources']: + for resource in plugin_config["dependencies"]["resources"]: if get_resource_path(resource) is None: - return _('Please add the resource %(resource)s to %(config_resource)s directory') % {'resource': resource, 'config_resource': CONFIG_RESOURCE} - - plugin_dependency_checker = os.path.join(plugin_path, 'dependency_checker.py') + return _( + "Please add the resource %(resource)s to %(config_resource)s directory" + ) % { + "resource": resource, + "config_resource": CONFIG_RESOURCE, + } + + plugin_dependency_checker = os.path.join(plugin_path, "dependency_checker.py") if os.path.isfile(plugin_dependency_checker): - dependency_checker = importlib.import_module((plugin_id + '.dependency_checker')) + dependency_checker = importlib.import_module( + (plugin_id + ".dependency_checker") + ) if dependency_checker and hasattr(dependency_checker, "validate"): return dependency_checker.validate(plugin_config, plugin_settings) @@ -217,75 +216,99 @@ def check_plugin_dependencies(plugin_id, plugin_config, plugin_settings, plugin_ def load_plugins_config(safeeyes_config): - """ - Load all the plugins from the given directory. - """ + """Load all the plugins from the given directory.""" configs = [] - for plugin in safeeyes_config.get('plugins'): - plugin_path = os.path.join(SYSTEM_PLUGINS_DIR, plugin['id']) + for plugin in safeeyes_config.get("plugins"): + plugin_path = os.path.join(SYSTEM_PLUGINS_DIR, plugin["id"]) if not os.path.isdir(plugin_path): # User plugin - plugin_path = os.path.join(USER_PLUGINS_DIR, plugin['id']) - plugin_config_path = os.path.join(plugin_path, 'config.json') - plugin_icon_path = os.path.join(plugin_path, 'icon.png') - plugin_module_path = os.path.join(plugin_path, 'plugin.py') + plugin_path = os.path.join(USER_PLUGINS_DIR, plugin["id"]) + plugin_config_path = os.path.join(plugin_path, "config.json") + plugin_icon_path = os.path.join(plugin_path, "icon.png") + plugin_module_path = os.path.join(plugin_path, "plugin.py") if not os.path.isfile(plugin_module_path): return icon = None if os.path.isfile(plugin_icon_path): icon = plugin_icon_path else: - icon = get_resource_path('ic_plugin.png') + icon = get_resource_path("ic_plugin.png") config = load_json(plugin_config_path) if config is None: continue - dependency_description = check_plugin_dependencies(plugin['id'], config, plugin.get('settings', {}), plugin_path) + dependency_description = check_plugin_dependencies( + plugin["id"], config, plugin.get("settings", {}), plugin_path + ) if dependency_description: - config['error'] = True - config['meta']['dependency_description'] = dependency_description - icon = get_resource_path('ic_warning.png') + config["error"] = True + config["meta"]["dependency_description"] = dependency_description + icon = get_resource_path("ic_warning.png") else: - config['error'] = False - config['id'] = plugin['id'] - config['icon'] = icon - config['enabled'] = plugin['enabled'] - for setting in config['settings']: - setting['safeeyes_config'] = plugin['settings'] + config["error"] = False + config["id"] = plugin["id"] + config["icon"] = icon + config["enabled"] = plugin["enabled"] + for setting in config["settings"]: + setting["safeeyes_config"] = plugin["settings"] configs.append(config) return configs def desktop_environment(): - """ - Detect the desktop environment. - """ + """Detect the desktop environment.""" global DESKTOP_ENVIRONMENT - desktop_session = os.environ.get('DESKTOP_SESSION') - current_desktop = os.environ.get('XDG_CURRENT_DESKTOP') - env = 'unknown' + desktop_session = os.environ.get("DESKTOP_SESSION") + current_desktop = os.environ.get("XDG_CURRENT_DESKTOP") + env = "unknown" if desktop_session is not None: desktop_session = desktop_session.lower() - if desktop_session in ['gnome', 'unity', 'budgie-desktop', 'cinnamon', 'mate', 'xfce4', 'lxde', 'pantheon', 'fluxbox', 'blackbox', 'openbox', 'icewm', 'jwm', 'afterstep', 'trinity', 'kde']: + if desktop_session in [ + "gnome", + "unity", + "budgie-desktop", + "cinnamon", + "mate", + "xfce4", + "lxde", + "pantheon", + "fluxbox", + "blackbox", + "openbox", + "icewm", + "jwm", + "afterstep", + "trinity", + "kde", + ]: env = desktop_session - elif desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop): - env = 'xfce' - elif desktop_session.startswith('lubuntu'): - env = 'lxde' - elif 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true': - env = 'kde' - elif os.environ.get('GNOME_DESKTOP_SESSION_ID') or desktop_session.startswith('gnome'): - env = 'gnome' - elif desktop_session.startswith('ubuntu'): - env = 'unity' + elif desktop_session.startswith("xubuntu") or ( + current_desktop is not None and "xfce" in current_desktop + ): + env = "xfce" + elif desktop_session.startswith("lubuntu"): + env = "lxde" + elif ( + "plasma" in desktop_session + or desktop_session.startswith("kubuntu") + or os.environ.get("KDE_FULL_SESSION") == "true" + ): + env = "kde" + elif os.environ.get("GNOME_DESKTOP_SESSION_ID") or desktop_session.startswith( + "gnome" + ): + env = "gnome" + elif desktop_session.startswith("ubuntu"): + env = "unity" elif current_desktop is not None: - if current_desktop.startswith('sway'): - env = 'sway' + if current_desktop.startswith("sway"): + env = "sway" DESKTOP_ENVIRONMENT = env return env + def is_wayland(): - """ - Determine if Wayland is running + """Determine if Wayland is running. + https://unix.stackexchange.com/a/325972/222290 """ global IS_WAYLAND @@ -297,22 +320,20 @@ def is_wayland(): return IS_WAYLAND try: - session_id = subprocess.check_output(['loginctl']).split(b'\n')[1].split()[0] + session_id = subprocess.check_output(["loginctl"]).split(b"\n")[1].split()[0] output = subprocess.check_output( - ['loginctl', 'show-session', session_id, '-p', 'Type'] + ["loginctl", "show-session", session_id, "-p", "Type"] ) except BaseException: - logging.warning('Unable to determine if wayland is running. Assuming no.') + logging.warning("Unable to determine if wayland is running. Assuming no.") IS_WAYLAND = False else: - IS_WAYLAND = bool(re.search(b'wayland', output, re.IGNORECASE)) + IS_WAYLAND = bool(re.search(b"wayland", output, re.IGNORECASE)) return IS_WAYLAND def execute_command(command, args=[]): - """ - Execute the shell command without waiting for its response. - """ + """Execute the shell command without waiting for its response.""" if command: command_to_execute = [] if isinstance(command, str): @@ -324,22 +345,18 @@ def execute_command(command, args=[]): try: subprocess.Popen(command_to_execute) except BaseException: - logging.error('Error in executing the command ' + str(command)) + logging.error("Error in executing the command " + str(command)) def command_exist(command): - """ - Check whether the given command exist in the system or not. - """ + """Check whether the given command exist in the system or not.""" if shutil.which(command): return True return False def module_exist(module): - """ - Check wther the given Python module exists or not. - """ + """Check wther the given Python module exists or not.""" try: importlib.util.find_spec(module) return True @@ -348,20 +365,18 @@ def module_exist(module): def merge_configs(new_config, old_config): - """ - Merge the values of old_config into the new_config. - """ + """Merge the values of old_config into the new_config.""" new_config = new_config.copy() new_config.update(old_config) return new_config def initialize_safeeyes(): + """Create the config file and style sheet in XDG_CONFIG_HOME(or + ~/.config)/safeeyes directory. """ - Create the config file and style sheet in XDG_CONFIG_HOME(or ~/.config)/safeeyes directory. - """ - logging.info('Copy the config files to XDG_CONFIG_HOME(or ~/.config)/safeeyes') - + logging.info("Copy the config files to XDG_CONFIG_HOME(or ~/.config)/safeeyes") + # Remove the ~/.config/safeeyes/safeeyes.json file delete(CONFIG_FILE_PATH) @@ -374,9 +389,13 @@ def initialize_safeeyes(): create_user_stylesheet_if_missing() - # initialize_safeeyes gets called when the configuration file is not present, which happens just after installation or manual deletion of .config/safeeyes/safeeyes.json file. In these cases, we want to force the creation of a startup entry + # initialize_safeeyes gets called when the configuration file is not present, which + # happens just after installation or manual deletion of + # .config/safeeyes/safeeyes.json file. In these cases, we want to force the creation + # of a startup entry create_startup_entry(force=True) + def create_user_stylesheet_if_missing(): # Create the XDG_CONFIG_HOME(or ~/.config)/safeeyes/style directory if not os.path.isdir(STYLE_SHEET_DIRECTORY): @@ -387,15 +406,16 @@ def create_user_stylesheet_if_missing(): shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) os.chmod(STYLE_SHEET_PATH, 0o666) + def create_startup_entry(force=False): - """ - Create start up entry. - """ - startup_dir_path = os.path.join(HOME_DIRECTORY, '.config/autostart') - startup_entry = os.path.join(startup_dir_path, 'io.github.slgobinath.SafeEyes.desktop') + """Create start up entry.""" + startup_dir_path = os.path.join(HOME_DIRECTORY, ".config/autostart") + startup_entry = os.path.join( + startup_dir_path, "io.github.slgobinath.SafeEyes.desktop" + ) # until SafeEyes 2.1.5 the startup entry had another name - # https://github.com/slgobinath/SafeEyes/commit/684d16265a48794bb3fd670da67283fe4e2f591b#diff-0863348c2143a4928518a4d3661f150ba86d042bf5320b462ea2e960c36ed275L398 - obsolete_entry = os.path.join(startup_dir_path, 'safeeyes.desktop') + # https://github.com/slgobinath/SafeEyes/commit/684d16265a48794bb3fd670da67283fe4e2f591b#diff-0863348c2143a4928518a4d3661f150ba86d042bf5320b462ea2e960c36ed275L398 + obsolete_entry = os.path.join(startup_dir_path, "safeeyes.desktop") create_link = False @@ -403,14 +423,16 @@ def create_startup_entry(force=False): # if force is True, just create the link create_link = True else: - # if force is False, we want to avoid creating the startup symlink if it was manually deleted by the user, we want to create it only if a broken one is found + # if force is False, we want to avoid creating the startup symlink if it was + # manually deleted by the user, we want to create it only if a broken one is + # found if os.path.islink(startup_entry): # if the link exists, check if it is broken try: os.stat(startup_entry) except FileNotFoundError: # a FileNotFoundError will get thrown if the startup symlink is broken - create_link = True + create_link = True if os.path.islink(obsolete_entry): # if a link with the old naming exists, delete it and create a new one @@ -432,23 +454,27 @@ def create_startup_entry(force=False): def initialize_platform(): - """ - Copy icons and generate desktop entries. - """ + """Copy icons and generate desktop entries.""" logging.debug("Initialize the platform") - applications_dir_path = os.path.join(HOME_DIRECTORY, '.local/share/applications') - icons_dir_path = os.path.join(HOME_DIRECTORY, '.local/share/icons') - desktop_entry = os.path.join(applications_dir_path, 'io.github.slgobinath.SafeEyes.desktop') + applications_dir_path = os.path.join(HOME_DIRECTORY, ".local/share/applications") + icons_dir_path = os.path.join(HOME_DIRECTORY, ".local/share/icons") + desktop_entry = os.path.join( + applications_dir_path, "io.github.slgobinath.SafeEyes.desktop" + ) # Create the folder if not exist mkdir(icons_dir_path) # Create a desktop entry - if not os.path.exists(os.path.join(sys.prefix, "share/applications/io.github.slgobinath.SafeEyes.desktop")): + if not os.path.exists( + os.path.join( + sys.prefix, "share/applications/io.github.slgobinath.SafeEyes.desktop" + ) + ): # Create the folder if not exist mkdir(applications_dir_path) - + # Remove existing file delete(desktop_entry) @@ -459,17 +485,21 @@ def initialize_platform(): logging.error("Failed to create desktop entry at %s" % desktop_entry) # Add links for all icons - for (path, _, filenames) in os.walk(SYSTEM_ICONS): + for path, _, filenames in os.walk(SYSTEM_ICONS): for filename in filenames: system_icon = os.path.join(path, filename) - local_icon = os.path.join(icons_dir_path, os.path.relpath(system_icon, SYSTEM_ICONS)) - global_icon = os.path.join(sys.prefix, "share/icons", os.path.relpath(system_icon, SYSTEM_ICONS)) + local_icon = os.path.join( + icons_dir_path, os.path.relpath(system_icon, SYSTEM_ICONS) + ) + global_icon = os.path.join( + sys.prefix, "share/icons", os.path.relpath(system_icon, SYSTEM_ICONS) + ) parent_dir = str(Path(local_icon).parent) - + if os.path.exists(global_icon): # This icon is already added to the /usr/share/icons/hicolor folder continue - + # Create the directory if not exists mkdir(parent_dir) @@ -491,7 +521,7 @@ def reset_config(): # Copy the safeeyes.json and safeeyes_style.css shutil.copy2(SYSTEM_CONFIG_FILE_PATH, CONFIG_FILE_PATH) shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) - + # Add write permission (e.g. if original file was stored in /nix/store) os.chmod(CONFIG_FILE_PATH, 0o666) os.chmod(STYLE_SHEET_PATH, 0o666) @@ -500,28 +530,28 @@ def reset_config(): def replace_style_sheet(): - """ - Replace the user style sheet by system style sheet. - """ + """Replace the user style sheet by system style sheet.""" delete(STYLE_SHEET_PATH) shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) os.chmod(STYLE_SHEET_PATH, 0o666) def initialize_logging(debug): - """ - Initialize the logging framework using the Safe Eyes specific configurations. + """Initialize the logging framework using the Safe Eyes specific + configurations. """ # Configure logging. root_logger = logging.getLogger() log_formatter = logging.Formatter( - '%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s') + "%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s" + ) # Append the logs and overwrite once reached 1MB if debug: # Log to file file_handler = RotatingFileHandler( - LOG_FILE_PATH, maxBytes=1024 * 1024, backupCount=5, encoding=None, delay=0) + LOG_FILE_PATH, maxBytes=1024 * 1024, backupCount=5, encoding=None, delay=0 + ) file_handler.setFormatter(log_formatter) # Log to console console_handler = logging.StreamHandler() @@ -535,11 +565,9 @@ def initialize_logging(debug): def __open_plugin_config(plugins_dir, plugin_id): - """ - Open the given plugin's configuration. - """ - plugin_config_path = os.path.join(plugins_dir, plugin_id, 'config.json') - plugin_module_path = os.path.join(plugins_dir, plugin_id, 'plugin.py') + """Open the given plugin's configuration.""" + plugin_config_path = os.path.join(plugins_dir, plugin_id, "config.json") + plugin_module_path = os.path.join(plugins_dir, plugin_id, "plugin.py") if not os.path.isfile(plugin_config_path) or not os.path.isfile(plugin_module_path): # Either the config.json or plugin.py is not available return None @@ -547,52 +575,48 @@ def __open_plugin_config(plugins_dir, plugin_id): def __update_plugin_config(plugin, plugin_config, config): - """ - Update the plugin configuration. - """ + """Update the plugin configuration.""" if plugin_config is None: - config['plugins'].remove(plugin) + config["plugins"].remove(plugin) else: - if parse(plugin.get('version', '0.0.0')) != parse(plugin_config['meta']['version']): + if parse(plugin.get("version", "0.0.0")) != parse( + plugin_config["meta"]["version"] + ): # Update the configuration - plugin['version'] = plugin_config['meta']['version'] + plugin["version"] = plugin_config["meta"]["version"] setting_ids = [] # Add the new settings - for setting in plugin_config['settings']: - setting_ids.append(setting['id']) - if 'settings' not in plugin: - plugin['settings'] = {} - if plugin['settings'].get(setting['id'], None) is None: - plugin['settings'][setting['id']] = setting['default'] + for setting in plugin_config["settings"]: + setting_ids.append(setting["id"]) + if "settings" not in plugin: + plugin["settings"] = {} + if plugin["settings"].get(setting["id"], None) is None: + plugin["settings"][setting["id"]] = setting["default"] # Remove the removed ids keys_to_remove = [] - for key in plugin.get('settings', []): + for key in plugin.get("settings", []): if key not in setting_ids: keys_to_remove.append(key) for key in keys_to_remove: - del plugin['settings'][key] + del plugin["settings"][key] def __add_plugin_config(plugin_id, plugin_config, safe_eyes_config): - """ - """ if plugin_config is None: return config = {} - config['id'] = plugin_id - config['enabled'] = False # By default plugins are disabled - config['version'] = plugin_config['meta']['version'] - if plugin_config['settings']: - config['settings'] = {} - for setting in plugin_config['settings']: - config['settings'][setting['id']] = setting['default'] - safe_eyes_config['plugins'].append(config) + config["id"] = plugin_id + config["enabled"] = False # By default plugins are disabled + config["version"] = plugin_config["meta"]["version"] + if plugin_config["settings"]: + config["settings"] = {} + for setting in plugin_config["settings"]: + config["settings"][setting["id"]] = setting["default"] + safe_eyes_config["plugins"].append(config) def merge_plugins(config): - """ - Merge plugin configurations with Safe Eyes configuration. - """ + """Merge plugin configurations with Safe Eyes configuration.""" system_plugins = None user_plugins = None @@ -609,8 +633,8 @@ def merge_plugins(config): user_plugins = [] # Create a list of existing plugins - for plugin in config['plugins']: - plugin_id = plugin['id'] + for plugin in config["plugins"]: + plugin_id = plugin["id"] if plugin_id in system_plugins: plugin_config = __open_plugin_config(SYSTEM_PLUGINS_DIR, plugin_id) __update_plugin_config(plugin, plugin_config, config) @@ -620,7 +644,7 @@ def merge_plugins(config): __update_plugin_config(plugin, plugin_config, config) user_plugins.remove(plugin_id) else: - config['plugins'].remove(plugin) + config["plugins"].remove(plugin) # Add all system plugins for plugin_id in system_plugins: @@ -634,23 +658,19 @@ def merge_plugins(config): def open_session(): - """ - Open the last session. - """ - logging.info('Reading the session file') + """Open the last session.""" + logging.info("Reading the session file") session = load_json(SESSION_FILE_PATH) if session is None: - session = {'plugin': {}} + session = {"plugin": {}} return session def create_gtk_builder(glade_file): - """ - Create a Gtk builder and load the glade file. - """ + """Create a Gtk builder and load the glade file.""" builder = Gtk.Builder() - builder.set_translation_domain('safeeyes') + builder.set_translation_domain("safeeyes") builder.add_from_file(glade_file) # Tranlslate all sub components for obj in builder.get_objects(): @@ -669,18 +689,14 @@ def load_and_scale_image(path, width, height): if not os.path.isfile(path): return None pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( - filename=path, - width=width, - height=height, - preserve_aspect_ratio=True) + filename=path, width=width, height=height, preserve_aspect_ratio=True + ) image = Gtk.Image.new_from_pixbuf(pixbuf) return image def has_method(module, method_name, no_of_args=0): - """ - Check whether the given function is defined in the module or not. - """ + """Check whether the given function is defined in the module or not.""" if hasattr(module, method_name): if len(inspect.getfullargspec(getattr(module, method_name)).args) == no_of_args: return True @@ -688,8 +704,6 @@ def has_method(module, method_name, no_of_args=0): def remove_if_exists(list_of_items, item): - """ - Remove the item from the list_of_items it it exists. - """ + """Remove the item from the list_of_items it it exists.""" if item in list_of_items: list_of_items.remove(item) diff --git a/setup.py b/setup.py index 480c3ab4..0d0faba3 100644 --- a/setup.py +++ b/setup.py @@ -1,86 +1,136 @@ -import os, sys, site +import os +import sys import subprocess import setuptools -requires = [ - 'babel', - 'psutil', - 'croniter', - 'PyGObject', - 'packaging', - 'python-xlib' -] +requires = ["babel", "psutil", "croniter", "PyGObject", "packaging", "python-xlib"] _ROOT = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(_ROOT, 'README.md')) as f: +with open(os.path.join(_ROOT, "README.md")) as f: long_description = f.read() def __compile_po_files(): - """ - Compile the *.po trainslation files. - """ - localedir = 'safeeyes/config/locale' - po_dirs = [localedir + '/' + l + '/LC_MESSAGES/' - for l in next(os.walk(localedir))[1]] + """Compile the *.po trainslation files.""" + localedir = "safeeyes/config/locale" + po_dirs = [ + localedir + "/" + d + "/LC_MESSAGES/" for d in next(os.walk(localedir))[1] + ] for po_dir in po_dirs: - po_files = [f - for f in next(os.walk(po_dir))[2] - if os.path.splitext(f)[1] == '.po'] + po_files = [ + f for f in next(os.walk(po_dir))[2] if os.path.splitext(f)[1] == ".po" + ] for po_file in po_files: filename, _ = os.path.splitext(po_file) - mo_file = filename + '.mo' - msgfmt_cmd = 'msgfmt {} -o {}'.format( - po_dir + po_file, po_dir + mo_file) + mo_file = filename + ".mo" + msgfmt_cmd = "msgfmt {} -o {}".format(po_dir + po_file, po_dir + mo_file) subprocess.call(msgfmt_cmd, shell=True) def __data_files(): - """ - Collect the data files. - """ + """Collect the data files.""" root_dir = sys.prefix - return [(os.path.join(root_dir, "share/applications"), ["safeeyes/platform/io.github.slgobinath.SafeEyes.desktop"]), - (os.path.join(root_dir, "share/icons/hicolor/24x24/status"), ["safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-enabled.png", "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-timer.png"]), - (os.path.join(root_dir, "share/icons/hicolor/24x24/apps"), ["safeeyes/platform/icons/hicolor/24x24/apps/io.github.slgobinath.SafeEyes.png"]), - (os.path.join(root_dir, "share/icons/hicolor/16x16/status"), ["safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-enabled.png", "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-timer.png"]), - (os.path.join(root_dir, "share/icons/hicolor/16x16/apps"), ["safeeyes/platform/icons/hicolor/16x16/apps/io.github.slgobinath.SafeEyes.png"]), - (os.path.join(root_dir, "share/icons/hicolor/32x32/status"), ["safeeyes/platform/icons/hicolor/32x32/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/32x32/status/io.github.slgobinath.SafeEyes-enabled.png"]), - (os.path.join(root_dir, "share/icons/hicolor/32x32/apps"), ["safeeyes/platform/icons/hicolor/32x32/apps/io.github.slgobinath.SafeEyes.png"]), - (os.path.join(root_dir, "share/icons/hicolor/64x64/apps"), ["safeeyes/platform/icons/hicolor/64x64/apps/io.github.slgobinath.SafeEyes.png"]), - (os.path.join(root_dir, "share/icons/hicolor/128x128/apps"), ["safeeyes/platform/icons/hicolor/128x128/apps/io.github.slgobinath.SafeEyes.png"]), - (os.path.join(root_dir, "share/icons/hicolor/48x48/status"), ["safeeyes/platform/icons/hicolor/48x48/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/48x48/status/io.github.slgobinath.SafeEyes-enabled.png"]), - (os.path.join(root_dir, "share/icons/hicolor/48x48/apps"), ["safeeyes/platform/icons/hicolor/48x48/apps/io.github.slgobinath.SafeEyes.png"]),] + return [ + ( + os.path.join(root_dir, "share/applications"), + ["safeeyes/platform/io.github.slgobinath.SafeEyes.desktop"], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/24x24/status"), + [ + "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-disabled.png", + "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-enabled.png", + "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-timer.png", + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/24x24/apps"), + [ + "safeeyes/platform/icons/hicolor/24x24/apps/io.github.slgobinath.SafeEyes.png" + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/16x16/status"), + [ + "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-disabled.png", + "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-enabled.png", + "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-timer.png", + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/16x16/apps"), + [ + "safeeyes/platform/icons/hicolor/16x16/apps/io.github.slgobinath.SafeEyes.png" + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/32x32/status"), + [ + "safeeyes/platform/icons/hicolor/32x32/status/io.github.slgobinath.SafeEyes-disabled.png", + "safeeyes/platform/icons/hicolor/32x32/status/io.github.slgobinath.SafeEyes-enabled.png", + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/32x32/apps"), + [ + "safeeyes/platform/icons/hicolor/32x32/apps/io.github.slgobinath.SafeEyes.png" + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/64x64/apps"), + [ + "safeeyes/platform/icons/hicolor/64x64/apps/io.github.slgobinath.SafeEyes.png" + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/128x128/apps"), + [ + "safeeyes/platform/icons/hicolor/128x128/apps/io.github.slgobinath.SafeEyes.png" + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/48x48/status"), + [ + "safeeyes/platform/icons/hicolor/48x48/status/io.github.slgobinath.SafeEyes-disabled.png", + "safeeyes/platform/icons/hicolor/48x48/status/io.github.slgobinath.SafeEyes-enabled.png", + ], + ), + ( + os.path.join(root_dir, "share/icons/hicolor/48x48/apps"), + [ + "safeeyes/platform/icons/hicolor/48x48/apps/io.github.slgobinath.SafeEyes.png" + ], + ), + ] def __package_files(directory): - """ - Collect the package files. - """ + """Collect the package files.""" paths = [] - for (path, _, filenames) in os.walk(directory): + for path, _, filenames in os.walk(directory): for filename in filenames: - paths.append(os.path.join('..', path, filename)) + paths.append(os.path.join("..", path, filename)) return paths def __package_data(): - """ - Return a list of package data. - """ + """Return a list of package data.""" __compile_po_files() - data = ['glade/*.glade', 'resource/*'] - data.extend(__package_files('safeeyes/config')) - data.extend(__package_files('safeeyes/plugins')) - data.extend(__package_files('safeeyes/platform')) + data = ["glade/*.glade", "resource/*"] + data.extend(__package_files("safeeyes/config")) + data.extend(__package_files("safeeyes/plugins")) + data.extend(__package_files("safeeyes/platform")) return data + setuptools.setup( name="safeeyes", version="2.2.2", - description="Protect your eyes from eye strain using this continuous breaks reminder.", + description=( + "Protect your eyes from eye strain using this continuous breaks reminder." + ), long_description=long_description, long_description_content_type="text/markdown", author="Gobinath Loganathan", @@ -88,16 +138,21 @@ def __package_data(): url="https://github.com/slgobinath/SafeEyes", download_url="https://github.com/slgobinath/SafeEyes/archive/v2.2.2.tar.gz", packages=setuptools.find_packages(), - package_data={'safeeyes': __package_data()}, + package_data={"safeeyes": __package_data()}, data_files=__data_files(), install_requires=requires, - entry_points={'console_scripts': ['safeeyes = safeeyes.__main__:main']}, - keywords='linux utility health eye-strain safe-eyes', + entry_points={"console_scripts": ["safeeyes = safeeyes.__main__:main"]}, + keywords="linux utility health eye-strain safe-eyes", classifiers=[ "Operating System :: POSIX :: Linux", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Development Status :: 5 - Production/Stable", "Environment :: X11 Applications :: GTK", "Intended Audience :: End Users/Desktop", - "Topic :: Utilities"] + [('Programming Language :: Python :: %s' % x) for x in '3 3.5 3.6 3.7 3.8 3.9'.split()] + "Topic :: Utilities", + ] + + [ + "Programming Language :: Python :: %s" % x + for x in "3 3.5 3.6 3.7 3.8 3.9".split() + ], )