From 5de6869309540b9214a07682523c94f835ded4d3 Mon Sep 17 00:00:00 2001 From: Gobinath Date: Sat, 7 Oct 2017 09:10:31 -0400 Subject: [PATCH] Safeeyes 2.0.0 (#194) --- .gitignore | 8 +- README.md | 127 +- safeeyes/AboutDialog.py | 70 +- safeeyes/BreakScreen.py | 399 ++--- safeeyes/Notification.py | 80 - safeeyes/PluginManager.py | 304 ++++ safeeyes/Plugins.py | 143 -- safeeyes/SafeEyes.py | 270 ++++ safeeyes/SafeEyesCore.py | 564 ++++--- safeeyes/SettingsDialog.py | 226 --- safeeyes/TrayIcon.py | 299 ---- safeeyes/Utility.py | 840 ++++++----- safeeyes/__main__.py | 366 ++--- safeeyes/config/lang/ca.json | 62 - safeeyes/config/lang/cs.json | 62 - safeeyes/config/lang/de.json | 62 - safeeyes/config/lang/en.json | 62 - safeeyes/config/lang/es.json | 62 - safeeyes/config/lang/et.json | 62 - safeeyes/config/lang/fa.json | 62 - safeeyes/config/lang/fr.json | 62 - safeeyes/config/lang/ge.json | 62 - safeeyes/config/lang/hi.json | 62 - safeeyes/config/lang/hu.json | 62 - safeeyes/config/lang/id.json | 62 - safeeyes/config/lang/it.json | 62 - safeeyes/config/lang/mk.json | 62 - safeeyes/config/lang/nl.json | 62 - safeeyes/config/lang/pl.json | 62 - safeeyes/config/lang/pt.json | 62 - safeeyes/config/lang/ru.json | 62 - safeeyes/config/lang/sk.json | 62 - safeeyes/config/lang/ta.json | 62 - safeeyes/config/lang/tr.json | 62 - safeeyes/config/lang/uk.json | 62 - safeeyes/config/lang/vi.json | 67 - safeeyes/config/lang/zh_Hans.json | 62 - .../locale/en_US/LC_MESSAGES/safeeyes.po | 310 ++++ .../config/locale/ta/LC_MESSAGES/safeeyes.po | 310 ++++ safeeyes/config/safeeyes.json | 130 +- safeeyes/config/style/safeeyes_style.css | 20 +- safeeyes/glade/about_dialog.glade | 28 +- safeeyes/glade/break_screen.glade | 206 +-- safeeyes/glade/item_bool.glade | 61 + safeeyes/glade/item_break.glade | 70 + safeeyes/glade/item_int.glade | 66 + safeeyes/glade/item_plugin.glade | 146 ++ safeeyes/glade/item_text.glade | 60 + safeeyes/glade/new_break.glade | 214 +++ safeeyes/glade/settings_break.glade | 480 ++++++ safeeyes/glade/settings_dialog.glade | 1329 +++++++++++------ safeeyes/glade/settings_plugin.glade | 59 + safeeyes/model.py | 178 +++ safeeyes/plugins/audiblealert/config.json | 16 + safeeyes/plugins/audiblealert/icon.png | Bin 0 -> 444 bytes safeeyes/plugins/audiblealert/plugin.py | 55 + safeeyes/plugins/donotdisturb/config.json | 35 + safeeyes/plugins/donotdisturb/icon.png | Bin 0 -> 213 bytes safeeyes/plugins/donotdisturb/plugin.py | 93 ++ safeeyes/plugins/healthstats/config.json | 16 + safeeyes/plugins/healthstats/icon.png | Bin 0 -> 607 bytes safeeyes/plugins/healthstats/plugin.py | 81 + safeeyes/plugins/notification/config.json | 15 + safeeyes/plugins/notification/icon.png | Bin 0 -> 222 bytes safeeyes/plugins/notification/plugin.py | 88 ++ safeeyes/plugins/screensaver/config.json | 29 + safeeyes/plugins/screensaver/icon.png | Bin 0 -> 398 bytes safeeyes/plugins/screensaver/plugin.py | 113 ++ safeeyes/plugins/smartpause/config.json | 22 + safeeyes/plugins/smartpause/icon.png | Bin 0 -> 555 bytes safeeyes/plugins/smartpause/plugin.py | 154 ++ safeeyes/plugins/trayicon/config.json | 45 + safeeyes/plugins/trayicon/icon.png | Bin 0 -> 429 bytes safeeyes/plugins/trayicon/plugin.py | 406 +++++ safeeyes/resource/ic_plugin.png | Bin 0 -> 489 bytes safeeyes/resource/ic_warning.png | Bin 0 -> 503 bytes safeeyes/rpc.py | 111 ++ safeeyes/settings.py | 542 +++++++ setup.py | 67 +- 79 files changed, 6565 insertions(+), 4179 deletions(-) delete mode 100644 safeeyes/Notification.py create mode 100644 safeeyes/PluginManager.py delete mode 100644 safeeyes/Plugins.py create mode 100644 safeeyes/SafeEyes.py delete mode 100644 safeeyes/SettingsDialog.py delete mode 100644 safeeyes/TrayIcon.py delete mode 100644 safeeyes/config/lang/ca.json delete mode 100644 safeeyes/config/lang/cs.json delete mode 100644 safeeyes/config/lang/de.json delete mode 100644 safeeyes/config/lang/en.json delete mode 100644 safeeyes/config/lang/es.json delete mode 100644 safeeyes/config/lang/et.json delete mode 100644 safeeyes/config/lang/fa.json delete mode 100644 safeeyes/config/lang/fr.json delete mode 100644 safeeyes/config/lang/ge.json delete mode 100644 safeeyes/config/lang/hi.json delete mode 100644 safeeyes/config/lang/hu.json delete mode 100644 safeeyes/config/lang/id.json delete mode 100644 safeeyes/config/lang/it.json delete mode 100644 safeeyes/config/lang/mk.json delete mode 100644 safeeyes/config/lang/nl.json delete mode 100644 safeeyes/config/lang/pl.json delete mode 100644 safeeyes/config/lang/pt.json delete mode 100644 safeeyes/config/lang/ru.json delete mode 100644 safeeyes/config/lang/sk.json delete mode 100644 safeeyes/config/lang/ta.json delete mode 100644 safeeyes/config/lang/tr.json delete mode 100644 safeeyes/config/lang/uk.json delete mode 100644 safeeyes/config/lang/vi.json delete mode 100644 safeeyes/config/lang/zh_Hans.json create mode 100644 safeeyes/config/locale/en_US/LC_MESSAGES/safeeyes.po create mode 100644 safeeyes/config/locale/ta/LC_MESSAGES/safeeyes.po create mode 100644 safeeyes/glade/item_bool.glade create mode 100644 safeeyes/glade/item_break.glade create mode 100644 safeeyes/glade/item_int.glade create mode 100644 safeeyes/glade/item_plugin.glade create mode 100644 safeeyes/glade/item_text.glade create mode 100644 safeeyes/glade/new_break.glade create mode 100644 safeeyes/glade/settings_break.glade create mode 100644 safeeyes/glade/settings_plugin.glade create mode 100644 safeeyes/model.py create mode 100644 safeeyes/plugins/audiblealert/config.json create mode 100644 safeeyes/plugins/audiblealert/icon.png create mode 100644 safeeyes/plugins/audiblealert/plugin.py create mode 100644 safeeyes/plugins/donotdisturb/config.json create mode 100644 safeeyes/plugins/donotdisturb/icon.png create mode 100644 safeeyes/plugins/donotdisturb/plugin.py create mode 100644 safeeyes/plugins/healthstats/config.json create mode 100644 safeeyes/plugins/healthstats/icon.png create mode 100644 safeeyes/plugins/healthstats/plugin.py create mode 100644 safeeyes/plugins/notification/config.json create mode 100644 safeeyes/plugins/notification/icon.png create mode 100644 safeeyes/plugins/notification/plugin.py create mode 100644 safeeyes/plugins/screensaver/config.json create mode 100644 safeeyes/plugins/screensaver/icon.png create mode 100644 safeeyes/plugins/screensaver/plugin.py create mode 100644 safeeyes/plugins/smartpause/config.json create mode 100644 safeeyes/plugins/smartpause/icon.png create mode 100644 safeeyes/plugins/smartpause/plugin.py create mode 100644 safeeyes/plugins/trayicon/config.json create mode 100644 safeeyes/plugins/trayicon/icon.png create mode 100644 safeeyes/plugins/trayicon/plugin.py create mode 100644 safeeyes/resource/ic_plugin.png create mode 100644 safeeyes/resource/ic_warning.png create mode 100644 safeeyes/rpc.py create mode 100644 safeeyes/settings.py diff --git a/.gitignore b/.gitignore index c538041..0afef64 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# Glade temporary files +*.glade~ + # Distribution / packaging .Python env/ @@ -91,4 +94,7 @@ ENV/ .spyderproject # Rope project settings -.ropeproject \ No newline at end of file +.ropeproject + +# Visual Studio Code settings +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index c7d099a..f774426 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,4 @@ # Safe Eyes - -[![GitHub version](https://badge.fury.io/gh/slgobinath%2FSafeEyes.svg)](https://badge.fury.io/gh/slgobinath%2FSafeEyes) -[![PyPI version](https://badge.fury.io/py/safeeyes.svg)](https://badge.fury.io/py/safeeyes) -[![Translation status](https://hosted.weblate.org/widgets/safe-eyes/-/translations/svg-badge.svg)](https://hosted.weblate.org/engage/safe-eyes/?utm_source=widget) - Protect your eyes from eye strain using this simple and beautiful, yet extensible break reminder. A Free and Open Source Linux alternative to EyeLeo. Visit to the official site: http://slgobinath.github.io/SafeEyes/ for more details. @@ -17,87 +12,65 @@ Ensure to meet the following dependencies when compiling from source: - gir1.2-appindicator3-0.1 - gir1.2-notify-0.7 - libappindicator-gtk3 -- python3-pyaudio - python3-psutil - xprintidle (optional) -## Customizing options -One of the key advantage of Safe Eyes over other similar products is its highly customizable design. You can change almost everything in Safe Eyes. A detailed documentation is available in the official site: [Customize Safe Eyes](http://slgobinath.github.io/SafeEyes/#customize) +## Writing Plug-in for Safe Eyes +A plugin is a combination of two files: `plugin.py` and `config.json`. These two files must be placed in a directory: `~/.config/safeeyes/plugins/`. Optionally a plugin also can have an image file `icon.png` which is used to represent the plugin in the Settings dialog. -## Contribute -I started this project for my own use and later released it as an open source alternative to EyeLeo and progressively reached to the current state with the great support of open source community. Most of the creative ideas were suggested and implemented by users. You can always add more to Safe Eyes. I have listed some possible ways here: [How to contribute](http://slgobinath.github.io/SafeEyes/#contribute) +For example, a Weather plugin may have the following file structure: +``` +~ +└── .config + └── safeeyes + └── plugins + └── weather + ├── config.json + ├── icon.png + └── plugin.py +``` -## Features +The `icon.png` must be `24x24` pixels size. If `icon.png` is not available, the default gear icon will be shown in the Settings dialog. -General Features: +A sample `config.json` is provided below: +```json +{ + "meta": { + "name": "Weather", + "description": "Show the current weather on break screen", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": ["pyowm"], + "shell_commands": [], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [ + { + "id": "api", + "label": "OpenWeatherMap API Key", + "type": "TEXT", + "default": "" + }, + { + "id": "location", + "label": "Location", + "type": "TEXT", + "default": "" + } + ], + "break_override_allowed": true +} +``` -- Short breaks with eye exercises -- Long breaks to change physical position and to warm up -- Disable the keyboard during break -- Notifications before every break -- Do not disturb when working with full-screen applications( Eg: Watching movies) -- Smart pause and resume based on system idle time (Require `xprintidle`) -- Multi-monitor support -- Multi-language support -- Elegant and customizable design +The `meta` properties must provide the name of the plugin, a short description and the current version of the plugin. -Optional Features: - -- Strict break for those who are addicted to computer -- Postpone break -- Skip or take break based on active windows (Regardless of full-screen-mode) -- Customize individual break time -- Define your own custom exercise -- Audible alert at the end of break -- Turn on/off audible alert for individual breaks -- Customize disable time period -- Lock screen after long breaks -- Add images to breaks -- Plug-in support to extend Safe Eyes - -For more details: [SafeEyes Features](http://slgobinath.github.io/SafeEyes/#features) - -## Currently available translations - * [Català](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ca.json) - * [Čeština](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/cs.json) - * [Deutsch](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/de.json) - * [English](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/en.json) - * [Español](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/es.json) - * [فارسی](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/fa.json) - * [Français](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/fr.json) - * [ქართული](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ge.json) - * [हिंदी](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/hi.json) - * [Magyar](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/hu.json) - * [Bahasa Indonesia](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/id.json) - * [Македонски](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/mk.json) - * [Polski](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/pl.json) - * [Português](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/pt.json) - * [Русский](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ru.json) - * [Slovenský](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/sk.json) - * [தமிழ்](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ta.json) - * [Türkçe](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/tr.json) - * [Українська](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/uk.json) - * [Tiếng Việt](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/vi.json) - -Do you want to see your language here? Please translate Safe Eyes to whatever the languages you know. Visit to **Translate Safe Eyes** in [Customize Safe Eyes](http://slgobinath.github.io/SafeEyes/#customize) to see how to translate. - -## Tested Environments - -Core functionalities of Safe Eyes are tested by the developer in the following environments: - -* Antergos 17.4 -* Elementary OS Loki -* Fedora 25 -* Kubuntu 17.04 -* Linux Mint 18.1 -* Manjaro 16.10.3 -* Ubuntu 14.04 -* Ubuntu 16.04 -* Ubuntu 16.10 -* Ubuntu Budgie 17.04 -* Ubuntu Mate 16.04 -* Xubuntu 16.10 +The `dependencies` property defines various dependency constraints of the plugin. The dependencies can be Python modules, commandline tools, desktop environments or Safe Eyes resources. The `operating_systems` property is reserved for operating system dependency but not checked for at the moment. +If a dependency is not available, the Safe Eyes will not load the plugin. The Settings dialog will show a warning symbol and a message to install/check the missing dependencies. Dependencies are checked in the order of *Desktop Environment*, *Python Modules*, *Commandline Tools* and *Resources*. If a dependency is not available, Safe Eyes will stop looking for the rest. +The configurations related to the plugin must be defined in `settings`. Each setting must have an `id`, `label`, `type` and a default value matching the `type`. Safe Eyes 2.0.0 supports only the following types: `INT`, `TEXT` and `BOOL`. According to the types, Settings dialog will show a *Spin*, *Text Field* or *Switch Button* as the input field. ## License GNU General Public License v3 diff --git a/safeeyes/AboutDialog.py b/safeeyes/AboutDialog.py index 6ecdbaa..1afd95c 100644 --- a/safeeyes/AboutDialog.py +++ b/safeeyes/AboutDialog.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Safe Eyes is a utility to remind you to take break frequently # to protect your eyes from eye strain. @@ -15,44 +16,47 @@ # 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. +""" -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk +import os + +from safeeyes import Utility + +ABOUT_DIALOG_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/about_dialog.glade") -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. - """ +class AboutDialog(object): + """ + 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, glade_file, version, language): - builder = Gtk.Builder() - builder.add_from_file(glade_file) - builder.connect_signals(self) - self.window = builder.get_object("window_about") - builder.get_object('lbl_decription').set_label(language['app_info']['description']) - builder.get_object('lbl_license').set_label(str(language['ui_controls']['license']) + ':') - builder.get_object('btn_close').set_label(language['ui_controls']['close']) + 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(_('description')) + 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) + # Set the version at the runtime + builder.get_object('lbl_app_name').set_label('Safe Eyes ' + version) - def show(self): - """ - Show the About dialog. - """ - self.window.show_all() + def show(self): + """ + Show the About dialog. + """ + self.window.show_all() - def on_window_delete(self, *args): - """ - Window close event handler. - """ - self.window.destroy() + def on_window_delete(self, *args): + """ + Window close event handler. + """ + self.window.destroy() - def on_close_clicked(self, button): - """ - Close button click event handler. - """ - self.window.destroy() + def on_close_clicked(self, *args): + """ + Close button click event handler. + """ + self.window.destroy() diff --git a/safeeyes/BreakScreen.py b/safeeyes/BreakScreen.py index 9e324ed..780cab3 100644 --- a/safeeyes/BreakScreen.py +++ b/safeeyes/BreakScreen.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Safe Eyes is a utility to remind you to take break frequently # to protect your eyes from eye strain. @@ -16,227 +17,241 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import gi, threading, logging, time -from Xlib.display import Display, X +import logging +import os +import threading +import time + +import gi +from safeeyes import Utility +from Xlib.display import Display +from Xlib.display import X + gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gdk, GLib +from gi.repository import Gdk +from gi.repository import GLib +from gi.repository import Gtk + +BREAK_SCREEN_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/break_screen.glade") -class BreakScreen: - """ - The fullscreen window which prevents users from using the computer. - This class reads the break_screen.glade and build the user interface. - """ +class BreakScreen(object): + """ + 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_skip, on_postpone, glade_file, style_sheet_path): - self.context = context - self.on_skip = on_skip - self.on_postpone = on_postpone - self.is_pretified = False - self.windows = [] - self.count_labels = [] - self.glade_file = glade_file - self.enable_shortcut = False - self.display = Display() + def __init__(self, context, on_skip, on_postpone, style_sheet_path): + self.context = context + self.count_labels = [] + self.display = Display() + self.enable_postpone = False + self.enable_shortcut = False + self.is_pretified = False + self.keycode_shortcut_postpone = 65 + self.keycode_shortcut_skip = 9 + self.on_postpone = on_postpone + self.on_skip = on_skip + self.shortcut_disable_time = 2 + self.strict_break = False + self.windows = [] - # 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) + # 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) - def initialize(self, config, language): - """ - Initialize the internal properties from configuration - """ - logging.info("Initialize the break screen") - self.skip_button_text = language['ui_controls']['skip'] - self.postpone_button_text = language['ui_controls']['postpone'] - self.strict_break = config.get('strict_break', False) - self.enable_postpone = config.get('allow_postpone', False) - self.keycode_shortcut_skip = config.get('shortcut_skip', 9) - self.keycode_shortcut_postpone = config.get('shortcut_postpone', 65) - self.shortcut_disable_time = config.get('shortcut_disable_time', 2) + def initialize(self, config): + """ + 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) - def skip_break(self): - """ - Skip the break from the break screen - """ - logging.info("User skipped the break") - # Must call on_skip before close to lock screen before closing the break screen - self.on_skip() - self.close() + def skip_break(self): + """ + Skip the break from the break screen + """ + logging.info("User skipped the break") + # Must call on_skip before close to lock screen before closing the break screen + self.on_skip() + self.close() - def postpone_break(self): - """ - Postpone the break from the break screen - """ - logging.info("User postponed the break") - self.on_postpone() - self.close() + def postpone_break(self): + """ + Postpone the break from the break screen + """ + logging.info("User postponed the break") + self.on_postpone() + self.close() - def on_window_delete(self, *args): - """ - Window close event handler. - """ - logging.info("Closing the break screen") - self.__release_keyboard() - self.close() + def on_window_delete(self, *args): + """ + Window close event handler. + """ + logging.info("Closing the break screen") + self.__release_keyboard() + self.close() - def on_skip_clicked(self, button): - """ - Skip button press event handler. - """ - self.skip_break() + def on_skip_clicked(self, button): + """ + Skip button press event handler. + """ + self.skip_break() - def on_postpone_clicked(self, button): - """ - Postpone button press event handler. - """ - self.postpone_break() + def on_postpone_clicked(self, button): + """ + Postpone button press event handler. + """ + self.postpone_break() - def show_count_down(self, count_down, seconds): - """ - Show/update the count down on all screens. - """ - self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= count_down - mins, secs = divmod(seconds, 60) - timeformat = '{:02d}:{:02d}'.format(mins, secs) - GLib.idle_add(lambda: self.__update_count_down(timeformat)) + def show_count_down(self, countdown, seconds): + """ + Show/update the count down on all screens. + """ + self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= seconds + mins, secs = divmod(countdown, 60) + timeformat = '{:02d}:{:02d}'.format(mins, secs) + GLib.idle_add(lambda: self.__update_count_down(timeformat)) - def show_message(self, message, image_path, plugins_data): - """ - Show the break screen with the given message on all displays. - """ - self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= 0 - GLib.idle_add(lambda: self.__show_break_screen(message, image_path, plugins_data)) + def show_message(self, break_obj, widget): + """ + Show the break screen with the given message on all displays. + """ + message = break_obj.name + image_path = break_obj.image + self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= 0 + GLib.idle_add(lambda: self.__show_break_screen(message, image_path, widget)) - def close(self): - """ - Hide the break screen from active window and destroy all other windows - """ - logging.info("Close the break screen(s)") - self.__release_keyboard() + def close(self): + """ + Hide the break screen from active window and destroy all other windows + """ + logging.info("Close the break screen(s)") + self.__release_keyboard() - # Destroy other windows if exists - GLib.idle_add(lambda: self.__destroy_all_screens()) + # Destroy other windows if exists + GLib.idle_add(lambda: self.__destroy_all_screens()) - def __show_break_screen(self, message, image_path, plugins_data): - """ - Show an empty break screen on all screens. - """ - # Lock the keyboard - thread = threading.Thread(target=self.__lock_keyboard) - thread.start() + def __show_break_screen(self, message, image_path, widget): + """ + Show an empty break screen on all screens. + """ + # Lock the keyboard + thread = threading.Thread(target=self.__lock_keyboard) + thread.start() - logging.info("Show break screens in all displays") - screen = Gtk.Window().get_screen() - no_of_monitors = screen.get_n_monitors() + logging.info("Show break screens in all displays") + screen = Gtk.Window().get_screen() + no_of_monitors = screen.get_n_monitors() - for monitor in range(no_of_monitors): - monitor_gemoetry = screen.get_monitor_geometry(monitor) - x = monitor_gemoetry.x - y = monitor_gemoetry.y + for monitor in range(no_of_monitors): + monitor_gemoetry = screen.get_monitor_geometry(monitor) + x = monitor_gemoetry.x + y = monitor_gemoetry.y - builder = Gtk.Builder() - builder.add_from_file(self.glade_file) - builder.connect_signals(self) + builder = Gtk.Builder() + builder.add_from_file(BREAK_SCREEN_GLADE) + builder.connect_signals(self) - window = builder.get_object("window_main") - lbl_message = builder.get_object("lbl_message") - lbl_count = builder.get_object("lbl_count") - lbl_left = builder.get_object("lbl_left") - lbl_right = builder.get_object("lbl_right") - img_break = builder.get_object("img_break") - box_buttons = builder.get_object("box_buttons") + window = builder.get_object("window_main") + lbl_message = builder.get_object("lbl_message") + lbl_count = builder.get_object("lbl_count") + lbl_widget = builder.get_object("lbl_widget") + img_break = builder.get_object("img_break") + box_buttons = builder.get_object("box_buttons") - # Add the buttons - if not self.strict_break: - # Add postpone button - if self.enable_postpone: - btn_postpone = Gtk.Button(self.postpone_button_text) - 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) + # Add the buttons + if not self.strict_break: + # Add postpone button + if self.enable_postpone: + btn_postpone = Gtk.Button(_('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) - # Add the skip button - btn_skip = Gtk.Button(self.skip_button_text) - 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) + # Add the skip button + btn_skip = Gtk.Button(_('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) - # Set values - if image_path: - img_break.set_from_file(image_path) - lbl_message.set_label(message) - lbl_left.set_markup(plugins_data['left']) - lbl_right.set_markup(plugins_data['right']) + # Set values + if image_path: + img_break.set_from_file(image_path) + lbl_message.set_label(message) + lbl_widget.set_markup(widget) - self.windows.append(window) - self.count_labels.append(lbl_count) + self.windows.append(window) + self.count_labels.append(lbl_count) - # 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': - # Fix flickering screen in KDE by setting opacity to 1 - window.set_opacity(0.9) + # 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': + # Fix flickering screen in KDE by setting opacity to 1 + window.set_opacity(0.9) - window.stick() - window.set_keep_above(True) - window.present() - window.move(x, y) - window.fullscreen() + window.stick() + window.set_keep_above(True) + window.present() + window.move(x, y) + window.fullscreen() - def __update_count_down(self, count): - """ - Update the countdown on all break screens. - """ - for label in self.count_labels: - label.set_text(count) + def __update_count_down(self, count): + """ + 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 - """ - logging.info("Lock the keyboard") - self.lock_keyboard = True + def __lock_keyboard(self): + """ + Lock the keyboard to prevent the user from using keyboard shortcuts + """ + logging.info("Lock the keyboard") + self.lock_keyboard = True - # Grab the keyboard - root = self.display.screen().root - root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask) - root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) + # Grab the keyboard + root = self.display.screen().root + root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask) + root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) - # Consume keyboard events - while self.lock_keyboard: - if self.display.pending_events() > 0: - # 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: - self.skip_break() - break - elif self.enable_postpone and event.detail == self.keycode_shortcut_postpone: - self.postpone_break() - break - else: - # Reduce the CPU usage by sleeping for a second - time.sleep(1) + # Consume keyboard events + while self.lock_keyboard: + if self.display.pending_events() > 0: + # 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: + self.skip_break() + break + elif self.enable_postpone and event.detail == self.keycode_shortcut_postpone: + self.postpone_break() + break + else: + # Reduce the CPU usage by sleeping for a second + time.sleep(1) - def __release_keyboard(self): - """ - Release the locked keyboard. - """ - logging.info("Unlock the keyboard") - self.lock_keyboard = False - self.display.ungrab_keyboard(X.CurrentTime) - self.display.flush() + def __release_keyboard(self): + """ + 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. - """ - for win in self.windows: - win.destroy() - del self.windows[:] - del self.count_labels[:] + def __destroy_all_screens(self): + """ + Close all the break screens. + """ + for win in self.windows: + win.destroy() + del self.windows[:] + del self.count_labels[:] diff --git a/safeeyes/Notification.py b/safeeyes/Notification.py deleted file mode 100644 index 9c6e831..0000000 --- a/safeeyes/Notification.py +++ /dev/null @@ -1,80 +0,0 @@ -# Safe Eyes is a utility to remind you to take break frequently -# to protect your eyes from eye strain. - -# Copyright (C) 2016 Gobinath - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import gi, logging -gi.require_version('Notify', '0.7') -from gi.repository import Notify -from safeeyes import Utility - - -APPINDICATOR_ID = 'safeeyes' - - -class Notification: - """ - This class is responsible for the notification to the user before the break. - """ - - def __init__(self, context, language): - logging.info('Initialize the notification') - Notify.init(APPINDICATOR_ID) - self.context = context - self.language = language - - def initialize(self, language): - """ - Initialize the notification object. - """ - self.language = language - - def show(self, warning_time): - """ - Show the notification - """ - logging.info('Show pre-break notification') - - # Construct the message based on the type of the next break - message = '\n' - if self.context['break_type'] == 'short': - message += self.language['messages']['ready_for_a_short_break'].format(warning_time) - else: - message += self.language['messages']['ready_for_a_long_break'].format(warning_time) - - self.notification = Notify.Notification.new('Safe Eyes', message, icon='safeeyes_enabled') - try: - self.notification.show() - except Exception as e: - logging.exception('Error in showing notification', e) - - def close(self): - """ - Close the notification if it is not closed by the system already. - """ - logging.info('Close pre-break notification') - try: - self.notification.close() - except: - # Some Linux systems automatically close the notification. - pass - - def quite(self): - """ - Uninitialize the notification. Call this method when closing the application. - """ - logging.info('Uninitialize Safe Eyes notification') - Utility.execute_main_thread(Notify.uninit) diff --git a/safeeyes/PluginManager.py b/safeeyes/PluginManager.py new file mode 100644 index 0000000..89a8976 --- /dev/null +++ b/safeeyes/PluginManager.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# 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. +A plugin must have the following directory structure: + + |- config.json + |- plugin.py + |- icon.png (Optional) + +The plugin.py can have following methods but all are optional: + - description() + If a custom description has to be displayed, use this function + - on_init(context, safeeyes_config, plugin_config) + Initialize the plugin. Will be called after loading and after every changes in configuration + - on_start() + Executes when Safe Eyes is enabled + - on_stop() + Executes when Safe Eyes is disabled + - enable() + Executes once the plugin.py is loaded as a module + - disable() + Executes if the plugin is disabled at the runtime by the user + - on_exit() + Executes before Safe Eyes exits +""" + +import importlib +import inspect +import logging +import os +import sys + +from safeeyes import Utility + +sys.path.append(os.path.abspath(Utility.SYSTEM_PLUGINS_DIR)) +sys.path.append(os.path.abspath(Utility.USER_PLUGINS_DIR)) + +HORIZONTAL_LINE_LENGTH = 64 + + +class PluginManager(object): + """ + Imports the Safe Eyes plugins and calls the methods defined in those plugins. + """ + + def __init__(self, context, config): + logging.info('Load all the plugins') + self.__plugins = {} + self.__plugins_on_init = [] + self.__plugins_on_start = [] + self.__plugins_on_stop = [] + self.__plugins_on_exit = [] + self.__plugins_on_pre_break = [] + self.__plugins_on_start_break = [] + self.__plugins_on_stop_break = [] + self.__plugins_on_countdown = [] + self.__plugins_update_next_break = [] + self.__widget_plugins = [] + self.last_break = None + self.horizontal_line = '─' * HORIZONTAL_LINE_LENGTH + + def init(self, context, config): + """ + Initialize all the plugins with init(context, safeeyes_config, plugin_config) function. + """ + # Load the plugins + for plugin in config.get('plugins'): + try: + self.__load_plugin(plugin, context) + except BaseException: + logging.error('Error in loading the plugin: %s', plugin['id']) + continue + # Initialize the plugins + for plugin in self.__plugins_on_init: + plugin['module'].init(context, config, plugin['config']) + return True + + def start(self): + """ + Execute the on_start() function of plugins. + """ + for plugin in self.__plugins_on_start: + plugin['module'].on_start() + return True + + def stop(self): + """ + Execute the on_stop() function of plugins. + """ + for plugin in self.__plugins_on_stop: + plugin['module'].on_stop() + return True + + def exit(self): + """ + Execute the on_exit() function of plugins. + """ + for plugin in self.__plugins_on_exit: + plugin['module'].on_exit() + return True + + def pre_break(self, break_obj): + """ + Execute the on_pre_break(break_obj) function of plugins. + """ + for plugin in self.__plugins_on_pre_break: + if break_obj.plugin_enabled(plugin['id'], plugin['enabled']): + if plugin['module'].on_pre_break(break_obj): + return False + return True + + def start_break(self, break_obj): + """ + Execute the start_break(break_obj) function of plugins. + """ + self.last_break = break_obj + for plugin in self.__plugins_on_start_break: + if break_obj.plugin_enabled(plugin['id'], plugin['enabled']): + if plugin['module'].on_start_break(break_obj): + return False + + return True + + def stop_break(self): + """ + Execute the stop_break() function of plugins. + """ + for plugin in self.__plugins_on_stop_break: + if self.last_break.plugin_enabled(plugin['id'], plugin['enabled']): + plugin['module'].on_stop_break() + + def countdown(self, countdown, seconds): + """ + Execute the on_countdown(countdown, seconds) function of plugins. + """ + for plugin in self.__plugins_on_countdown: + if self.last_break.plugin_enabled(plugin['id'], plugin['enabled']): + plugin['module'].on_countdown(countdown, seconds) + + def update_next_break(self, break_time): + """ + Execute the update_next_break(break_time) function of plugins. + """ + for plugin in self.__plugins_update_next_break: + plugin['module'].update_next_break(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. + """ + widget = '' + for plugin in self.__widget_plugins: + if break_obj.plugin_enabled(plugin['id'], plugin['enabled']): + try: + title = plugin['module'].get_widget_title(break_obj).upper().strip() + if title == '': + continue + content = plugin['module'].get_widget_content(break_obj) + if content == '': + continue + widget += '{}\n{}\n{}\n\n\n'.format(title, self.horizontal_line, content) + except BaseException: + continue + return widget.strip() + + def __has_method(self, module, method_name, no_of_args=0): + """ + Check whether the given function is defined in the module or not. + """ + if hasattr(module, method_name): + if len(inspect.getargspec(getattr(module, method_name)).args) == no_of_args: + return True + return False + + def __remove_if_exists(self, list_of_items, item): + """ + Remove the item from the list_of_items it it exists. + """ + if item in list_of_items: + list_of_items.remove(item) + + def __load_plugin(self, plugin, context): + """ + Load the given plugin. + """ + plugin_enabled = plugin['enabled'] + if plugin['id'] in self.__plugins and not plugin_enabled: + # A disabled plugin but that was loaded earlier + plugin_obj = self.__plugins[plugin['id']] + if plugin_obj['enabled']: + # Previously enabled but now disabled + plugin_obj['enabled'] = False + self.__remove_if_exists(self.__plugins_on_start, plugin_obj) + self.__remove_if_exists(self.__plugins_on_stop, plugin_obj) + self.__remove_if_exists(self.__plugins_on_exit, plugin_obj) + self.__remove_if_exists(self.__plugins_update_next_break, plugin_obj) + # Call the plugin.disable method if available + if self.__has_method(plugin_obj['module'], 'disable'): + plugin_obj['module'].disable() + logging.info("Successfully unloaded the plugin '%s'", plugin['id']) + + if not plugin_obj['break_override_allowed']: + # Remaining methods also should be removed + self.__remove_if_exists(self.__plugins_on_init, plugin_obj) + self.__remove_if_exists(self.__plugins_on_pre_break, plugin_obj) + self.__remove_if_exists(self.__plugins_on_start_break, plugin_obj) + self.__remove_if_exists(self.__plugins_on_stop_break, plugin_obj) + self.__remove_if_exists(self.__plugins_on_countdown, plugin_obj) + self.__remove_if_exists(self.__widget_plugins, plugin_obj) + del self.__plugins[plugin['id']] + return + + # Look for plugin.py + plugin_dir = None + 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')): + plugin_dir = Utility.USER_PLUGINS_DIR + else: + logging.error('plugin.py not found for the plugin: %s', plugin['id']) + return + # Look for config.json + plugin_config_path = os.path.join(plugin_dir, plugin['id'], 'config.json') + if not os.path.isfile(plugin_config_path): + logging.error('config.json not found for the plugin: %s', plugin['id']) + return + plugin_config = Utility.load_json(plugin_config_path) + if plugin_config is None: + return + + if (plugin_enabled or plugin_config.get('break_override_allowed', False)): + if plugin['id'] in self.__plugins: + # The plugin is already enabled or partially loaded due to break_override_allowed + # Use the existing plugin object + plugin_obj = self.__plugins[plugin['id']] + if plugin_obj['enabled']: + # Already loaded completely + return + # Plugin was partially loaded due to break_override_allowed + if plugin_enabled: + # Load the rest of the methods + plugin_obj['enabled'] = True + module = plugin_obj['module'] + if self.__has_method(module, 'on_start'): + self.__plugins_on_start.append(plugin_obj) + if self.__has_method(module, 'on_stop'): + self.__plugins_on_stop.append(plugin_obj) + if self.__has_method(module, 'on_exit'): + self.__plugins_on_exit.append(plugin_obj) + if self.__has_method(module, 'update_next_break', 1): + self.__plugins_update_next_break.append(plugin_obj) + else: + # This is the first time to load the plugin + # Check for dependencies + if Utility.check_plugin_dependencies(plugin_config): + return + + # Load the plugin module + module = importlib.import_module((plugin['id'] + '.plugin')) + logging.info("Successfully loaded %s", str(module)) + plugin_obj = {'id': plugin['id'], 'module': module, 'config': plugin.get('settings', {}), 'enabled': plugin_enabled, 'break_override_allowed': plugin_config.get('break_override_allowed', False)} + self.__plugins[plugin['id']] = plugin_obj + if self.__has_method(module, 'enable'): + module.enable() + if plugin_enabled: + if self.__has_method(module, 'on_start'): + self.__plugins_on_start.append(plugin_obj) + if self.__has_method(module, 'on_stop'): + self.__plugins_on_stop.append(plugin_obj) + if self.__has_method(module, 'on_exit'): + self.__plugins_on_exit.append(plugin_obj) + if self.__has_method(module, 'update_next_break', 1): + self.__plugins_update_next_break.append(plugin_obj) + if self.__has_method(module, 'init', 3): + self.__plugins_on_init.append(plugin_obj) + if self.__has_method(module, 'on_pre_break', 1): + self.__plugins_on_pre_break.append(plugin_obj) + if self.__has_method(module, 'on_start_break', 1): + self.__plugins_on_start_break.append(plugin_obj) + if self.__has_method(module, 'on_stop_break', 0): + self.__plugins_on_stop_break.append(plugin_obj) + if self.__has_method(module, 'on_countdown', 2): + self.__plugins_on_countdown.append(plugin_obj) + if self.__has_method(module, 'get_widget_title', 1) and self.__has_method(module, 'get_widget_content', 1): + self.__widget_plugins.append(plugin_obj) diff --git a/safeeyes/Plugins.py b/safeeyes/Plugins.py deleted file mode 100644 index d28c729..0000000 --- a/safeeyes/Plugins.py +++ /dev/null @@ -1,143 +0,0 @@ -# Safe Eyes is a utility to remind you to take break frequently -# to protect your eyes from eye strain. - -# Copyright (C) 2017 Gobinath - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import logging, importlib, os, sys, inspect, copy -from multiprocessing.pool import ThreadPool -from safeeyes import Utility - -plugins_directory = os.path.join(Utility.config_directory, 'plugins') -sys.path.append(os.path.abspath(plugins_directory)) - - -class Plugins: - """ - Imports the Safe Eyes plugins and calls the methods defined in those plugins. - """ - - def __init__(self, config): - logging.info('Load all the plugins') - self.__plugins = [] - - for plugin in config['plugins']: - if plugin['location'].lower() in ['left', 'right']: - if os.path.isfile(os.path.join(plugins_directory, plugin['name'] + '.py')): - module = importlib.import_module(plugin['name']) - if self.__has_method(module, 'start') and self.__has_method(module, 'pre_notification') and self.__has_method(module, 'pre_break') and self.__has_method(module, 'post_break') and self.__has_method(module, 'exit'): - self.__plugins.append({'name': plugin['name'], 'module': module, 'location': plugin['location'].lower()}) - else: - logging.warning('Ignoring the plugin ' + str(plugin['name']) + ' due to invalid method signature') - else: - logging.warning('Plugin file ' + str(plugin['name']) + '.py not found') - else: - logging.warning('Ignoring the plugin ' + str(plugin['name']) + ' due to invalid location value: ' + plugin['location']) - - if self.__plugins: - self.__thread_pool = ThreadPool(min([4, len(self.__plugins)])) - - def start(self, context): - """ - Call the start function of all the plugins in separate thread. - """ - if self.__plugins: - context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes - for plugin in self.__plugins: - try: - self.__thread_pool.apply_async(plugin['module'].start, (context,)) - except Exception as e: - pass - - def pre_notification(self, context): - """ - Call the pre_notification function of all the plugins in separate thread. - """ - if self.__plugins: - context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes - for plugin in self.__plugins: - try: - self.__thread_pool.apply_async(plugin['module'].pre_notification, (context,)) - except Exception as e: - pass - - def pre_break(self, context): - """ - Call the pre_break function of all the plugins and provide maximum 1 second to return the result. - If they return the reault within 1 sec, append it to the output. - - Returns: {'left': 'Markup of plugins to be aligned on left', 'right': 'Markup of plugins to be aligned on right' } - """ - output = {'left': ' \n', 'right': ' \n'} - if self.__plugins: - context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes - multiple_results = [self.__thread_pool.apply_async(plugin['module'].pre_break, (context,)) for plugin in self.__plugins] - for i in range(len(multiple_results)): - try: - result = multiple_results[i].get(timeout=1) - if result: - # Limit the line length to 50 characters - large_lines = list(filter(lambda x: len(x) > 50, Utility.html_to_text(result).splitlines())) - if large_lines: - logging.warning('Ignoring lengthy result from the plugin ' + self.__plugins[i]['name']) - continue - output[self.__plugins[i]['location']] += (result + '\n\n') - except Exception: - # Something went wrong in the plugin - logging.warning('Error when executing the plugin ' + self.__plugins[i]['name']) - - return output - - def post_break(self, context): - """ - Call the post_break function of all the plugins in separate thread. - """ - if self.__plugins: - context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes - for plugin in self.__plugins: - try: - self.__thread_pool.apply_async(plugin['module'].post_break, (context,)) - except Exception as e: - pass - - def exit(self, context): - """ - Call the exit function of all the plugins in separate thread. - """ - if self.__plugins: - context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes - - # Give maximum 1 sec for all plugins before terminating the thread pool - multiple_results = [self.__thread_pool.apply_async(plugin['module'].exit, (context,)) for plugin in self.__plugins] - for i in range(len(multiple_results)): - try: - multiple_results[i].get(timeout=1) - except Exception: - # Something went wrong in the plugin - pass - - try: - self.__thread_pool.terminate() # Shutdown the pool - except Exception as e: - pass - - def __has_method(self, module, method_name, no_of_args=1): - """ - Check whether the given function is defined in the module or not. - """ - if hasattr(module, method_name): - if len(inspect.getargspec(getattr(module, method_name)).args) == no_of_args: - return True - return False diff --git a/safeeyes/SafeEyes.py b/safeeyes/SafeEyes.py new file mode 100644 index 0000000..3c3d634 --- /dev/null +++ b/safeeyes/SafeEyes.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# 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. +""" + +import atexit +import logging +from threading import Timer + +import dbus +import gi +from dbus.mainloop.glib import DBusGMainLoop +from safeeyes import Utility +from safeeyes.AboutDialog import AboutDialog +from safeeyes.BreakScreen import BreakScreen +from safeeyes.model import State +from safeeyes.rpc import RPCServer +from safeeyes.PluginManager import PluginManager +from safeeyes.SafeEyesCore import SafeEyesCore +from safeeyes.settings import SettingsDialog + +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +SAFE_EYES_VERSION = "2.0.0" + + +class SafeEyes(object): + """ + This class represents a runnable Safe Eyes instance. + """ + + def __init__(self, system_locale, config): + self.active = False + self.break_screen = None + self.safe_eyes_core = None + self.config = config + self.context = {} + self.plugins_manager = None + self.settings_dialog_active = False + self.rpc_server = None + + # Initialize the Safe Eyes Context + self.context['version'] = SAFE_EYES_VERSION + self.context['desktop'] = Utility.desktop_environment() + self.context['locale'] = system_locale + self.context['api'] = {} + self.context['api']['show_settings'] = self.show_settings + self.context['api']['show_about'] = self.show_about + self.context['api']['enable_safeeyes'] = self.enable_safeeyes + self.context['api']['disable_safeeyes'] = self.disable_safeeyes + self.context['api']['quit'] = self.quit + if self.config.get('persist_state'): + self.context['session'] = Utility.open_session() + else: + self.context['session'] = {'plugin': {}} + + self.break_screen = BreakScreen(self.context, self.on_skipped, self.on_postponed, Utility.STYLE_SHEET_PATH) + self.break_screen.initialize(self.config) + self.plugins_manager = PluginManager(self.context, self.config) + self.safe_eyes_core = SafeEyesCore(self.context) + self.safe_eyes_core.on_pre_break += self.plugins_manager.pre_break + self.safe_eyes_core.on_start_break += self.start_break + self.safe_eyes_core.on_count_down += self.countdown + self.safe_eyes_core.on_stop_break += self.stop_break + self.safe_eyes_core.on_update_next_break += self.update_next_break + self.safe_eyes_core.initialize(self.config) + self.context['api']['take_break'] = self.safe_eyes_core.take_break + self.context['api']['has_breaks'] = self.safe_eyes_core.has_breaks + self.plugins_manager.init(self.context, self.config) + atexit.register(self.persist_session) + self.rpc_server = RPCServer(self.config.get('rpc_port'), self.context) + self.rpc_server.start() + + def start(self): + """ + Start Safe Eyes + """ + if 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.safe_eyes_core.start() + self.handle_system_suspend() + + def show_settings(self): + """ + 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, self.save_settings) + settings_dialog.show() + + def show_about(self): + """ + 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. + """ + logging.info("Quit Safe Eyes") + self.context['state'] = State.QUIT + self.plugins_manager.stop() + self.safe_eyes_core.stop() + self.plugins_manager.exit() + self.rpc_server.stop() + Gtk.main_quit() + + def handle_suspend_callback(self, sleeping): + """ + 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: + # Sleeping / suspending + if self.active: + logging.info("Stop Safe Eyes due to system suspend") + self.plugins_manager.stop() + self.safe_eyes_core.stop() + else: + # Resume from sleep + if self.active and self.safe_eyes_core.has_breaks(): + logging.info("Resume Safe Eyes after system wakeup") + self.plugins_manager.start() + self.safe_eyes_core.start() + + def handle_system_suspend(self): + """ + Setup system suspend listener. + """ + DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + bus.add_signal_receiver(self.handle_suspend_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1') + + def on_skipped(self): + """ + 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. + """ + 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. + """ + self.settings_dialog_active = False + logging.info("Saving settings to safeeyes.json") + + # Stop the Safe Eyes core + if self.active: + self.plugins_manager.stop() + self.safe_eyes_core.stop() + + # Write the configuration to file + config.save() + self.persist_session() + + logging.info("Initialize SafeEyesCore with modified settings") + + # Restart the core and intialize the components + self.config = config + self.safe_eyes_core.initialize(config) + self.break_screen.initialize(config) + self.plugins_manager.init(self.context, self.config) + if self.active and self.safe_eyes_core.has_breaks(): + # 1 sec delay is required to give enough time for core to be stopped + Timer(1.0, self.safe_eyes_core.start).start() + self.plugins_manager.start() + + def enable_safeeyes(self, scheduled_next_break_time=-1): + """ + Listen to tray icon enable action and send the signal to core. + """ + if not self.active and self.safe_eyes_core.has_breaks(): + self.active = True + self.safe_eyes_core.start(scheduled_next_break_time) + self.plugins_manager.start() + + def disable_safeeyes(self): + """ + 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() + + def start_break(self, break_obj): + """ + Pass the break information to plugins and break screen. + """ + if not self.plugins_manager.start_break(break_obj): + return False + # Get the HTML widgets content from plugins + widget = self.plugins_manager.get_break_screen_widgets(break_obj) + self.break_screen.show_message(break_obj, widget) + return True + + def countdown(self, countdown, seconds): + """ + 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_time): + """ + Update the next break to plugins and save the session. + """ + self.plugins_manager.update_next_break(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. + """ + self.break_screen.close() + self.plugins_manager.stop_break() + return True + + def take_break(self): + """ + Take a break now. + """ + self.safe_eyes_core.take_break() + + 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']) + else: + Utility.delete(Utility.SESSION_FILE_PATH) + \ No newline at end of file diff --git a/safeeyes/SafeEyesCore.py b/safeeyes/SafeEyesCore.py index ce14ce4..20b3307 100644 --- a/safeeyes/SafeEyesCore.py +++ b/safeeyes/SafeEyesCore.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Safe Eyes is a utility to remind you to take break frequently # to protect your eyes from eye strain. @@ -15,338 +16,329 @@ # 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. +""" +import datetime +import logging +import threading +import time -import time, datetime, threading, logging from safeeyes import Utility +from safeeyes.model import Break +from safeeyes.model import BreakType +from safeeyes.model import EventHook +from safeeyes.model import State -class SafeEyesCore: - """ - Core of Safe Eyes which runs the scheduler and notifies the breaks. - """ +class SafeEyesCore(object): + """ + Core of Safe Eyes runs the scheduler and notifies the breaks. + """ - def __init__(self, context, show_notification, start_break, end_break, on_countdown, update_next_break_info): - # Initialize the variables - self.break_count = -1 - self.long_break_message_index = -1 - self.short_break_message_index = -1 - self.active = False - self.running = False - self.show_notification = show_notification - self.start_break = start_break - self.end_break = end_break - self.on_countdown = on_countdown - self.update_next_break_info = update_next_break_info - self.notification_condition = threading.Condition() - self.idle_condition = threading.Condition() - self.lock = threading.Lock() - self.context = context - self.context['skipped'] = False - self.context['postponed'] = False + def __init__(self, context): + """ + Create an instance of SafeEyesCore and initialize the variables. + """ + self.break_count = 0 + self.break_interval = 0 + self.breaks = None + self.long_break_duration = 0 + self.next_break_index = context['session'].get('next_break_index', 0) + self.postpone_duration = 0 + self.pre_break_warning_time = 0 + self.running = False + self.short_break_duration = 0 + self.scheduled_next_break_time = -1 + # This event is fired before for a break + self.on_pre_break = EventHook() + # This event is fired at the start of a break + self.on_start_break = EventHook() + # This event is fired during every count down + self.on_count_down = EventHook() + # This event is fired at the end of a break + self.on_stop_break = EventHook() + # This event is fired when deciding the next break time + self.on_update_next_break = EventHook() + self.waiting_condition = threading.Condition() + self.lock = threading.Lock() + self.context = context + self.context['skipped'] = False + self.context['postponed'] = False + self.context['state'] = State.WAITING + self.context['new_cycle'] = False - def initialize(self, config, language): - """ - Initialize the internal properties from configuration - """ - logging.info("Initialize the core") - self.short_break_exercises = [] # language['exercises']['short_break_exercises'] - self.long_break_exercises = [] # language['exercises']['long_break_exercises'] + def initialize(self, config): + """ + Initialize the internal properties from configuration + """ + logging.info("Initialize the core") + self.breaks = [] + self.pre_break_warning_time = config.get('pre_break_warning_time') + self.long_break_duration = config.get('long_break_duration') + self.short_break_duration = config.get('short_break_duration') + self.break_interval = config.get('break_interval') + self.postpone_duration = config.get('postpone_duration') - self.no_of_short_breaks_per_long_break = config['no_of_short_breaks_per_long_break'] - self.pre_break_warning_time = config['pre_break_warning_time'] - self.long_break_duration = config['long_break_duration'] - self.short_break_duration = config['short_break_duration'] - self.break_interval = config['break_interval'] - self.idle_time = config['idle_time'] - self.postpone_duration = config['postpone_duration'] - self.skip_break_window_classes = [x.lower() for x in config['active_window_class']['skip_break']] - self.take_break_window_classes = [x.lower() for x in config['active_window_class']['take_break']] - self.custom_exercises = config['custom_exercises'] - # Enable idle time pause only if xprintidle is available - self.context['idle_pause_enabled'] = Utility.command_exist('xprintidle') + self.__init_breaks(BreakType.SHORT_BREAK, config.get('short_breaks'), config.get('no_of_short_breaks_per_long_break')) + self.__init_breaks(BreakType.LONG_BREAK, config.get('long_breaks'), config.get('no_of_short_breaks_per_long_break')) + self.break_count = len(self.breaks) + if self.break_count == 0: + # No breaks found + return + self.next_break_index = (self.next_break_index) % self.break_count + self.context['session']['next_break_index'] = self.next_break_index - exercises = language['exercises'] - for short_break_config in config['short_breaks']: - exercise_name = short_break_config['name'] - name = None + def start(self, next_break_time=-1): + """ + Start Safe Eyes is it is not running already. + """ + if not self.has_breaks(): + return + with self.lock: + if not self.running: + logging.info("Start Safe Eyes core") + self.running = True + self.scheduled_next_break_time = int(next_break_time) + Utility.start_thread(self.__scheduler_job) - if exercise_name in self.custom_exercises: - name = self.custom_exercises[exercise_name] - else: - name = exercises[exercise_name] + def stop(self): + """ + Stop Safe Eyes if it is running. + """ + with self.lock: + if not self.running: + return - break_time = short_break_config.get('time', self.short_break_duration) - audible_alert = short_break_config.get('audible_alert', config['audible_alert']) - image = short_break_config.get('image') + logging.info("Stop Safe Eye core") - # Validate time value - if not isinstance(break_time, int) or break_time <= 0: - logging.error('Invalid time in short break: ' + str(short_break_config)) - continue + # Prevent resuming from a long break + if self.has_breaks() and self.__is_long_break(): + # Next break will be a long break. + self.__select_next_break() - self.short_break_exercises.append([name, break_time, audible_alert, image]) + # Stop the break thread + self.waiting_condition.acquire() + self.running = False + if self.context['state'] != State.QUIT: + self.context['state'] = State.STOPPED + self.waiting_condition.notify_all() + self.waiting_condition.release() - for long_break_config in config['long_breaks']: - exercise_name = long_break_config['name'] - name = None + def skip(self): + """ + User skipped the break using Skip button + """ + self.context['skipped'] = True - if exercise_name in self.custom_exercises: - name = self.custom_exercises[exercise_name] - else: - name = exercises[exercise_name] + def postpone(self): + """ + User postponed the break using Postpone button + """ + self.context['postponed'] = True - break_time = long_break_config.get('time', self.long_break_duration) - audible_alert = long_break_config.get('audible_alert', config['audible_alert']) - image = long_break_config.get('image') + def take_break(self): + """ + Calling this method stops the scheduler and show the next break screen + """ + if not self.has_breaks(): + return + if not self.context['state'] == State.WAITING: + return + Utility.start_thread(self.__take_break) - # Validate time value - if not isinstance(break_time, int) or break_time <= 0: - logging.error('Invalid time in long break: ' + str(long_break_config)) - continue + def has_breaks(self): + """ + Check whether Safe Eyes has breaks or not. + """ + return bool(self.breaks) - self.long_break_exercises.append([name, break_time, audible_alert, image]) + def __take_break(self): + """ + Show the next break screen + """ + logging.info('Take a break due to external request') - def start(self): - """ - Start Safe Eyes is it is not running already. - """ - with self.lock: - if not self.active: - logging.info("Scheduling next break") - self.active = True - self.running = True - Utility.start_thread(self.__scheduler_job) - if self.context['idle_pause_enabled']: - Utility.start_thread(self.__start_idle_monitor) + with self.lock: + if not self.running: + return - def stop(self): - """ - Stop Safe Eyes if it is running. - """ - with self.lock: - if self.active: - logging.info("Stop the core") + logging.info("Stop the scheduler") - # Prevent resuming from a long break - current_break_count = self.break_count = 0 - self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break) - if self.__is_long_break(): - # Next break will be a long break. Leave the increment so that next break will be short break after the long - pass - else: - # Reset to the current state - self.break_count = current_break_count + # Stop the break thread + self.waiting_condition.acquire() + self.running = False + self.waiting_condition.notify_all() + self.waiting_condition.release() + time.sleep(1) # Wait for 1 sec to ensure the sceduler is dead + self.running = True - # Stop the break thread - self.notification_condition.acquire() - self.active = False - self.running = False - self.notification_condition.notify_all() - self.notification_condition.release() + self.context['new_cycle'] = self.next_break_index == 0 + Utility.execute_main_thread(self.__fire_start_break) - # Stop the idle monitor - self.idle_condition.acquire() - self.idle_condition.notify_all() - self.idle_condition.release() + def __scheduler_job(self): + """ + Scheduler task to execute during every interval + """ + if not self.running: + return - def pause(self): - """ - Pause Safe Eyes if it is running. - """ - with self.lock: - if self.active and self.running: - self.notification_condition.acquire() - self.running = False - self.notification_condition.notify_all() - self.notification_condition.release() + self.context['state'] = State.WAITING + time_to_wait = self.break_interval # In minutes - def resume(self): - """ - Resume Safe Eyes if it is not running. - """ - with self.lock: - if self.active and not self.running: - self.running = True - Utility.start_thread(self.__scheduler_job) + if self.context['postponed']: + # Wait until the postpone time + time_to_wait = self.postpone_duration + self.context['postponed'] = False - def skip_break(self): - """ - User skipped the break using Skip button - """ - self.context['skipped'] = True + current_time = datetime.datetime.now() + current_timestamp = int(current_time.timestamp()) + if current_timestamp < self.scheduled_next_break_time: + time_to_wait = int((self.scheduled_next_break_time - current_timestamp) / 60) + self.scheduled_next_break_time = -1 - def postpone_break(self): - """ - User postponed the break using Postpone button - """ - self.context['postponed'] = True + next_break_time = current_time + datetime.timedelta(minutes=time_to_wait) + self.on_update_next_break.fire(next_break_time) - def __scheduler_job(self): - """ - Scheduler task to execute during every interval - """ - if not self.__is_running(): - return + if self.__is_long_break(): + self.context['break_type'] = 'long' + else: + self.context['break_type'] = 'short' - time_to_wait = self.break_interval # In minutes + # Wait for the pre break warning period + logging.info("Waiting for %s minutes until next break", time_to_wait) + self.__wait_for(time_to_wait * 60) # Convert to seconds - if self.context['postponed']: - # Reduce the break count by 1 to show the same break again - if self.break_count == 0: - self.break_count = -1 - else: - self.break_count = ((self.break_count - 1) % self.no_of_short_breaks_per_long_break) - if self.__is_long_break(): - self.long_break_message_index = (self.long_break_message_index - 1) % len(self.long_break_exercises) - else: - self.short_break_message_index = (self.short_break_message_index - 1) % len(self.short_break_exercises) + logging.info("Pre-break waiting is over") - # Wait until the postpone time - time_to_wait = self.postpone_duration - self.context['postponed'] = False + if not self.running: + return - next_break_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait) - self.update_next_break_info(next_break_time) + self.context['new_cycle'] = self.next_break_index == 0 + Utility.execute_main_thread(self.__fire_pre_break) - self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break) - if self.__is_long_break(): - self.context['break_type'] = 'long' - else: - self.context['break_type'] = 'short' + def __fire_pre_break(self): + """ + Show the notification and start the break after the notification. + """ + self.context['state'] = State.PRE_BREAK + if not self.on_pre_break.fire(self.breaks[self.next_break_index]): + # Plugins wanted to ignore this break + self.__start_next_break() + return + Utility.start_thread(self.__wait_until_prepare) - # Wait for the pre break warning period - logging.info("Pre-break waiting for {} minutes".format(time_to_wait)) - self.notification_condition.acquire() - self.notification_condition.wait(time_to_wait * 60) # Convert to seconds - self.notification_condition.release() + def __wait_until_prepare(self): + 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: + return + Utility.execute_main_thread(self.__fire_start_break) - logging.info("Pre-break waiting is over") + def __fire_start_break(self): + # Show the break screen + if not self.on_start_break.fire(self.breaks[self.next_break_index]): + # Plugins wanted to ignore this break + self.__start_next_break() + return + Utility.start_thread(self.__start_break) - if not self.__is_running(): - return + def __start_break(self): + """ + Start the break screen. + """ + self.context['state'] = State.BREAK + break_obj = self.breaks[self.next_break_index] + countdown = break_obj.time + total_break_time = countdown - logging.info("Ready to show the break") + while countdown and self.running and not self.context['skipped'] and not self.context['postponed']: + seconds = total_break_time - countdown + self.on_count_down.fire(countdown, seconds) + time.sleep(1) # Sleep for 1 second + countdown -= 1 + Utility.execute_main_thread(self.__fire_stop_break) - self.is_before_break = False - Utility.execute_main_thread(self.__check_active_window) + 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']: + logging.info("Break is terminated automatically") + self.on_stop_break.fire() - def __show_notification(self): - """ - Show the notification and start the break after the notification. - """ - # Show the notification - self.show_notification() + # Reset the skipped flag + self.context['skipped'] = False + self.__start_next_break() - logging.info("Wait for {} seconds which is the time to prepare".format(self.pre_break_warning_time)) - # Wait for the pre break warning period - self.notification_condition.acquire() - self.notification_condition.wait(self.pre_break_warning_time) - self.notification_condition.release() + def __wait_for(self, duration): + """ + Wait until someone wake up or the timeout happens. + """ + self.waiting_condition.acquire() + self.waiting_condition.wait(duration) + self.waiting_condition.release() - self.is_before_break = True - Utility.execute_main_thread(self.__check_active_window) + def __select_next_break(self): + """ + Select the next break. + """ + self.next_break_index = (self.next_break_index + 1) % self.break_count + self.context['session']['next_break_index'] = self.next_break_index - def __check_active_window(self): - """ - Check the active window for full-screen and user defined exceptions. - """ - # Check the active window again. (User might changed the window) - if self.__is_running() and Utility.is_active_window_skipped(self.skip_break_window_classes, self.take_break_window_classes, self.is_before_break): - # If full screen app found, do not show break screen - logging.info("Found a skip_break or full-screen window. Skip the break") - if self.__is_running(): - # Schedule the break again - Utility.start_thread(self.__scheduler_job) - return + def __is_long_break(self): + """ + Check if the next break is long break. + """ + return self.breaks[self.next_break_index].type is BreakType.LONG_BREAK - # Execute the post-operation - if self.is_before_break: - Utility.start_thread(self.__start_break) - else: - Utility.start_thread(self.__show_notification) + def __start_next_break(self): + if not self.context['postponed']: + self.__select_next_break() - def __start_break(self): - """ - Start the break screen. - """ - # User can disable SafeEyes during notification - if self.__is_running(): - message = "" - image = None - seconds = 0 - audible_alert = None - if self.__is_long_break(): - logging.info("Count is {}; get a long beak message".format(self.break_count)) - self.long_break_message_index = (self.long_break_message_index + 1) % len(self.long_break_exercises) - message = self.long_break_exercises[self.long_break_message_index][0] - seconds = self.long_break_exercises[self.long_break_message_index][1] - audible_alert = self.long_break_exercises[self.long_break_message_index][2] - image = self.long_break_exercises[self.long_break_message_index][3] - else: - logging.info("Count is {}; get a short beak message".format(self.break_count)) - self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises) - message = self.short_break_exercises[self.short_break_message_index][0] - seconds = self.short_break_exercises[self.short_break_message_index][1] - audible_alert = self.short_break_exercises[self.short_break_message_index][2] - image = self.short_break_exercises[self.short_break_message_index][3] + if self.running: + # Schedule the break again + Utility.start_thread(self.__scheduler_job) - self.context['break_length'] = seconds - self.context['audible_alert'] = audible_alert - total_break_time = seconds + def __init_breaks(self, break_type, break_configs, short_breaks_per_long_break=0): + """ + Fill the self.breaks using short and local breaks. + """ + # Defin the default break time + default_break_time = self.short_break_duration - # Show the break screen - self.start_break(message, image) + # Duplicate short breaks to equally distribute the long breaks + if break_type is BreakType.LONG_BREAK: + if self.breaks: + default_break_time = self.long_break_duration + required_short_breaks = short_breaks_per_long_break * len(break_configs) + no_of_short_breaks = len(self.breaks) + short_break_index = 0 + while no_of_short_breaks < required_short_breaks: + self.breaks.append(self.breaks[short_break_index]) + short_break_index += 1 + no_of_short_breaks += 1 + else: + # If there are no short breaks, extend the break interval according to long break interval + self.break_interval = int(self.break_interval * short_breaks_per_long_break) - # Use self.active instead of self.__is_running to avoid idle pause interrupting the break - while seconds and self.active and not self.context['skipped'] and not self.context['postponed']: - count_down = total_break_time - seconds - self.context['count_down'] = count_down - self.on_countdown(count_down, seconds) - time.sleep(1) # Sleep for 1 second - seconds -= 1 + iteration = 1 + for break_config in break_configs: + name = _(break_config['name']) + break_time = break_config.get('duration', default_break_time) + image = break_config.get('image') + plugins = break_config.get('plugins', None) - # Loop terminated because of timeout (not skipped) -> Close the break alert - if not self.context['skipped'] and not self.context['postponed']: - logging.info("Break is terminated automatically") - self.end_break(audible_alert) + # Validate time value + if not isinstance(break_time, int) or break_time <= 0: + logging.error('Invalid time in break: ' + str(break_config)) + continue - # Reset the skipped flag - self.context['skipped'] = False - - # Resume - if self.__is_running(): - # Schedule the break again - Utility.start_thread(self.__scheduler_job) - - def __is_running(self): - """ - Tells whether Safe Eyes is running or not. - """ - return self.active and self.running - - def __is_long_break(self): - """ - Check if the current break is long break or short current - """ - return self.break_count == self.no_of_short_breaks_per_long_break - 1 - - def __start_idle_monitor(self): - """ - Continuously check the system idle time and pause/resume Safe Eyes based on it. - """ - while self.active: - # Wait for 2 seconds - self.idle_condition.acquire() - self.idle_condition.wait(2) - self.idle_condition.release() - - if self.active: - # Get the system idle time - system_idle_time = Utility.system_idle_time() - if system_idle_time >= self.idle_time and self.running: - logging.info('Pause Safe Eyes due to system idle') - self.pause() - elif system_idle_time < self.idle_time and not self.running: - logging.info('Resume Safe Eyes due to user activity') - self.resume() + break_obj = Break(break_type, name, break_time, image, plugins) + if break_type is BreakType.SHORT_BREAK: + self.breaks.append(break_obj) + else: + # Long break + index = iteration * (short_breaks_per_long_break + 1) - 1 + self.breaks.insert(index, break_obj) + iteration += 1 diff --git a/safeeyes/SettingsDialog.py b/safeeyes/SettingsDialog.py deleted file mode 100644 index 476ace9..0000000 --- a/safeeyes/SettingsDialog.py +++ /dev/null @@ -1,226 +0,0 @@ -# Safe Eyes is a utility to remind you to take break frequently -# to protect your eyes from eye strain. - -# Copyright (C) 2016 Gobinath - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GObject -from safeeyes import Utility - - -class SettingsDialog: - """ - Create and initialize SettingsDialog instance. - """ - def __init__(self, config, language, languages, able_to_lock_screen, on_save_settings, glade_file): - self.config = config - self.on_save_settings = on_save_settings - self.languages = [] - self.language = language - - builder = Gtk.Builder() - builder.add_from_file(glade_file) - builder.connect_signals(self) - - xprintidle_available = Utility.command_exist('xprintidle') - - # Get the UI components - self.window = builder.get_object('window_settings') - 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_interval_between_two_breaks = builder.get_object('spin_interval_between_two_breaks') - self.spin_short_between_long = builder.get_object('spin_short_between_long') - self.spin_time_to_prepare = builder.get_object('spin_time_to_prepare') - self.spin_idle_time_to_pause = builder.get_object('spin_idle_time_to_pause') - self.spin_postpone_duration = builder.get_object('spin_postpone_duration') - self.spin_disable_keyboard_shortcut = builder.get_object('spin_disable_keyboard_shortcut') - self.switch_show_time_in_tray = builder.get_object('switch_show_time_in_tray') - self.switch_strict_break = builder.get_object('switch_strict_break') - self.switch_postpone = builder.get_object('switch_postpone') - self.switch_audible_alert = builder.get_object('switch_audible_alert') - self.cmb_language = builder.get_object('cmb_language') - self.switch_screen_lock = builder.get_object('switch_screen_lock') - self.spin_time_to_screen_lock = builder.get_object('spin_time_to_screen_lock') - - # Translate the UI labels - builder.get_object('lbl_short_break').set_label(language['ui_controls']['short_break_duration']) - builder.get_object('lbl_long_break').set_label(language['ui_controls']['long_break_duration']) - builder.get_object('lbl_interval_bettween_breaks').set_label(language['ui_controls']['interval_between_two_breaks']) - builder.get_object('lbl_short_per_long').set_label(language['ui_controls']['no_of_short_breaks_between_two_long_breaks']) - builder.get_object('lbl_time_to_prepare').set_label(language['ui_controls']['time_to_prepare_for_break']) - builder.get_object('lbl_idle_time_to_pause').set_label(language['ui_controls']['idle_time']) - builder.get_object('lbl_postpone_duration').set_label(language['ui_controls']['postpone_duration']) - builder.get_object('lbl_allow_postpone').set_label(language['ui_controls']['allow_postpone']) - builder.get_object('lbl_disable_keyboard_shortcut').set_label(language['ui_controls']['disable_keyboard_shortcut']) - builder.get_object('lbl_show_time_in_tray').set_label(language['ui_controls']['show_time_in_tray']) - builder.get_object('lbl_strict_break').set_label(language['ui_controls']['strict_break']) - builder.get_object('lbl_audible_alert').set_label(language['ui_controls']['audible_alert']) - builder.get_object('lbl_language').set_label(language['ui_controls']['language']) - builder.get_object('lbl_enable_screen_lock').set_label(language['ui_controls']['enable_screen_lock']) - builder.get_object('lbl_lock_screen_after').set_label(language['ui_controls']['time_to_screen_lock']) - builder.get_object('btn_cancel').set_label(language['ui_controls']['cancel']) - builder.get_object('btn_save').set_label(language['ui_controls']['save']) - - # Set the current values of input fields - self.spin_short_break_duration.set_value(config['short_break_duration']) - self.spin_long_break_duration.set_value(config['long_break_duration']) - self.spin_interval_between_two_breaks.set_value(config['break_interval']) - self.spin_short_between_long.set_value(config['no_of_short_breaks_per_long_break']) - self.spin_time_to_prepare.set_value(config['pre_break_warning_time']) - self.spin_idle_time_to_pause.set_value(config['idle_time'] and xprintidle_available) - self.spin_postpone_duration.set_value(config['postpone_duration']) - self.spin_disable_keyboard_shortcut.set_value(config['shortcut_disable_time']) - self.switch_show_time_in_tray.set_active(config['show_time_in_tray']) - self.switch_strict_break.set_active(config['strict_break']) - self.switch_audible_alert.set_active(config['audible_alert'] and Utility.pyaudio is None) - self.spin_time_to_screen_lock.set_value(config['time_to_screen_lock']) - - # Enable idle_time_to_pause only if xprintidle is available - self.spin_idle_time_to_pause.set_sensitive(xprintidle_available) - - self.switch_screen_lock.set_sensitive(able_to_lock_screen) - self.switch_screen_lock.set_active(able_to_lock_screen and config['enable_screen_lock']) - self.switch_postpone.set_active(config['allow_postpone'] and not config['strict_break']) - - # Update relative states - # GtkSwitch state-set signal is available only from 3.14 - if Gtk.get_minor_version() >= 14: - self.switch_strict_break.connect('state-set', self.on_switch_strict_break_activate) - self.switch_screen_lock.connect('state-set', self.on_switch_screen_lock_activate) - self.switch_postpone.connect('state-set', self.on_switch_postpone_activate) - self.on_switch_strict_break_activate(self.switch_strict_break, self.switch_strict_break.get_active()) - self.on_switch_screen_lock_activate(self.switch_screen_lock, self.switch_screen_lock.get_active()) - self.on_switch_postpone_activate(self.switch_postpone, self.switch_postpone.get_active()) - - if Utility.pyaudio is None: - self.switch_audible_alert.connect('state-set', self.on_switch_audible_alert_activate) - - # Initialize the language combobox - language_list_store = Gtk.ListStore(GObject.TYPE_STRING) - language_index = 2 - lang_code = config['language'] - - # Add 'System Language' as the first option - language_list_store.append([language['ui_controls']['system_language']]) - language_list_store.append(['-']) - self.languages.append('system') - self.languages.append('system') # Dummy record for row separator - if 'system' == lang_code: - self.cmb_language.set_active(0) - - for key in sorted(languages.keys()): - language_list_store.append([languages[key]]) - self.languages.append(key) - if key == lang_code: - self.cmb_language.set_active(language_index) - language_index += 1 - - self.cmb_language.set_model(language_list_store) - self.cmb_language.set_row_separator_func(lambda m, i: m.get_value(i, 0) == '-') - cell = Gtk.CellRendererText() - self.cmb_language.pack_start(cell, True) - self.cmb_language.add_attribute(cell, 'text', 0) - - def show(self): - """ - Show the SettingsDialog. - """ - self.window.show_all() - - def on_switch_screen_lock_activate(self, switch, state): - """ - Event handler to the state change of the screen_lock switch. - Enable or disable the self.spin_time_to_screen_lock based on the state of the screen_lock switch. - """ - self.spin_time_to_screen_lock.set_sensitive(self.switch_screen_lock.get_active()) - - def on_switch_strict_break_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. - """ - strict_break_enable = state # self.switch_strict_break.get_active() - self.switch_postpone.set_sensitive(not strict_break_enable) - if strict_break_enable: - self.switch_postpone.set_active(False) - - 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. - """ - self.spin_postpone_duration.set_sensitive(self.switch_postpone.get_active()) - - def on_switch_audible_alert_activate(self, switch, state): - """ - Event handler to the state change of the audible_alert switch. - Show the information message dialog to install pyaudio if not installed. - """ - if state and Utility.pyaudio is None: - self.__show_message_dialog(self.language['messages']['audible_alert_disabled'], self.language['messages']['software_required'].format('pyaudio')) - switch.emit_stop_by_name('state-set') - self.switch_audible_alert.set_active(False) - - def on_window_delete(self, *args): - """ - Event handler for Settings dialog close action. - """ - self.window.destroy() - - def on_save_clicked(self, button): - """ - Event handler for Save button click. - """ - self.config['short_break_duration'] = self.spin_short_break_duration.get_value_as_int() - self.config['long_break_duration'] = self.spin_long_break_duration.get_value_as_int() - self.config['break_interval'] = self.spin_interval_between_two_breaks.get_value_as_int() - self.config['no_of_short_breaks_per_long_break'] = self.spin_short_between_long.get_value_as_int() - self.config['pre_break_warning_time'] = self.spin_time_to_prepare.get_value_as_int() - self.config['idle_time'] = self.spin_idle_time_to_pause.get_value_as_int() - self.config['postpone_duration'] = self.spin_postpone_duration.get_value_as_int() - self.config['shortcut_disable_time'] = self.spin_disable_keyboard_shortcut.get_value_as_int() - self.config['show_time_in_tray'] = self.switch_show_time_in_tray.get_active() - self.config['strict_break'] = self.switch_strict_break.get_active() - self.config['language'] = self.languages[self.cmb_language.get_active()] - self.config['time_to_screen_lock'] = self.spin_time_to_screen_lock.get_value_as_int() - self.config['enable_screen_lock'] = self.switch_screen_lock.get_active() - self.config['allow_postpone'] = self.switch_postpone.get_active() - # Check if pyaudio is installed when turning audible notifications on - if self.switch_audible_alert.get_active() and not Utility.pyaudio: - # Notify user that pyaudio is not installed - self.__show_message_dialog(self.language['messages']['audible_alert_disabled'], self.language['messages']['software_required'].format('pyaudio')) - self.config['audible_alert'] = False - else: - self.config['audible_alert'] = self.switch_audible_alert.get_active() - - self.on_save_settings(self.config) # Call the provided save method - self.window.destroy() # Close the settings window - - def on_cancel_clicked(self, button): - """ - Event handler for Cancel button click. - """ - self.window.destroy() - - def __show_message_dialog(self, primary_text, secondary_text): - """ - Show a popup message dialog. - """ - dialog = Gtk.MessageDialog(self.window, 0, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, primary_text) - dialog.format_secondary_text(secondary_text) - dialog.run() - dialog.destroy() diff --git a/safeeyes/TrayIcon.py b/safeeyes/TrayIcon.py deleted file mode 100644 index 08ea21a..0000000 --- a/safeeyes/TrayIcon.py +++ /dev/null @@ -1,299 +0,0 @@ -# Safe Eyes is a utility to remind you to take break frequently -# to protect your eyes from eye strain. - -# Copyright (C) 2016 Gobinath - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import gi, logging, threading, datetime -gi.require_version('Gtk', '3.0') -gi.require_version('AppIndicator3', '0.1') -from gi.repository import Gtk -from gi.repository import AppIndicator3 as appindicator -from safeeyes import Utility - -# Global variables -APPINDICATOR_ID = 'safeeyes' - - -class TrayIcon: - """ - Create and show the tray icon along with the tray menu. - """ - - def __init__(self, config, language, on_show_settings, on_show_about, on_enable, on_disable, on_quite): - logging.info("Initialize the tray icon") - self.on_show_settings = on_show_settings - self.on_show_about = on_show_about - self.on_quite = on_quite - self.on_enable = on_enable - self.on_disable = on_disable - self.language = language - self.dateTime = None - self.active = True - self.wakeup_time = None - self.idle_condition = threading.Condition() - self.lock = threading.Lock() - - # Construct the tray icon - self.indicator = appindicator.Indicator.new( - APPINDICATOR_ID, "safeeyes_enabled", appindicator.IndicatorCategory.APPLICATION_STATUS) - self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) - - self.initialize(config) - - # Construct the context menu - self.menu = Gtk.Menu() - - # Next break info menu item - self.item_info = Gtk.ImageMenuItem() - img_timer = Gtk.Image() - img_timer.set_from_icon_name("safeeyes_timer", 16) - self.item_info.set_image(img_timer) - - self.item_separator = Gtk.SeparatorMenuItem() - - self.item_enable = Gtk.MenuItem() - self.item_enable.connect('activate', self.on_enable_clicked) - - self.item_disable = Gtk.MenuItem() - self.item_disable.connect('activate', self.on_disable_clicked) - - self.sub_menu_disable = Gtk.Menu() - self.sub_menu_items = [] - - # Read disable options and build the sub menu - for disable_option in config['disable_options']: - time_in_minutes = 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)) - continue - time_unit = disable_option['unit'].lower() - if time_unit == 'seconds' or time_unit == 'second': - time_in_minutes = int(time_in_minutes / 60) - elif time_unit == 'minutes' or time_unit == 'minute': - time_in_minutes = int(time_in_minutes * 1) - elif time_unit == 'hours' or time_unit == 'hour': - time_in_minutes = int(time_in_minutes * 60) - else: - # Invalid unit - logging.error('Invalid unit in disable option: ' + str(disable_option)) - continue - - # Create submenu - sub_menu_item = Gtk.MenuItem() - sub_menu_item.connect('activate', self.on_disable_clicked, time_in_minutes) - self.sub_menu_items.append([sub_menu_item, disable_option['label'], disable_option['time']]) - self.sub_menu_disable.append(sub_menu_item) - - # Disable until restart submenu - self.sub_menu_item_until_restart = Gtk.MenuItem() - self.sub_menu_item_until_restart.connect('activate', self.on_disable_clicked, -1) - self.sub_menu_disable.append(self.sub_menu_item_until_restart) - - # Add the sub menu to the enable/disable menu - self.item_disable.set_submenu(self.sub_menu_disable) - - # Settings menu item - self.item_settings = Gtk.MenuItem() - self.item_settings.connect('activate', self.show_settings) - - # About menu item - self.item_about = Gtk.MenuItem() - self.item_about.connect('activate', self.show_about) - - # Quit menu item - self.item_quit = Gtk.MenuItem() - self.item_quit.connect('activate', self.quit_safe_eyes) - - self.set_labels(language) - - # At startup, no need for activate menu - self.item_enable.set_sensitive(False) - - # Append all menu items and show the menu - self.menu.append(self.item_info) - self.menu.append(self.item_separator) - self.menu.append(self.item_enable) - self.menu.append(self.item_disable) - self.menu.append(self.item_settings) - self.menu.append(self.item_about) - self.menu.append(self.item_quit) - self.menu.show_all() - - self.indicator.set_menu(self.menu) - - def initialize(self, config): - """ - Initialize the tray icon by setting the config. - """ - self.config = config - - def set_labels(self, language): - """ - Update the text of menu items based on the selected language. - """ - self.language = language - for entry in self.sub_menu_items: - entry[0].set_label(self.language['ui_controls'][entry[1]].format(entry[2])) - - self.sub_menu_item_until_restart.set_label(self.language['ui_controls']['until_restart']) - self.item_enable.set_label(self.language['ui_controls']['enable']) - self.item_disable.set_label(self.language['ui_controls']['disable']) - - if self.active: - if self.dateTime: - self.__set_next_break_info() - else: - if self.wakeup_time: - self.item_info.set_label(self.language['messages']['disabled_until_x'].format(Utility.format_time(self.wakeup_time))) - else: - self.item_info.set_label(self.language['messages']['disabled_until_restart']) - - self.item_settings.set_label(self.language['ui_controls']['settings']) - self.item_about.set_label(self.language['ui_controls']['about']) - self.item_quit.set_label(self.language['ui_controls']['quit']) - - def show_icon(self): - """ - Show the tray icon. - """ - Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.ACTIVE) - - def hide_icon(self): - """ - Hide the tray icon. - """ - Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.PASSIVE) - - def quit_safe_eyes(self, *args): - """ - Handle Quit menu action. - This action terminates the application. - """ - self.on_quite() - with self.lock: - self.active = True - # Notify all schedulers - self.idle_condition.acquire() - self.idle_condition.notify_all() - self.idle_condition.release() - - def show_settings(self, *args): - """ - Handle Settings menu action. - This action shows the Settings dialog. - """ - self.on_show_settings() - - def show_about(self, *args): - """ - 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. - """ - logging.info("Update next break information") - self.dateTime = dateTime - self.__set_next_break_info() - - def __set_next_break_info(self): - """ - A private method to be called within this class to update the next break information using self.dateTime. - """ - formatted_time = Utility.format_time(self.dateTime) - message = self.language['messages']['next_break_at'].format(formatted_time) - # Update the tray icon label - if self.config.get('show_time_in_tray', False): - self.indicator.set_label(formatted_time, '') - else: - self.indicator.set_label('', '') - # Update the menu item label - Utility.execute_main_thread(self.item_info.set_label, message) - - def on_enable_clicked(self, *args): - """ - Handle 'Enable Safe Eyes' menu action. - This action enables the application if it is currently disabled. - """ - # active = self.item_enable.get_active() - if not self.active: - with self.lock: - logging.info('Enable Safe Eyes') - self.active = True - self.indicator.set_icon("safeeyes_enabled") - self.item_info.set_sensitive(True) - self.item_enable.set_sensitive(False) - self.item_disable.set_sensitive(True) - self.on_enable() - # Notify all schedulers - self.idle_condition.acquire() - self.idle_condition.notify_all() - self.idle_condition.release() - - def on_disable_clicked(self, *args): - """ - Handle the menu actions of all the sub menus of 'Disable Safe Eyes'. - This action disables the application if it is currently active. - """ - # active = self.item_enable.get_active() - if self.active and len(args) > 1: - logging.info('Disable Safe Eyes') - self.active = False - self.indicator.set_icon("safeeyes_disabled") - self.indicator.set_label('', '') - self.item_info.set_sensitive(False) - self.item_enable.set_sensitive(True) - self.item_disable.set_sensitive(False) - self.on_disable() - - time_to_wait = args[1] - if time_to_wait <= 0: - self.wakeup_time = None - self.item_info.set_label(self.language['messages']['disabled_until_restart']) - else: - self.wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait) - Utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait) - self.item_info.set_label(self.language['messages']['disabled_until_x'].format(Utility.format_time(self.wakeup_time))) - - def lock_menu(self): - """ - This method is called by the core to prevent user from disabling Safe Eyes after the notification. - """ - if self.active: - self.menu.set_sensitive(False) - - def unlock_menu(self): - """ - This method is called by the core to activate the menu after the the break. - """ - if self.active: - self.menu.set_sensitive(True) - - def __schedule_resume(self, time_minutes): - """ - Schedule a local timer to enable Safe Eyes after the given timeout. - """ - self.idle_condition.acquire() - self.idle_condition.wait(time_minutes * 60) # Convert to seconds - self.idle_condition.release() - - with self.lock: - if not self.active: - Utility.execute_main_thread(self.item_enable.activate) diff --git a/safeeyes/Utility.py b/safeeyes/Utility.py index 97fdd10..16f085b 100644 --- a/safeeyes/Utility.py +++ b/safeeyes/Utility.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Safe Eyes is a utility to remind you to take break frequently # to protect your eyes from eye strain. @@ -15,485 +16,482 @@ # 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. +""" -import gi -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, GLib, GdkX11 -from html.parser import HTMLParser +import errno +import imp +import json +import locale +import logging +import os +import shutil +import subprocess +import threading from distutils.version import LooseVersion from logging.handlers import RotatingFileHandler -import babel.dates, os, errno, re, subprocess, threading, logging, locale, json, shutil, wave -bin_directory = os.path.dirname(os.path.realpath(__file__)) -home_directory = os.path.expanduser('~') -system_language_directory = os.path.join(bin_directory, 'config/lang') -config_directory = os.path.join(home_directory, '.config/safeeyes') -config_file_path = os.path.join(config_directory, 'safeeyes.json') -style_sheet_path = os.path.join(config_directory, 'style/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(config_directory, 'safeeyes.log') -pyaudio = None +import babel.dates +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GLib +gi.require_version('Gdk', '3.0') -def import_dependencies(): - """ - Import the optional Python dependencies. - """ - try: - # Import pyaudio if exists - global pyaudio - pyaudio = __import__("pyaudio") - except ImportError: - logging.warning('Install pyaudio for audible notifications.') - - -def play_notification(): - """ - Play the alert.wav - """ - if pyaudio: - logging.info('Playing audible alert') - CHUNK = 1024 - - try: - # Open the sound file - path = get_resource_path('alert.wav') - if path is None: - return - sound = wave.open(path, 'rb') - - # Create a sound stream - wrapper = pyaudio.PyAudio() - stream = wrapper.open(format=wrapper.get_format_from_width( - sound.getsampwidth()), - channels=sound.getnchannels(), - rate=sound.getframerate(), - output=True) - - # Write file data into the sound stream - data = sound.readframes(CHUNK) - while data != b'': - stream.write(data) - data = sound.readframes(CHUNK) - - # Close steam - stream.stop_stream() - stream.close() - sound.close() - wrapper.terminate() - - except Exception as e: - logging.warning('Unable to play audible alert') - logging.exception(e) +BIN_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) +HOME_DIRECTORY = os.path.expanduser('~') +CONFIG_DIRECTORY = os.path.join(HOME_DIRECTORY, '.config/safeeyes') +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(CONFIG_DIRECTORY, 'style/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(CONFIG_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') +DESKTOP_ENVIRONMENT = None 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. - """ - if resource_name is None: - return None - resource_location = os.path.join(config_directory, 'resource', resource_name) - if not os.path.isfile(resource_location): - resource_location = os.path.join(bin_directory, 'resource', resource_name) - if not os.path.isfile(resource_location): - logging.error('Resource not found: ' + resource_name) - resource_location = None + """ + 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) + if not os.path.isfile(resource_location): + # Resource not found + resource_location = None - return resource_location - - -def system_idle_time(): - """ - Get system idle time in minutes. - Return the idle time if xprintidle is available, otherwise return 0. - """ - try: - return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 60000 # Convert to minutes - except: - return 0 + return resource_location def start_thread(target_function, **args): - """ - Execute the function in a separate thread. - """ - thread = threading.Thread(target=target_function, kwargs=args) - thread.start() + """ + Execute the function in a separate thread. + """ + thread = threading.Thread(target=target_function, kwargs=args) + thread.start() def execute_main_thread(target_function, args=None): - """ - Execute the given function in main thread. - """ - if args: - GLib.idle_add(lambda: target_function(args)) - else: - GLib.idle_add(lambda: target_function()) + """ + Execute the given function in main thread. + """ + if args: + GLib.idle_add(lambda: target_function(args)) + else: + GLib.idle_add(target_function) -def is_active_window_skipped(skip_break_window_classes, take_break_window_classes, unfullscreen_allowed=False): - """ - Check for full-screen applications. - This method must be executed by the main thread. If not, it will cause to random failure. - """ - 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'] - - try: - stdout = subprocess.check_output(cmdlist).decode('utf-8') - except subprocess.CalledProcessError: - logging.warning('Error in finding full-screen application') - pass - else: - if stdout: - is_fullscreen = 'FULLSCREEN' in stdout - # Extract the process name - process_names = re.findall('"(.+?)"', stdout) - if process_names: - process = process_names[1].lower() - if process in skip_break_window_classes: - return True - elif process in take_break_window_classes: - if is_fullscreen and unfullscreen_allowed: - try: - active_window.unfullscreen() - except: - logging.error('Error in unfullscreen the window ' + process) - pass - return False - - return is_fullscreen - - return False - - -def __system_locale(): - """ - Return the system locale. If not available, return en_US.UTF-8. - """ - locale.setlocale(locale.LC_ALL, '') - system_locale = locale.getlocale(locale.LC_TIME)[0] - if not system_locale: - system_locale = 'en_US.UTF-8' - return system_locale +def system_locale(): + """ + Return the system locale. If not available, return en_US.UTF-8. + """ + try: + locale.setlocale(locale.LC_ALL, '') + sys_locale = locale.getlocale(locale.LC_TIME)[0] + if not sys_locale: + sys_locale = 'en_US.UTF-8' + return sys_locale + except BaseException: + # Some systems does not return proper locale + return 'en_US.UTF-8' def format_time(time): - """ - Format time based on the system time. - """ - system_locale = __system_locale() - return babel.dates.format_time(time, format='short', locale=system_locale) + """ + Format time based on the system time. + """ + sys_locale = system_locale() + return babel.dates.format_time(time, format='short', locale=sys_locale) def mkdir(path): - """ - 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)) - raise + """ + 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)) + raise -def parse_language_code(lang_code): - """ - Convert the user defined language code to a valid one. - This includes converting to lower case and finding system locale language, - if the given lang_code code is 'system'. - """ - # Convert to lower case - lang_code = str(lang_code).lower() - - # If it is system, use the system language - if lang_code == 'system': - logging.info('Use system language for Safe Eyes') - system_locale = __system_locale() - lang_code = system_locale[0:2].lower() - - # Check whether translation is available for this language. - # If not available, use English by default. - language_file_path = os.path.join(system_language_directory, lang_code + '.json') - if not os.path.exists(language_file_path): - logging.warn('The language {} does not exist. Use English instead'.format(lang_code)) - lang_code = 'en' - - return lang_code +def load_json(json_path): + """ + Load the JSON file from the given path. + """ + json_obj = None + if os.path.isfile(json_path): + try: + with open(json_path) as config_file: + json_obj = json.load(config_file) + except BaseException: + pass + return json_obj -def load_language(lang_code): - """ - Load the desired language from the available list based on the preference. - """ - # Convert the user defined language code to a valid one - lang_code = parse_language_code(lang_code) - - # Construct the translation file path - language_file_path = os.path.join(system_language_directory, lang_code + '.json') - - language = None - # Read the language file and construct the json object - with open(language_file_path) as language_file: - language = json.load(language_file) - - return language +def write_json(json_path, json_obj): + """ + Write the JSON object at the given path + """ + try: + with open(json_path, 'w') as json_file: + json.dump(json_obj, json_file, indent=4, sort_keys=True) + except BaseException: + pass -def read_lang_files(): - """ - Read all the language translations and build a key-value mapping of language names - in English and ISO 639-1 (Filename without extension). - """ - languages = {} - for lang_file_name in os.listdir(system_language_directory): - lang_file_path = os.path.join(system_language_directory, lang_file_name) - if os.path.isfile(lang_file_path): - with open(lang_file_path) as lang_file: - lang = json.load(lang_file) - languages[lang_file_name.lower().replace('.json', '')] = lang['meta_info']['language_name'] +def delete(file_path): + """ + Delete the given file or directory + """ + try: + os.remove(file_path) + except OSError: + pass - return languages + +def check_plugin_dependencies(plugin_config): + """ + Check the plugin dependencies. + """ + # Check the desktop environment + 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 + + # Check the 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']: + if not command_exist(command): + return _("Please install the command-line tool '%s'") % command + + # Check the 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} + + return None + + +def load_plugins_config(safeeyes_config): + """ + 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']) + 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') + 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') + config = load_json(plugin_config_path) + if config is None: + continue + dependency_description = check_plugin_dependencies(config) + if dependency_description: + plugin['enabled'] = False + config['error'] = True + config['meta']['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'] + configs.append(config) + return configs def desktop_environment(): - """ - Detect the desktop environment. - """ - 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 desktop_session in ['gnome', 'unity', 'budgie-desktop', 'cinnamon', 'mate', 'xfce4', 'lxde', 'pantheon', 'fluxbox', 'blackbox', 'openbox', 'icewm', 'jwm', 'afterstep', 'trinity', 'kde']: - return desktop_session - elif (desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop)): - return 'xfce' - elif desktop_session.startswith('ubuntu'): - return 'unity' - elif desktop_session.startswith('lubuntu'): - return 'lxde' - elif 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true': - return 'kde' - elif os.environ.get('GNOME_DESKTOP_SESSION_ID'): - return 'gnome' - return 'unknown' + """ + Detect the desktop environment. + """ + global DESKTOP_ENVIRONMENT + 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']: + 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'): + env = 'gnome' + elif desktop_session.startswith('ubuntu'): + env = 'unity' + DESKTOP_ENVIRONMENT = env + return env -def lock_screen_command(): - """ - 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'] - XFCE: ['xflock4'] - Otherwise: None - """ - 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 command_exist('xflock4'): - return ['xflock4'] - elif desktop_session == 'cinnamon' and command_exist('cinnamon-screensaver-command'): - return ['cinnamon-screensaver-command', '--lock'] - elif (desktop_session == 'pantheon' or desktop_session.startswith('lubuntu')) and command_exist('light-locker-command'): - return ['light-locker-command', '--lock'] - elif desktop_session == 'mate' and 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'): - if command_exist('gnome-screensaver-command'): - return ['gnome-screensaver-command', '--lock'] - else: - # 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 command_exist('gnome-screensaver-command'): - # Gnome 2 - return ['gnome-screensaver-command', '--lock'] - elif command_exist('xscreensaver-command'): - # this will fail if the daemon is not running - return os.system('xscreensaver-command -version > /dev/null') == 0 - return None - - -def lock_desktop(command): - """ - Lock the screen using the predefined commands - """ - if command: - try: - subprocess.Popen(command) - except Exception as e: - logging.error('Error in executing the commad' + str(command) + ' to lock screen') - - -def html_to_text(html): - """ - Convert HTML to plain text - """ - extractor = __HTMLTextExtractor() - extractor.feed(html) - return extractor.get_data() +def execute_command(command, args=[]): + """ + Execute the shell command without waiting for its response. + """ + if command: + command_to_execute = [] + if isinstance(command, str): + command_to_execute.append(command) + else: + command_to_execute.extend(command) + if args: + command_to_execute.extend(args) + try: + subprocess.Popen(command_to_execute) + except BaseException: + logging.error('Error in executing the command ' + str(command)) def command_exist(command): - """ - Check whether the given command exist in the system or not. - """ - if shutil.which(command): - return True - else: - return False + """ + 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. + """ + try: + imp.find_module(module) + return True + except ImportError: + return False def merge_configs(new_config, old_config): - """ - Merge the values of old_config into the new_config. - """ - new_config = new_config.copy() - new_config.update(old_config) - return 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 ~/.config/safeeyes directory. - """ - logging.info('Copy the config files to ~/.config/safeeyes') +def initialize_safeeyes(): + """ + Create the config file and style sheet in ~/.config/safeeyes directory. + """ + logging.info('Copy the config files to ~/.config/safeeyes') - style_dir_path = os.path.join(home_directory, '.config/safeeyes/style') - startup_dir_path = os.path.join(home_directory, '.config/autostart') + style_dir_path = os.path.join(HOME_DIRECTORY, '.config/safeeyes/style') + startup_dir_path = os.path.join(HOME_DIRECTORY, '.config/autostart') - # Remove the ~/.config/safeeyes directory - shutil.rmtree(config_directory, ignore_errors=True) + # Remove the ~/.config/safeeyes directory + delete(os.path.join(CONFIG_DIRECTORY, 'safeeyes.json')) - # Remove the startup file - try: - os.remove(os.path.join(home_directory, os.path.join(startup_dir_path, 'safeeyes.desktop'))) - except: - pass + # Remove the startup file + delete(os.path.join(HOME_DIRECTORY, os.path.join(startup_dir_path, 'safeeyes.desktop'))) - # Create the ~/.config/safeeyes/style directory - mkdir(style_dir_path) - mkdir(startup_dir_path) + # Create the ~/.config/safeeyes/style directory + mkdir(style_dir_path) + mkdir(startup_dir_path) - # Copy the safeeyes.json - shutil.copy2(system_config_file_path, config_file_path) + # Copy the safeeyes.json + shutil.copy2(SYSTEM_CONFIG_FILE_PATH, CONFIG_FILE_PATH) - # Copy the new startup file - try: - os.symlink("/usr/share/applications/safeeyes.desktop", os.path.join(startup_dir_path, 'safeeyes.desktop')) - except OSError as exc: - pass + # Copy the new startup file + try: + os.symlink("/usr/share/applications/safeeyes.desktop", os.path.join(startup_dir_path, 'safeeyes.desktop')) + except OSError: + pass - # Copy the new style sheet - if not os.path.isfile(style_sheet_path): - shutil.copy2(system_style_sheet_path, style_sheet_path) + # Copy the new style sheet + if not os.path.isfile(STYLE_SHEET_PATH): + shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH) -def intialize_logging(): - """ - Initialize the logging framework using the Safe Eyes specific configurations. - """ - # Create the directory to store log file if not exist - if not os.path.exists(config_directory): - try: - os.makedirs(config_directory) - except: - pass +def intialize_logging(debug): + """ + Initialize the logging framework using the Safe Eyes specific configurations. + """ + # Create the directory to store log file if not exist + if not os.path.exists(CONFIG_DIRECTORY): + try: + os.makedirs(CONFIG_DIRECTORY) + except OSError: + pass - # Configure logging. - log_formatter = logging.Formatter('%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s') + # Configure logging. + root_logger = logging.getLogger() + log_formatter = logging.Formatter('%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s') - # Apped the logs and overwrite once reached 5MB - handler = RotatingFileHandler(log_file_path, mode='a', maxBytes=5 * 1024 * 1024, backupCount=2, encoding=None, delay=0) - handler.setFormatter(log_formatter) - handler.setLevel(logging.INFO) - - root_logger = logging.getLogger() - root_logger.setLevel(logging.INFO) - root_logger.addHandler(handler) + # Append the logs and overwrite once reached 5MB + file_handler = RotatingFileHandler(LOG_FILE_PATH, mode='a', maxBytes=5 * 1024 * 1024, backupCount=2, encoding=None, delay=0) + file_handler.setFormatter(log_formatter) + + if debug: + file_handler.setLevel(logging.DEBUG) + root_logger.setLevel(logging.DEBUG) + console_handler = logging.StreamHandler() + console_handler.setFormatter(log_formatter) + root_logger.addHandler(console_handler) + else: + file_handler.setLevel(logging.INFO) + root_logger.setLevel(logging.INFO) + root_logger.addHandler(file_handler) -def read_config(): - """ - Read the configuration from the config directory. - If does not exist or outdated by major version, copy the system config and - startup script to user directory. - If the user config is outdated by minor version, update the config by the new values. - """ - logging.info('Reading the configuration file') - - if not os.path.isfile(config_file_path): - logging.info('Safe Eyes configuration file not found') - __initialize_safeeyes() - - # Read the configurations - with open(config_file_path) as config_file: - user_config = json.load(config_file) - - with open(system_config_file_path) as config_file: - system_config = json.load(config_file) - - user_config_version = str(user_config['meta']['config_version']) - system_config_version = str(system_config['meta']['config_version']) - - if LooseVersion(user_config_version) < LooseVersion(system_config_version): - # Outdated user config - logging.info('Update the old config version {} with new config version {}'.format(user_config_version, system_config_version)) - user_config_major_version = user_config_version.split('.')[0] - system_config_major_version = system_config_version.split('.')[0] - - if LooseVersion(user_config_major_version) < LooseVersion(system_config_major_version): - # Major version change - __initialize_safeeyes() - # Update the user_config - user_config = system_config - else: - # Minor version change - new_config = system_config.copy() - new_config.update(user_config) - # Update the version - new_config['meta']['config_version'] = system_config_version - - # Write the configuration to file - with open(config_file_path, 'w') as config_file: - json.dump(new_config, config_file, indent=4, sort_keys=True) - - # Update the user_config - user_config = new_config - - return user_config +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') + 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 + return load_json(plugin_config_path) -class __HTMLTextExtractor(HTMLParser): - """ - Helper class to convert HTML to text - """ - def __init__(self): - self.reset() - self.strict = False - self.convert_charrefs = True - self.fed = [] +def __update_plugin_config(plugin, plugin_config, config): + """ + Update the plugin configuration. + """ + if plugin_config is None: + config['plugins'].remove(plugin) + else: + if LooseVersion(plugin['version']) < LooseVersion(plugin_config['meta']['version']): + # Update the configuration + plugin['version'] = plugin_config['meta']['version'] + setting_ids = [] + # Add the new settings + for setting in plugin_config['settings']: + setting_ids.append(setting['id']) + 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['settings']: + if key not in setting_ids: + keys_to_remove.append(key) + for key in keys_to_remove: + del plugin['settings'][key] - def handle_data(self, d): - self.fed.append(d) - def get_data(self): - return ''.join(self.fed) +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) + + +def merge_plugins(config): + """ + Merge plugin configurations with Safe Eyes configuration. + """ + system_plugins = None + user_plugins = None + + # Load system plugins id + if os.path.isdir(SYSTEM_PLUGINS_DIR): + system_plugins = os.listdir(SYSTEM_PLUGINS_DIR) + else: + system_plugins = [] + + # Load user plugins id + if os.path.isdir(USER_PLUGINS_DIR): + user_plugins = os.listdir(USER_PLUGINS_DIR) + else: + user_plugins = [] + + # Create a list of existing plugins + 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) + system_plugins.remove(plugin_id) + elif plugin_id in user_plugins: + plugin_config = __open_plugin_config(USER_PLUGINS_DIR, plugin_id) + __update_plugin_config(plugin, plugin_config, config) + user_plugins.remove(plugin_id) + else: + config['plugins'].remove(plugin) + + # Add all system plugins + for plugin_id in system_plugins: + plugin_config = __open_plugin_config(SYSTEM_PLUGINS_DIR, plugin_id) + __add_plugin_config(plugin_id, plugin_config, config) + + # Add all user plugins + for plugin_id in user_plugins: + plugin_config = __open_plugin_config(USER_PLUGINS_DIR, plugin_id) + __add_plugin_config(plugin_id, plugin_config, config) + + +def open_session(): + """ + Open the last session. + """ + logging.info('Reading the session file') + + session = load_json(SESSION_FILE_PATH) + if session is None: + session = {'plugin': {}} + return session + + +def create_gtk_builder(glade_file): + """ + Create a Gtk builder and load the glade file. + """ + builder = Gtk.Builder() + builder.set_translation_domain('safeeyes') + builder.add_from_file(glade_file) + # Tranlslate all sub components + for obj in builder.get_objects(): + if (not isinstance(obj, Gtk.SeparatorMenuItem)) and hasattr(obj, "get_label"): + label = obj.get_label() + if label is not None: + obj.set_label(_(label)) + elif hasattr(obj, "get_title"): + title = obj.get_title() + if title is not None: + obj.set_title(_(title)) + return builder diff --git a/safeeyes/__main__.py b/safeeyes/__main__.py index 1d4f8bb..13a376b 100755 --- a/safeeyes/__main__.py +++ b/safeeyes/__main__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 - # Safe Eyes is a utility to remind you to take break frequently # to protect your eyes from eye strain. @@ -17,279 +16,124 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -import os, gi, json, dbus, logging, psutil, sys +""" +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 +import logging +import sys from threading import Timer -from dbus.mainloop.glib import DBusGMainLoop + +import gi +import psutil +from safeeyes import Utility +from safeeyes.model import Config +from safeeyes.SafeEyes import SafeEyes +from safeeyes.SafeEyes import SAFE_EYES_VERSION +from safeeyes.rpc import RPCClient + gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from safeeyes.AboutDialog import AboutDialog -from safeeyes.BreakScreen import BreakScreen -from safeeyes.Notification import Notification -from safeeyes.Plugins import Plugins -from safeeyes.SafeEyesCore import SafeEyesCore -from safeeyes.SettingsDialog import SettingsDialog -from safeeyes.TrayIcon import TrayIcon -from safeeyes import Utility -# Define necessary paths -break_screen_glade = os.path.join(Utility.bin_directory, "glade/break_screen.glade") -settings_dialog_glade = os.path.join(Utility.bin_directory, "glade/settings_dialog.glade") -about_dialog_glade = os.path.join(Utility.bin_directory, "glade/about_dialog.glade") - -is_active = True -SAFE_EYES_VERSION = "1.2.2" +gettext.install('safeeyes', Utility.LOCALE_PATH) -def show_settings(): - """ - Listen to tray icon Settings action and send the signal to Settings dialog. - """ - logging.info("Show Settings dialog") - able_to_lock_screen = False - if system_lock_command: - able_to_lock_screen = True - settings_dialog = SettingsDialog(config, language, Utility.read_lang_files(), able_to_lock_screen, save_settings, settings_dialog_glade) - settings_dialog.show() +def __running(): + """ + Check if SafeEyes is already running. + """ + process_count = 0 + for proc in psutil.process_iter(): + if not proc.cmdline: + continue + try: + # Check if safeeyes is in process arguments + if callable(proc.cmdline): + # Latest psutil has cmdline function + cmd_line = proc.cmdline() + 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): + process_count += 1 + if process_count > 1: + return True + + # Ignore if process does not exist or does not have command line args + except (IndexError, psutil.NoSuchProcess): + pass + return False -def show_about(): - """ - Listen to tray icon About action and send the signal to About dialog. - """ - logging.info("Show About dialog") - about_dialog = AboutDialog(about_dialog_glade, SAFE_EYES_VERSION, language) - about_dialog.show() - - -def show_notification(): - """ - Receive the signal from core and pass it to the Notification. - """ - if config['strict_break']: - Utility.execute_main_thread(tray_icon.lock_menu) - plugins.pre_notification(context) - notification.show(config['pre_break_warning_time']) - - -def show_alert(message, image_name): - """ - Receive the break signal from core and pass it to the break screen. - """ - logging.info("Show the break screen") - notification.close() - plugins_data = plugins.pre_break(context) - break_screen.show_message(message, Utility.get_resource_path(image_name), plugins_data) - if config['strict_break'] and is_active: - Utility.execute_main_thread(tray_icon.unlock_menu) - - -def close_alert(audible_alert_on): - """ - Receive the stop break signal from core and pass it to the break screen. - """ - logging.info("Close the break screen") - if config['enable_screen_lock'] and context['break_type'] == 'long': - # Lock the screen before closing the break screen - Utility.lock_desktop(system_lock_command) - break_screen.close() - if audible_alert_on: - Utility.play_notification() - plugins.post_break(context) - - -def on_quit(): - """ - Listen to the tray menu quit action and stop the core, notification and the app itself. - """ - logging.info("Quit Safe Eyes") - plugins.exit(context) - core.stop() - notification.quite() - Gtk.main_quit() - - -def handle_suspend_callback(sleeping): - """ - 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: - # Sleeping / suspending - if is_active: - core.stop() - logging.info("Stopped Safe Eyes due to system suspend") - else: - # Resume from sleep - if is_active: - core.start() - logging.info("Resumed Safe Eyes after system wakeup") - - -def handle_system_suspend(): - """ - Setup system suspend listener. - """ - DBusGMainLoop(set_as_default=True) - bus = dbus.SystemBus() - bus.add_signal_receiver(handle_suspend_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1') - - -def on_skipped(): - """ - Listen to break screen Skip action and send the signal to core. - """ - logging.info("User skipped the break") - if config['enable_screen_lock'] and context['break_type'] == 'long' and context.get('count_down', 0) >= config['time_to_screen_lock']: - # Lock the screen before closing the break screen - Utility.lock_desktop(system_lock_command) - core.skip_break() - plugins.post_break(context) - - -def on_postponed(): - """ - Listen to break screen Postpone action and send the signal to core. - """ - logging.info("User postponed the break") - if config['enable_screen_lock'] and context['break_type'] == 'long' and context.get('count_down', 0) >= config['time_to_screen_lock']: - # Lock the screen before closing the break screen - Utility.lock_desktop(system_lock_command) - core.postpone_break() - - -def save_settings(config): - """ - Listen to Settings dialog Save action and write to the config file. - """ - global language - - logging.info("Saving settings to safeeyes.json") - - # Stop the Safe Eyes core - if is_active: - core.stop() - - # Write the configuration to file - with open(Utility.config_file_path, 'w') as config_file: - json.dump(config, config_file, indent=4, sort_keys=True) - - # Reload the language translation - language = Utility.load_language(config['language']) - tray_icon.initialize(config) - tray_icon.set_labels(language) - - logging.info("Initialize SafeEyesCore with modified settings") - - # Restart the core and intialize the components - core.initialize(config, language) - break_screen.initialize(config, language) - notification.initialize(language) - if is_active: - # 1 sec delay is required to give enough time for core to be stopped - Timer(1.0, core.start).start() - - -def enable_safeeyes(): - """ - Listen to tray icon enable action and send the signal to core. - """ - global is_active - is_active = True - core.start() - - -def disable_safeeyes(): - """ - Listen to tray icon disable action and send the signal to core. - """ - global is_active - is_active = False - core.stop() - - -def running(): - """ - Check if SafeEyes is already running. - """ - process_count = 0 - for proc in psutil.process_iter(): - if not proc.cmdline: - continue - try: - # Check if safeeyes is in process arguments - if callable(proc.cmdline): - # Latest psutil has cmdline function - cmd_line = proc.cmdline() - 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): - process_count += 1 - if process_count > 1: - return True - - # Ignore if process does not exist or does not have command line args - except (IndexError, psutil.NoSuchProcess): - pass - return False - +def __evaluate_arguments(args, safe_eyes): + """ + Evaluate the arguments and execute the operations. + """ + if args.about: + Utility.execute_main_thread(safe_eyes.show_about) + elif args.disable: + Utility.execute_main_thread(safe_eyes.disable_safeeyes) + elif args.enable: + Utility.execute_main_thread(safe_eyes.enable_safeeyes) + elif args.settings: + Utility.execute_main_thread(safe_eyes.show_settings) + elif args.take_break: + Utility.execute_main_thread(safe_eyes.take_break) def main(): - """ - Start the Safe Eyes. - """ - # Initialize the logging - Utility.intialize_logging() + """ + Start the Safe Eyes. + """ + system_locale = gettext.translation('safeeyes', localedir=Utility.LOCALE_PATH, languages=[Utility.system_locale(), 'en_US'], fallback=True) + system_locale.install() + # locale.bindtextdomain is required for Glade files + # gettext.bindtextdomain(gettext.textdomain(), Utility.LOCALE_PATH) + locale.bindtextdomain('safeeyes', Utility.LOCALE_PATH) - logging.info("Starting Safe Eyes") + parser = argparse.ArgumentParser(prog='safeeyes', description=_('description')) + group = parser.add_mutually_exclusive_group() + group.add_argument('-a', '--about', help=_('show the about dialog'), action='store_true') + group.add_argument('-d', '--disable', help=_('disable the currently running safeeyes instance'), action='store_true') + group.add_argument('-e', '--enable', help=_('enable the currently running safeeyes instance'), action='store_true') + group.add_argument('-q', '--quit', help=_('quit the running safeeyes instance and exit'), action='store_true') + group.add_argument('-s', '--settings', help=_('show the settings dialog'), action='store_true') + group.add_argument('-t', '--take-break', help=_('Take a break now').lower(), action='store_true') + parser.add_argument('--debug', help=_('start safeeyes in debug mode'), action='store_true') + parser.add_argument('--version', action='version', version='%(prog)s ' + SAFE_EYES_VERSION) + args = parser.parse_args() - # Import the dependencies - Utility.import_dependencies() + # Initialize the logging + Utility.intialize_logging(args.debug) + config = Config() - if not running(): - - global break_screen - global core - global config - global notification - global tray_icon - global language - global context - global plugins - global system_lock_command - - config = Utility.read_config() - - context = {} - language = Utility.load_language(config['language']) - # Get the lock command only one time - if config['lock_screen_command']: - system_lock_command = config['lock_screen_command'] - else: - system_lock_command = Utility.lock_screen_command() - - # Initialize the Safe Eyes Context - context['version'] = SAFE_EYES_VERSION - context['desktop'] = Utility.desktop_environment() - - tray_icon = TrayIcon(config, language, show_settings, show_about, enable_safeeyes, disable_safeeyes, on_quit) - break_screen = BreakScreen(context, on_skipped, on_postponed, break_screen_glade, Utility.style_sheet_path) - break_screen.initialize(config, language) - notification = Notification(context, language) - plugins = Plugins(config) - core = SafeEyesCore(context, show_notification, show_alert, close_alert, break_screen.show_count_down, tray_icon.next_break_time) - core.initialize(config, language) - plugins.start(context) # Call the start method of all plugins - core.start() - - handle_system_suspend() - - Gtk.main() - else: - logging.info('Another instance of safeeyes is already running') - sys.exit(0) + if __running(): + logging.info("Safe Eyes is already running") + rpc_client = RPCClient(config.get('rpc_port')) + if args.about: + rpc_client.show_about() + elif args.disable: + rpc_client.disable_safeeyes() + elif args.enable: + rpc_client.enable_safeeyes() + elif args.settings: + rpc_client.show_settings() + elif args.take_break: + rpc_client.take_break() + elif args.quit: + rpc_client.quit() + else: + # Default behavior is opening settings + rpc_client.show_settings() + sys.exit(0) + elif not args.quit: + logging.info("Starting Safe Eyes") + safeeyes = SafeEyes(system_locale, config) + safeeyes.start() + Timer(1.0, lambda: __evaluate_arguments(args, safeeyes)).start() + Gtk.main() if __name__ == '__main__': - main() + main() diff --git a/safeeyes/config/lang/ca.json b/safeeyes/config/lang/ca.json deleted file mode 100644 index dee29a4..0000000 --- a/safeeyes/config/lang/ca.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Català", - "language_name_en": "Catalan" - }, - "app_info": { - "description": "Safe Eyes protegeix els vostres ulls de la fatiga visual (astenopia) recordant-vos que feu petits descansos mentre esteu treballant amb l'ordinador." - }, - "exercises": { - "short_break_close_eyes": "Tanqueu fortament els ulls", - "short_break_roll_eyes": "Moveu els ulls a banda i banda", - "short_break_rotate_clockwise": "Gireu els ulls en sentit horari", - "short_break_rotate_counter_clockwise": "Gireu els ulls en sentit antihorari", - "short_break_blink": "Parpallegeu", - "short_break_focus_far_distance": "Fixeu la mirada en un punt llunyà", - "short_break_drink_water": "Bebeu una mica d'aigua", - "long_break_walk": "Camineu una estona", - "long_break_lean_back": "Reclineu-vos sobre la cadira i relaxeu-vos" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Prepareu-vos per a una pausa en {} segons", - "ready_for_a_long_break": "Prepareu-vos per a una pausa en {} segons", - "disabled_until_restart": "Inhabilita fins que es reinicïi", - "disabled_until_x": "Inhabilitat fins {}", - "next_break_at": "Propera pausa a les {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Quant a", - "allow_postpone": "Permet posposar les pauses", - "audible_alert": "Alerta sonora en finalitzar una pausa", - "cancel": "Canceŀla", - "close": "Tanca", - "disable": "Desactiva Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Activa Safe Eyes", - "enable_screen_lock": "Bloca la pantalla després de cada pausa llarga", - "for_x_hour": "Durant {} hora", - "for_x_hours": "Durant {} hores", - "for_x_minutes": "Durant {} minuts", - "idle_time": "Temps mínim inactiu per a pausar (en minuts)", - "interval_between_two_breaks": "Interval entre dues pauses (en minuts)", - "language": "Llengua", - "license": "Llicència", - "long_break_duration": "Durada d'una pausa llarga (en segons)", - "no_of_short_breaks_between_two_long_breaks": "Nombre de pauses breus entre dues pauses llargues", - "postpone": "Posposa", - "postpone_duration": "Durada d'una posposició (en minuts)", - "quit": "Tanca", - "save": "Desa", - "settings": "Configuració", - "short_break_duration": "Durada d'una pausa breu (en segons)", - "show_time_in_tray": "Mostra l'hora de la propera pausa a l'àrea de notificacions", - "skip": "Omet", - "strict_break": "Pausa estrica (sense botó per ometre-la)", - "system_language": "Idioma del sistema", - "time_to_prepare_for_break": "Temps per preparar-se per a una pausa (en segons)", - "time_to_screen_lock": "Temps màxim per saltar-se una pausa, evitant la pantalla de bloqueig (en segons)", - "until_restart": "Fins que es reinicïi" - } -} diff --git a/safeeyes/config/lang/cs.json b/safeeyes/config/lang/cs.json deleted file mode 100644 index d827224..0000000 --- a/safeeyes/config/lang/cs.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Čeština", - "language_name_en": "Czech" - }, - "app_info": { - "description": "Safe Eyes chrání vaše oči před následky přetěžování (asthenopie), když dlouho hledíte do obrazovky počítače, připomínáním potřebných přestávek." - }, - "exercises": { - "short_break_close_eyes": "Zavřete oči", - "short_break_roll_eyes": "Zakroužete očima (několikrát na každou stranu)", - "short_break_rotate_clockwise": "Zakroužete očima ve směru hodinových ručiček", - "short_break_rotate_counter_clockwise": "Zakroužete očima proti směru hodinových ručiček", - "short_break_blink": "Zamrkejte", - "short_break_focus_far_distance": "Zaostřete pohled na nějaký vzdálený objekt", - "short_break_drink_water": "Napijte se vody", - "long_break_walk": "Na chvíli se projděte", - "long_break_lean_back": "Opřete se zády do židle a uvolněte se" - }, - "messages": { - "audible_alert_disabled": "Nedaří se zapnout zvuková oznamování", - "ready_for_a_short_break": "Připravte se na krátkou přestávku za {} sekund", - "ready_for_a_long_break": "Připravte se na dlouhou přestávku za {} sekund", - "disabled_until_restart": "Pozastaveno do restartu", - "disabled_until_x": "Pozastaveno do {}", - "next_break_at": "Příští přestávka v {}", - "software_required": "Tato funkce vyžaduje instalaci {}" - }, - "ui_controls": { - "about": "O aplikaci", - "allow_postpone": "Umožnit odkládání přestávek", - "audible_alert": "Zvukové upozornění na konci přestávky", - "cancel": "Storno", - "close": "Zavřít", - "disable": "Pozastavit Safe Eyes", - "disable_keyboard_shortcut": "Délka (s) vypnutí klávesové zkratky (prevence neúmyslného přeskočení)", - "enable": "Spustit Safe Eyes", - "enable_screen_lock": "Po uplynutí každé velké přestávky ponechat obrazovku uzamčenou", - "for_x_hour": "Na {} hodinu", - "for_x_hours": "Na {} hodiny", - "for_x_minutes": "Na {} minut", - "idle_time": "Pozastavit časovač při nečinnosti delší než (minut)", - "interval_between_two_breaks": "Interval mezi dvěma po sobě jdoucími přestávkami", - "language": "Jazyk", - "license": "Licence", - "long_break_duration": "Trvání dlouhé přestávky (v sekundách)", - "no_of_short_breaks_between_two_long_breaks": "Počet krátkých přestávek mezi dvěma dlouhými", - "postpone": "Odložit", - "postpone_duration": "O kolik odložit (v minutách)", - "quit": "Ukončit", - "save": "Uložit", - "settings": "Nastavení", - "short_break_duration": "Trvání krátké přestávky (v sekundách)", - "show_time_in_tray": "Zobrazovat čas příští přestávky v oznamovací oblasti", - "skip": "Přeskočit", - "strict_break": "Povinná přestávka (skrýt tlačítko pro přeskočení)", - "system_language": "Jazyk systému", - "time_to_prepare_for_break": "Jakou dobu dopředu hlásit blížící se přestávku (v sekundách)", - "time_to_screen_lock": "Časový limit pro přeskočení přestávky a tedy obejití zámku obrazovky (v sekundách)", - "until_restart": "Do restartu počítače" - } -} diff --git a/safeeyes/config/lang/de.json b/safeeyes/config/lang/de.json deleted file mode 100644 index 67a61dc..0000000 --- a/safeeyes/config/lang/de.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Deutsch", - "language_name_en": "German" - }, - "app_info": { - "description": "Safe Eyes schützt Ihre Augen vor Überlastung (Asthenopie), indem es Sie bei längerer Arbeit am Computer an regelmäßige Pausen erinnert." - }, - "exercises": { - "short_break_close_eyes": "Schließen Sie die Augen", - "short_break_roll_eyes": "Rollen Sie mit den Augen", - "short_break_rotate_clockwise": "Rotieren Sie mit den Augen im Uhrzeigersinn", - "short_break_rotate_counter_clockwise": "Rotieren Sie mit den Augen gegen den Uhrzeigersinn", - "short_break_blink": "Blinzeln Sie", - "short_break_focus_far_distance": "Fokussieren Sie sich auf einen Punkt in weiter Ferne", - "short_break_drink_water": "Trinken Sie ein wenig Wasser", - "long_break_walk": "Gehen Sie ein wenig", - "long_break_lean_back": "Lehnen Sie sich zurück und entspannen Sie sich" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Nächste Pause in {} Sekunden", - "ready_for_a_long_break": "Nächste Pause in {} Sekunden", - "disabled_until_restart": "Deaktiviert bis zum Neustart", - "disabled_until_x": "Deaktiviert bis {}", - "next_break_at": "Nächste Pause um {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Über", - "allow_postpone": "Erlaube Verschieben von Pausen", - "audible_alert": "Akustisches Signal am Ende der Pause", - "cancel": "Abbrechen", - "close": "Schließen", - "disable": "Safe Eyes deaktivieren", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Safe Eyes aktivieren", - "enable_screen_lock": "Sperrt den Bildschirm nach einer langen Pause", - "for_x_hour": "Für {} Stunde", - "for_x_hours": "Für {} Stunden", - "for_x_minutes": "Für {} Minuten", - "idle_time": "Minimale Leerlaufzeit zum Pausieren (in Minuten)", - "interval_between_two_breaks": "Intervall zwischen zwei Pausen", - "language": "Sprache", - "license": "Lizenz", - "long_break_duration": "Lange-Pause-Intervall (in Sekunden)", - "no_of_short_breaks_between_two_long_breaks": "Anzahl von kleinen Pausen zwischen zwei langen Pausen", - "postpone": "Verzögernn", - "postpone_duration": "Verzögerungsdauer (in Minuten)", - "quit": "Schließen", - "save": "Speichern", - "settings": "Einstellungen", - "short_break_duration": "Kleine-Pause-Intervall (in Sekunden)", - "show_time_in_tray": "Zeige Zeit für nächste Pause im Bereich der Systemnotifikationen", - "skip": "Überspringen", - "strict_break": "Strikte Pause (Überspringen nicht möglich)", - "system_language": "Systemsprache", - "time_to_prepare_for_break": "Zeit zur Vorbereitung für die Pause (in Sekunden)", - "time_to_screen_lock": "Maximale Zeit zum Überspringen der Bildschirmsperre (in Sekunden)", - "until_restart": "Bis zum Neustart" - } -} diff --git a/safeeyes/config/lang/en.json b/safeeyes/config/lang/en.json deleted file mode 100644 index 1c851df..0000000 --- a/safeeyes/config/lang/en.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "English", - "language_name_en": "English" - }, - "app_info": { - "description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." - }, - "exercises": { - "short_break_close_eyes": "Tightly close your eyes", - "short_break_roll_eyes": "Roll your eyes a few times to each side", - "short_break_rotate_clockwise": "Rotate your eyes in clockwise direction", - "short_break_rotate_counter_clockwise": "Rotate your eyes in counterclockwise direction", - "short_break_blink": "Blink your eyes", - "short_break_focus_far_distance": "Focus on a point in the far distance", - "short_break_drink_water": "Have some water", - "long_break_walk": "Walk for a while", - "long_break_lean_back": "Lean back at your seat and relax" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Ready for a short break in {} seconds", - "ready_for_a_long_break": "Ready for a long break in {} seconds", - "disabled_until_restart": "Disabled until restart", - "disabled_until_x": "Disabled until {}", - "next_break_at": "Next break at {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "About", - "allow_postpone": "Allow postponing the breaks", - "audible_alert": "Audible alert at the end of break", - "cancel": "Cancel", - "close": "Close", - "disable": "Disable Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Enable Safe Eyes", - "enable_screen_lock": "Lock the screen after every long break", - "for_x_hour": "For {} Hour", - "for_x_hours": "For {} Hours", - "for_x_minutes": "For {} Minutes", - "idle_time": "Minimum idle time to pause (in minutes)", - "interval_between_two_breaks": "Interval between two breaks (in minutes)", - "language": "Language", - "license": "License", - "long_break_duration": "Long break duration (in seconds)", - "no_of_short_breaks_between_two_long_breaks": "Number of short breaks between two long breaks", - "postpone": "Postpone", - "postpone_duration": "Postpone duration (in minutes)", - "quit": "Quit", - "save": "Save", - "settings": "Settings", - "short_break_duration": "Short break duration (in seconds)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Skip", - "strict_break": "Strict break (Hide skip button)", - "system_language": "System Language", - "time_to_prepare_for_break": "Time to prepare for break (in seconds)", - "time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)", - "until_restart": "Until restart" - } -} diff --git a/safeeyes/config/lang/es.json b/safeeyes/config/lang/es.json deleted file mode 100644 index 703be5f..0000000 --- a/safeeyes/config/lang/es.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Español", - "language_name_en": "Spanish" - }, - "app_info": { - "description": "Safe Eyes protege tus ojos de la fatiga visual (astenopía), recordándote tomar descansos cuando estás trabajando muchas horas en el ordenador." - }, - "exercises": { - "short_break_close_eyes": "Cierra fuertemente tus ojos", - "short_break_roll_eyes": "Pon los ojos en blanco hacia cada lado", - "short_break_rotate_clockwise": "Mueve tus ojos en círculos en sentido horario", - "short_break_rotate_counter_clockwise": "Mueve tus ojos en círculos en sentido antihorario", - "short_break_blink": "Parpadea tus ojos", - "short_break_focus_far_distance": "Enfoca un punto lejano", - "short_break_drink_water": "Bebe un poco de agua", - "long_break_walk": "Vete a andar un rato", - "long_break_lean_back": "Reclínate sobre tu silla y relájate" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Listo para una pausa en {} segundos", - "ready_for_a_long_break": "Listo para una pausa en {} segundos", - "disabled_until_restart": "Deshabilitado hasta reinicio", - "disabled_until_x": "Deshabilitado hasta {}", - "next_break_at": "Próxima pausa a las {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Acerca de", - "allow_postpone": "Permitir posponer las pausas", - "audible_alert": "Alerta sonora al final de cada pausa", - "cancel": "Cancelar", - "close": "Close", - "disable": "Desactivar Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Activar Safe Eyes", - "enable_screen_lock": "Bloquear la pantalla despues de cada pausa larga", - "for_x_hour": "Durante {} hora", - "for_x_hours": "Durante {} horas", - "for_x_minutes": "Durante {} minutos", - "idle_time": "Tiempo mínimo (en minutos) sin usar el ordenador para que Safe Eyes se desactive automáticamente", - "interval_between_two_breaks": "Tiempo entre dos pausas", - "language": "Idioma", - "license": "Licencia", - "long_break_duration": "Duración de una pausa larga (en segundos)", - "no_of_short_breaks_between_two_long_breaks": "Número de pausas cortas entre dos pausas largas", - "postpone": "Posponer", - "postpone_duration": "Duración de cada posposición (en minutos)", - "quit": "Salir", - "save": "Guardar", - "settings": "Preferencias", - "short_break_duration": "Duración de una pausa corta (en segundos)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Saltar", - "strict_break": "Pausa estricta (No hay botón Saltar)", - "system_language": "Idioma del sistema", - "time_to_prepare_for_break": "Tiempo para prepararse para una pausa (en segundos)", - "time_to_screen_lock": "Máximo tiempo para saltarse una pausa, evitando la pantalla de bloqueo (en segundos)", - "until_restart": "Hasta reinicio" - } -} diff --git a/safeeyes/config/lang/et.json b/safeeyes/config/lang/et.json deleted file mode 100644 index 63526ac..0000000 --- a/safeeyes/config/lang/et.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Eesti", - "language_name_en": "Estonian" - }, - "app_info": { - "description": "Safe Eyes aitab vähendada arvutiga töötamisel silmade väsimust, tuletades meelde puhkepause." - }, - "exercises": { - "short_break_close_eyes": "Sulge silmad", - "short_break_roll_eyes": "Vaata vasakule ja paremale", - "short_break_rotate_clockwise": "Liiguta silmi kellaosuti suunas", - "short_break_rotate_counter_clockwise": "Liiguta silmi kellaosutile vastupidiselt", - "short_break_blink": "Pilguta silmi", - "short_break_focus_far_distance": "Vaata kaugusesse", - "short_break_drink_water": "Joo vett", - "long_break_walk": "Jaluta ringi", - "long_break_lean_back": "Toetu seljatoele ja lõõgastu" - }, - "messages": { - "audible_alert_disabled": "Helimärguandeid ei tekitata", - "ready_for_a_short_break": "Lühike paus {} sekundi pärast", - "ready_for_a_long_break": "Pikk paus {} sekundi pärast", - "disabled_until_restart": "Peatatud taaskäivituseni", - "disabled_until_x": "Peatatud kuni {}", - "next_break_at": "Järgmine paus {}", - "software_required": "Selle võimaluse jaoks tuleb paigaldada tarkvara {}" - }, - "ui_controls": { - "about": "Programmist", - "allow_postpone": "Pauside edasilükkamine lubatud", - "audible_alert": "Pausi lõpus helimärguanne", - "cancel": "Tühista", - "close": "Sulge", - "disable": "Peata Safe Eyes", - "disable_keyboard_shortcut": "Klaviatuurikäskude eiramise aeg, et vältida juhuslikku edasilükkamist (sekundit)", - "enable": "Luba Safe Eyes", - "enable_screen_lock": "Pikka pausi lõpus ekraani lukustamine", - "for_x_hour": "{}-ks tunniks", - "for_x_hours": "{}-ks tunniks", - "for_x_minutes": "{}-ks minutiks", - "idle_time": "Minimaalne pausi aeg (minutites)", - "interval_between_two_breaks": "Kahe pausi vaheline aeg (minutites)", - "language": "Keel", - "license": "Litsents", - "long_break_duration": "Pika pausi kestvus (sekundites)", - "no_of_short_breaks_between_two_long_breaks": "Kui mitu lühikest pausi tehaks pikkade pauside vahel", - "postpone": "Lükka edasi", - "postpone_duration": "Edasilükkamise aeg (minutites)", - "quit": "Välju", - "save": "Salvesta", - "settings": "Seaded", - "short_break_duration": "Lühikese pausi kestvus (sekundites)", - "show_time_in_tray": "Kuvatakse järgmise pausi aega", - "skip": "Jäta vahele", - "strict_break": "Range paus (vahelejätmise nuppu ei näidata)", - "system_language": "Süsteemi keel", - "time_to_prepare_for_break": "Kui kaua enne pausi kuvatakse pausi hoiatust (sekundites)", - "time_to_screen_lock": "Kui kaua enne ekraani lukustamist saab pausi edasi lükata (sekundites)", - "until_restart": "Kuni taaskäivituseni" - } -} diff --git a/safeeyes/config/lang/fa.json b/safeeyes/config/lang/fa.json deleted file mode 100644 index 57a7ecc..0000000 --- a/safeeyes/config/lang/fa.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "فارسی", - "language_name_en": "Persian" - }, - "app_info": { - "description": "Safe Eyes .با یاد آوری شما برای استراحت کردن، وقتی ساعت های طولانی را در پشت کامپیوتر سپری میکنید. از تضعیف چشم های شما جلوگیری میکند " - }, - "exercises": { - "short_break_close_eyes": "چشم هایتان را محکم ببندید", - "short_break_roll_eyes": "چشم هایتان را چندین مرتبه به طرفین بچرخانید", - "short_break_rotate_clockwise": "چشم هایتان را در جهت عقربه های ساعت بچرخانید", - "short_break_rotate_counter_clockwise": "چشم هایتان را در خلاف جهت عقربه های ساعت بچرخانید", - "short_break_blink": "پلک بزنید", - "short_break_focus_far_distance": "روی یک نقطه در دور دست تمرکز کنید", - "short_break_drink_water": "مقداری آب بنوشید", - "long_break_walk": "مدتی قدم بزنید", - "long_break_lean_back": "به صندلی تکیه داده و راحت باشید" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "آماده استراحت در {} ثانیه بعد باشید", - "ready_for_a_long_break": "آماده استراحت در {} ثانیه بعد باشید", - "disabled_until_restart": "غیرفعال تا بارگذاری مجدد", - "disabled_until_x": "غیرفعال تا {}", - "next_break_at": "استراحت بعد در {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "درباره", - "allow_postpone": "اجازه به تعویق انداختن استراحت", - "audible_alert": "هشدار همراه با صدا در پایان استراحت", - "cancel": "لغو", - "close": "بستن", - "disable": "غیر فعال کردن Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "فعال کردن Safe Eyes", - "enable_screen_lock": "قفل کردن صفحه بعد از هر استراحت طولانی", - "for_x_hour": "برای {} ساعت", - "for_x_hours": "برای {} ساعت", - "for_x_minutes": "برای {} دقیقه", - "idle_time": "حداقل زمان بیکاری برای توقف کردن برنامه (‌به دقیقه)", - "interval_between_two_breaks": "فاصله بین دو استراحت (به ثانیه‌)", - "language": "زبان", - "license": "لایسنس", - "long_break_duration": "زمان استراحت بلند مدت (به دقیقه)", - "no_of_short_breaks_between_two_long_breaks": "تعداد استراحت های کوتاه بین هر دو استراحت بلند", - "postpone": "به تعویق انداختن", - "postpone_duration": "زمان به تعویق انداختن (به دقیقه)", - "quit": "خروج", - "save": "ذخیره", - "settings": "تنظیمات", - "short_break_duration": "مدت زمان استراحت کوتاه", - "show_time_in_tray": "نشان دادن زمان استراحت بعدی در بخش Tray", - "skip": "رد کردن", - "strict_break": "استراحت سخت گیرانه (پنهان کردن دکمه رد کردن استراحت)", - "system_language": "زبان سیستم", - "time_to_prepare_for_break": "زمان آماده شدن برای استراحت (به ثانیه)", - "time_to_screen_lock": "حداکثر زمان برای رد کردن، جلوگیری از قفل شدن صفحه (به ثانیه‌)", - "until_restart": "تا زمان بارگذاری مجدد" - } -} diff --git a/safeeyes/config/lang/fr.json b/safeeyes/config/lang/fr.json deleted file mode 100644 index d620a1d..0000000 --- a/safeeyes/config/lang/fr.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Français", - "language_name_en": "French" - }, - "app_info": { - "description": "Safe Eyes protège vos yeux contre la fatigue de l'œil (asthénopie) en vous rappelant de prendre des pauses lors de vos longues heures de travail sur un ordinateur." - }, - "exercises": { - "short_break_close_eyes": "Fermez bien vos yeux", - "short_break_roll_eyes": "Regardez de droite à gauche en alternance", - "short_break_rotate_clockwise": "Faites rouler vos yeux dans le sens horaire", - "short_break_rotate_counter_clockwise": "Faites rouler vos yeux dans le sens antihoraire", - "short_break_blink": "Clignez des yeux", - "short_break_focus_far_distance": "Regardez un point au loin", - "short_break_drink_water": "Buvez de l'eau", - "long_break_walk": "Marchez un peu", - "long_break_lean_back": "Adossez-vous à votre siège et relaxez-vous" - }, - "messages": { - "audible_alert_disabled": "Impossible d'activer les notifications sonores", - "ready_for_a_short_break": "Préparez-vous à une pause dans {} secondes", - "ready_for_a_long_break": "Préparez-vous à une pause dans {} secondes", - "disabled_until_restart": "Désactivé jusqu'au redémarrage", - "disabled_until_x": "Désactivé jusqu'à {}", - "next_break_at": "Prochaine pause à {}", - "software_required": "Pour activer cette fonction, veuillez installer {}" - }, - "ui_controls": { - "about": "À propos", - "allow_postpone": "Permettre le report des pauses", - "audible_alert": "Alerte sonore en fin d'une pause", - "cancel": "Annuler", - "close": "Fermer", - "disable": "Désactiver Safe Eyes", - "disable_keyboard_shortcut": "Période de désactivation du raccourci pour empêcher d'ignorer involontairement (en secondes)", - "enable": "Activer Safe Eyes", - "enable_screen_lock": "Verrouiller l'écran après chaque pause longue", - "for_x_hour": "Pendant {} heure", - "for_x_hours": "Pendant {} heures", - "for_x_minutes": "Pendant {} minutes", - "idle_time": "Durée minimale d'une pause (en minutes)", - "interval_between_two_breaks": "Intervalle entre deux pauses (en minutes)", - "language": "Langue", - "license": "Licence", - "long_break_duration": "Durée d'une pause longue (en secondes)", - "no_of_short_breaks_between_two_long_breaks": "Nombre de pauses courtes entre deux pauses longues", - "postpone": "Reporter", - "postpone_duration": "Durée de report (en minutes)", - "quit": "Quitter", - "save": "Enregistrer", - "settings": "Paramètres", - "short_break_duration": "Durée d'une pause courte (en secondes)", - "show_time_in_tray": "Afficher la prochaine pause dans la zone de notification", - "skip": "Ignorer", - "strict_break": "Pause stricte (cacher le bouton Ignorer)", - "system_language": "Langue du système", - "time_to_prepare_for_break": "Durée de préparation à une pause (en secondes)", - "time_to_screen_lock": "Durée maximale pour ignorer, en passant outre le verrouillage (en secondes)", - "until_restart": "Jusqu'au redémarrage" - } -} diff --git a/safeeyes/config/lang/ge.json b/safeeyes/config/lang/ge.json deleted file mode 100644 index a711ca8..0000000 --- a/safeeyes/config/lang/ge.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "ქართული", - "language_name_en": "Georgian" - }, - "app_info": { - "description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." - }, - "exercises": { - "short_break_close_eyes": "ძლიერად დახუჭე თვალები", - "short_break_roll_eyes": "ფართოდ გაახილეთ თვალები", - "short_break_rotate_clockwise": "დაატრიალეთ თვალები საათის მოძრაობის მიმართულებით", - "short_break_rotate_counter_clockwise": "დაატრიალეთ თვალები საათის მოძრაობის საწინააღმდეგოდ", - "short_break_blink": "თვალები დაახამხამეთ", - "short_break_focus_far_distance": "ფოკუსირება გააკეთეთ შორ ობიექტზე", - "short_break_drink_water": "დალიეთ წყალი", - "long_break_walk": "ცოტა გაიარეთ", - "long_break_lean_back": "სავარძლის საზურგეზე გადაწექით და ცოტა დაისვენეთ" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "მოემზადეთ შესვენებისთვის {} წამში", - "ready_for_a_long_break": "მოემზადეთ შესვენებისთვის {} წამში", - "disabled_until_restart": "გავაუქმოთ შემდეგ რესტარტამდე", - "disabled_until_x": "გაუქმებულია {} მდე", - "next_break_at": "შემდეგი შესვენება {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "პროგრამის შესახებ", - "allow_postpone": "Allow postponing the breaks", - "audible_alert": "ხმოვანი შეტყობინება შესვენების დამთავრებისას", - "cancel": "უარყოფა", - "close": "Close", - "disable": "Safe Eyes გამორთვა", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Safe Eyes ჩართვა", - "enable_screen_lock": "Lock the screen after every long break", - "for_x_hour": "{} საათით", - "for_x_hours": "{} საათით", - "for_x_minutes": "{} საათით", - "idle_time": "მინიმუმ idle დრო პაუზის ასაღებად (წუთებში)", - "interval_between_two_breaks": "ინტერვალი შესვენებებს შორის", - "language": "ენა", - "license": "License", - "long_break_duration": "დიდი შესვენების ხანგრძლივობა (წამებში)", - "no_of_short_breaks_between_two_long_breaks": "მცირე შესვენებების რაოდენობა ორ დიდ შესვენებას შორის", - "postpone": "Postpone", - "postpone_duration": "Postpone duration (in minutes)", - "quit": "გასვლა", - "save": "დამახსოვრება", - "settings": "პარამეტრები", - "short_break_duration": "მცირე შესვენების ხანგრძლივობა (წამებში)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "გამოტოვება", - "strict_break": "აუცილებელი შესვენება (დავმალოთ ღილაკი 'გამოტოვება')", - "system_language": "System Language", - "time_to_prepare_for_break": "შესვენებისთვის მოსამზადებელი დრო (წამებში)", - "time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)", - "until_restart": "შემდეგ რესტარტამდე" - } -} diff --git a/safeeyes/config/lang/hi.json b/safeeyes/config/lang/hi.json deleted file mode 100644 index db39c16..0000000 --- a/safeeyes/config/lang/hi.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "हिंदी", - "language_name_en": "Hindi" - }, - "app_info": { - "description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." - }, - "exercises": { - "short_break_close_eyes": "आँखों को ज़ोर से बंद करें", - "short_break_roll_eyes": "आँखों को घुमाएं", - "short_break_rotate_clockwise": "आँखों को घड़ी की दिशा में घुमाएं", - "short_break_rotate_counter_clockwise": "आँखों को घड़ी से उल्टी दिशा में घुमाएं", - "short_break_blink": "ऑंखें झपकायें", - "short_break_focus_far_distance": "आँखों को दूर किसी वस्तु पे टिकाएं", - "short_break_drink_water": "पानी पियें", - "long_break_walk": "थोड़ा टहलें", - "long_break_lean_back": "पीछे हट कर आराम करें" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "{} पलों में आराम", - "ready_for_a_long_break": "{} पलों में आराम", - "disabled_until_restart": "अगले आरंभ तक बंद", - "disabled_until_x": "{} तक बंद", - "next_break_at": "अगला आराम {} में", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "हमारे बारे में", - "allow_postpone": "Allow postponing the breaks", - "audible_alert": "आराम के ख़तम के ख़तम होने पर आवाज़", - "cancel": "रखना नहीं", - "close": "Close", - "disable": "सेफ आईज बंद", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "सेफ आईज शुरू", - "enable_screen_lock": "Lock the screen after every long break", - "for_x_hour": "{} घंटे के लिए", - "for_x_hours": "{} घंटों के लिए", - "for_x_minutes": "{} मिन्टों के लिए", - "idle_time": "बीच रोकने पर कितने मिन्टों का अंतर", - "interval_between_two_breaks": "दो आराम अवधियों में अंतर (मिन्टों में)", - "language": "भाषा", - "license": "License", - "long_break_duration": "लंबे आराम की अवधि (पलों में)", - "no_of_short_breaks_between_two_long_breaks": "दो बड़े आराम के बीच कितने छोटे आराम", - "postpone": "Postpone", - "postpone_duration": "Postpone duration (in minutes)", - "quit": "बंद", - "save": "रखें", - "settings": "सेटिंग्स", - "short_break_duration": "छोटे आराम की अवधि (पलों में)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "अभी नहीं", - "strict_break": "जरूरी आराम (रोक नहीं सकते)", - "system_language": "System Language", - "time_to_prepare_for_break": "कितने पलों पहले बताएं", - "time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)", - "until_restart": "अगले आरम्भ तक" - } -} diff --git a/safeeyes/config/lang/hu.json b/safeeyes/config/lang/hu.json deleted file mode 100644 index 8d1ca81..0000000 --- a/safeeyes/config/lang/hu.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Magyar", - "language_name_en": "Hungarian" - }, - "app_info": { - "description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." - }, - "exercises": { - "short_break_close_eyes": "Csukja be hosszabb időre a szemét!", - "short_break_roll_eyes": "Roll your eyes", - "short_break_rotate_clockwise": "Rotate your eyes in clockwise direction", - "short_break_rotate_counter_clockwise": "Rotate your eyes in counterclockwise direction", - "short_break_blink": "Pislogjon és forgassa meg a szemeit!", - "short_break_focus_far_distance": "Fókuszban egy pontot a távolban", - "short_break_drink_water": "Igyon egy kis vizet!", - "long_break_walk": "Sétáljon egyet!", - "long_break_lean_back": "Dőljön hátra és pihenjen!" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Tervezett szünet {} másodperc múlva!", - "ready_for_a_long_break": "Tervezett szünet {} másodperc múlva!", - "disabled_until_restart": "Disabled until restart", - "disabled_until_x": "Disabled until {}", - "next_break_at": "A következő szünet {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Ról ről", - "allow_postpone": "Allow postponing the breaks", - "audible_alert": "Audible alert at the end of break", - "cancel": "Mégse", - "close": "Close", - "disable": "Disable Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Safe Eyes Bekapcsolása", - "enable_screen_lock": "Lock the screen after every long break", - "for_x_hour": "For {} Hour", - "for_x_hours": "For {} Hours", - "for_x_minutes": "For {} Minutes", - "idle_time": "Minimum idle time to pause (in minutes)", - "interval_between_two_breaks": "Két szünet közötti idő", - "language": "Nyelv", - "license": "License", - "long_break_duration": "Hosszú szünet hossza (másodpercekben)", - "no_of_short_breaks_between_two_long_breaks": "Hány rövid szünet legyen a hosszabbak között?", - "postpone": "Postpone", - "postpone_duration": "Postpone duration (in minutes)", - "quit": "Kilépés", - "save": "Mentés", - "settings": "Beállítások", - "short_break_duration": "Rövid szünet hossza (másodpercekben)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Átugrás", - "strict_break": "Kötelezők a szünetek? (nincs átugrás gomb)", - "system_language": "System Language", - "time_to_prepare_for_break": "Szünet előtti figyelmeztetés (másodperc)", - "time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)", - "until_restart": "Until restart" - } -} diff --git a/safeeyes/config/lang/id.json b/safeeyes/config/lang/id.json deleted file mode 100644 index a879b53..0000000 --- a/safeeyes/config/lang/id.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Bahasa Indonesia", - "language_name_en": "Indonesian" - }, - "app_info": { - "description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." - }, - "exercises": { - "short_break_close_eyes": "Tutup rapat mata Anda", - "short_break_roll_eyes": "Putar mata Anda", - "short_break_rotate_clockwise": "Putar mata Anda searah jarum jam", - "short_break_rotate_counter_clockwise": "Putar mata Anda melawan arah jarum jam", - "short_break_blink": "Kedipkan mata Anda", - "short_break_focus_far_distance": "Fokus ke titik di kejauhan", - "short_break_drink_water": "Silakan meminum air", - "long_break_walk": "Silakan berjalan sebentar", - "long_break_lean_back": "Silakan bersandar ke kursi dan bersantai" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Bersiap beristirahat dalam {} detik", - "ready_for_a_long_break": "Bersiap beristirahat dalam {} detik", - "disabled_until_restart": "Dimatikan hingga dijalankan ulang", - "disabled_until_x": "Dimatikan hingga {}", - "next_break_at": "Istirahat selanjutnya pada {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Tentang", - "allow_postpone": "Allow postponing the breaks", - "audible_alert": "Peringatan bersuara saat istirahat berakhir", - "cancel": "Batal", - "close": "Close", - "disable": "Matikan Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Hidupkan Safe Eyes", - "enable_screen_lock": "Lock the screen after every long break", - "for_x_hour": "Selama {} Jam", - "for_x_hours": "Selama {} Jam", - "for_x_minutes": "Selama {} Menit", - "idle_time": "Waktu diam jeda minimal (dalam menit)", - "interval_between_two_breaks": "Jarak antara dua istirahat (dalam menit)", - "language": "Bahasa", - "license": "License", - "long_break_duration": "Durasi istirahat panjang (dalam detik)", - "no_of_short_breaks_between_two_long_breaks": "Jumlah istirahat singkat di antara dua istirahat panjang", - "postpone": "Postpone", - "postpone_duration": "Postpone duration (in minutes)", - "quit": "Keluar", - "save": "Simpan", - "settings": "Pengaturan", - "short_break_duration": "Durasi istirahat singkat (dalam detik)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Lewati", - "strict_break": "Paksa istirahat (sembunyikan tombol Lewati)", - "system_language": "System Language", - "time_to_prepare_for_break": "Waktu persiapan istirahat (dalam detik)", - "time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)", - "until_restart": "Hingga dijalankan ulang" - } -} diff --git a/safeeyes/config/lang/it.json b/safeeyes/config/lang/it.json deleted file mode 100644 index 250a2bf..0000000 --- a/safeeyes/config/lang/it.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Italiano", - "language_name_en": "Italiano" - }, - "app_info": { - "description": "Safe Eyes protegge i tuoi occhi dall'affaticamento (Astenopia) ricordandoti di fare pause quando lavori per lunghi periodi al computer." - }, - "exercises": { - "short_break_close_eyes": "Chiudi gli occhi stringendoli", - "short_break_roll_eyes": "Ruota gli occhi un po' di volte ad ogni lato", - "short_break_rotate_clockwise": "Ruota gli occhi in senso orario", - "short_break_rotate_counter_clockwise": "Ruota gli occhi in senso antiorario", - "short_break_blink": "Batti gli occhi", - "short_break_focus_far_distance": "Focalizza un punto in lontananza", - "short_break_drink_water": "Bevi un po' d'acqua", - "long_break_walk": "Cammina un po'", - "long_break_lean_back": "Appoggiati allo schienale e rilassati" - }, - "messages": { - "audible_alert_disabled": "Impossibile abilitare le notifiche audio", - "ready_for_a_short_break": "Preparati per una breve pausa tra {} secondi", - "ready_for_a_long_break": "preparati per una lunga pausa tra {} secondi", - "disabled_until_restart": "Disabilitato fino al riavvio", - "disabled_until_x": "Disabilitato fino alle {}", - "next_break_at": "Prossima pausa alle {}", - "software_required": "Per abilitare quest'impostazione, installa {}" - }, - "ui_controls": { - "about": "Informazioni", - "allow_postpone": "Consenti di posticipare pause", - "audible_alert": "Notifica audio alla fine della pausa", - "cancel": "Annulla", - "close": "Chiudi", - "disable": "Disabilita Safe Eyes", - "disable_keyboard_shortcut": "Disabilita tasti di scelta rapida per evitare di saltare le pause (in secondi)", - "enable": "Abilita Safe Eyes", - "enable_screen_lock": "Blocca lo schermo dopo ogni pausa lunga", - "for_x_hour": "Per {} ora", - "for_x_hours": "Per {} ore", - "for_x_minutes": "Per {} minuti", - "idle_time": "Tempo minimo di inattività per la pausa (in minuti)", - "interval_between_two_breaks": "Intervallo tra due pause (in minuti)", - "language": "Lingua", - "license": "Licenza", - "long_break_duration": "Durata pausa lunga (in secondi)", - "no_of_short_breaks_between_two_long_breaks": "Numero di pause brevi tra due pause lunghe", - "postpone": "Posponi", - "postpone_duration": "Durata posticipo (in minuti)", - "quit": "Chiudi", - "save": "Salva", - "settings": "Impostazioni", - "short_break_duration": "Durata pausa breve (in secondi)", - "show_time_in_tray": "Mostra l'ora della prossima pausa nella barra di sistema", - "skip": "Salta", - "strict_break": "Pausa severa (Nasconde il bottone salta)", - "system_language": "Lingua di sistema", - "time_to_prepare_for_break": "Tempo per prepararsi alla pausa (in secondi)", - "time_to_screen_lock": "Tempo massimo da saltare, evitando la schermata di blocco (in secondi)", - "until_restart": "Fino al riavvio" - } -} diff --git a/safeeyes/config/lang/mk.json b/safeeyes/config/lang/mk.json deleted file mode 100644 index 920e8db..0000000 --- a/safeeyes/config/lang/mk.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Македонски", - "language_name_en": "Macedonian" - }, - "app_info": { - "description": "Safe Eyes ве штити од замор на очите (asthenophobia) така што ве потсетува да правите паузи додека работите долги часови на компјутер." - }, - "exercises": { - "short_break_close_eyes": "Цврсто затворете ги очите", - "short_break_roll_eyes": "Превртете ги очите неколку пати на сите страни", - "short_break_rotate_clockwise": "Превртете ги очите во насока на стрелките на часовникот", - "short_break_rotate_counter_clockwise": "Превртете ги очите обратно од стрелките на часовникот", - "short_break_blink": "Трепнете неколкупати", - "short_break_focus_far_distance": "Фокусирајте се на точка далеку од вас", - "short_break_drink_water": "Напијте се малку вода", - "long_break_walk": "Пешачете малку", - "long_break_lean_back": "Наслонете се на столот и одморете" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Пауза за {} секунди", - "ready_for_a_long_break": "Пауза за {} секунди", - "disabled_until_restart": "Оневозможено до рестарт", - "disabled_until_x": "Оневозможено до {}", - "next_break_at": "Следна пауза во {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "За", - "allow_postpone": "Овозможи одложување на паузи", - "audible_alert": "Звучен оглас на крај на пауза", - "cancel": "Откажи", - "close": "Затвори", - "disable": "Оневозможете го Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Овозможете го Safe Eyes", - "enable_screen_lock": "Заклучување на екранот по секоја долга пауза", - "for_x_hour": "За {} час", - "for_x_hours": "За {} часа", - "for_x_minutes": "За {} минути", - "idle_time": "Минимум време на мирување до пауза (Во минути)", - "interval_between_two_breaks": "Време помеѓу две паузи (Во минути)", - "language": "Јазик", - "license": "Лиценца", - "long_break_duration": "Траење на долгите паузи (Во секунди)", - "no_of_short_breaks_between_two_long_breaks": "Број на кратки паузи помеѓу две долги", - "postpone": "Одложи", - "postpone_duration": "Траење на одложувањето (Во секунди)", - "quit": "Исклучи", - "save": "Зачувај", - "settings": "Подесувања", - "short_break_duration": "Траење на кратките паузи (Во секунди)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Skip", - "strict_break": "Строга пауза (Сокриј го „Прескокни“ копчето)", - "system_language": "Системски Јазик", - "time_to_prepare_for_break": "Време за подготовка за пауза (Во секунди)", - "time_to_screen_lock": "Максимално време од прескокнување на паузата без да се заклучи екранот (Во секунди)", - "until_restart": "До рестарт" - } -} diff --git a/safeeyes/config/lang/nl.json b/safeeyes/config/lang/nl.json deleted file mode 100644 index 0902402..0000000 --- a/safeeyes/config/lang/nl.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Nederlands", - "language_name_en": "Dutch" - }, - "app_info": { - "description": "Safe Eyes beschermt uw ogen tegen vermoeidheid (asthenopie) door je eraan te herinneren tijdig een pauze in te lassen wanneer je langere tijd aan de computer werkt." - }, - "exercises": { - "short_break_close_eyes": "Sluit even heel hard de ogen", - "short_break_roll_eyes": "Rol een paar keer met de ogen naar links en naar rechts", - "short_break_rotate_clockwise": "Draai met de ogen in wijzerzin", - "short_break_rotate_counter_clockwise": "Draai met de ogen in tegenwijzerzin", - "short_break_blink": "Knipper met de ogen", - "short_break_focus_far_distance": "Richt je focus op een punt in de verte", - "short_break_drink_water": "Drink een glas water", - "long_break_walk": "Ga een eindje wandelen", - "long_break_lean_back": "Leun achterover op je stoel en relax even" - }, - "messages": { - "audible_alert_disabled": "Hoorbare meldingen uitschakelen", - "ready_for_a_short_break": "Klaar voor een korte pauze over {} seconden", - "ready_for_a_long_break": "Klaar voor een lange pauze over {} seconden", - "disabled_until_restart": "Uitschakelen tot heropstart", - "disabled_until_x": "Uitgeschakeld tot {}", - "next_break_at": "Volgende pauze om {}", - "software_required": "Om deze functie te activeren, gelieve {} te installeren" - }, - "ui_controls": { - "about": "Over", - "allow_postpone": "Pauzes uitstellen toestaan", - "audible_alert": "Hoorbaar alarm op het einde van de pauze", - "cancel": "Annuleren", - "close": "Sluiten", - "disable": "Safe Eyes uitschakelen", - "disable_keyboard_shortcut": "Periode uitgeschakelde snelkoppeling om ongewild overslaan te voorkomen (in seconden)", - "enable": "Safe Eyes inschakelen", - "enable_screen_lock": "Vergrendel het scherm na elke lange pauze", - "for_x_hour": "Voor {} uur", - "for_x_hours": "Voor {} uren", - "for_x_minutes": "Voor {} minuten", - "idle_time": "Minimale periode van inactiviteit alvorens te pauzeren (in minuten)", - "interval_between_two_breaks": "Interval tussen twee pauzes (in minuten)", - "language": "Taal", - "license": "Licentie", - "long_break_duration": "Tijdsduur lange pauze (in seconden)", - "no_of_short_breaks_between_two_long_breaks": "Aantal korte pauzes tussen twee lange pauzes", - "postpone": "Uitstellen", - "postpone_duration": "Tijdsduur uitstellen (in minuten)", - "quit": "Afsluiten", - "save": "Opslaan", - "settings": "Instellingen", - "short_break_duration": "Tijdsduur korte pauze (in seconden)", - "show_time_in_tray": "Toon de volgende pauzetijd in het systeemvak", - "skip": "Overslaan", - "strict_break": "Strikte pauze (overslaan knop verbergen)", - "system_language": "Systeemtaal", - "time_to_prepare_for_break": "Voorbereidingstijd voor pauze (in seconden)", - "time_to_screen_lock": "Maximale tijd tot overslaan, het afsluitscherm omzeilend (in seconden)", - "until_restart": "Tot heropstart" - } -} diff --git a/safeeyes/config/lang/pl.json b/safeeyes/config/lang/pl.json deleted file mode 100644 index 435df8b..0000000 --- a/safeeyes/config/lang/pl.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Polski", - "language_name_en": "Polish" - }, - "app_info": { - "description": "Safe Eyes chroni Twoje oczy przed przemęczeniem i astenopią przypominając Ci o robieniu przerw podczas długotrwałej pracy przy komputerze." - }, - "exercises": { - "short_break_close_eyes": "Mocno zaciśnij powieki", - "short_break_roll_eyes": "Ruszaj oczami z boku na bok", - "short_break_rotate_clockwise": "Wykonuj krążenia oczami zgodnie z ruchem wskazówek zegara", - "short_break_rotate_counter_clockwise": "Wykonuj krążenia oczami przeciwnie do ruchu wskazówek zegara", - "short_break_blink": "Pomrugaj oczami", - "short_break_focus_far_distance": "Skup wzrok na odległym punkcie", - "short_break_drink_water": "Napij się wody", - "long_break_walk": "Przejdź się chwilę", - "long_break_lean_back": "Oprzyj się wygodnie na krześle i zrelaksuj" - }, - "messages": { - "audible_alert_disabled": "Nie można aktywować powiadomień dźwiękowych", - "ready_for_a_short_break": "Przygotuj się! Przerwa za {} sekund(y).", - "ready_for_a_long_break": "Przygotuj się! Przerwa za {} sekund(y).", - "disabled_until_restart": "Wyłączony do ponownego uruchomienia", - "disabled_until_x": "Wyłączony do {}", - "next_break_at": "Następna przerwa o {}", - "software_required": "Aby aktywować tę funkcję, zainstaluj {}" - }, - "ui_controls": { - "about": "O programie", - "allow_postpone": "Pozwól na odroczenie przerwy", - "audible_alert": "Powiadomienie dźwiękowe na koniec przerwy", - "cancel": "Anuluj", - "close": "Zamknij", - "disable": "Zatrzymaj Safe Eyes", - "disable_keyboard_shortcut": "Czas ignorowania skrótu klawiszowego zapobiegający niezamierzonemu pominięciu przerwy (w sekundach)", - "enable": "Uruchom Safe Eyes", - "enable_screen_lock": "Zablokuj ekran po każdej długiej przerwie", - "for_x_hour": "Na {} godzinę", - "for_x_hours": "Na {} godzin(y)", - "for_x_minutes": "Na {} minut(y)", - "idle_time": "Najkrótszy czas bez aktywności do wstrzymania odliczania (w minutach)", - "interval_between_two_breaks": "Czas między kolejnymi przerwami (w minutach)", - "language": "Język", - "license": "Licencja użytkownika", - "long_break_duration": "Czas trwania długiej przerwy (w sekundach)", - "no_of_short_breaks_between_two_long_breaks": "Liczba krótkich przerw między długimi przerwami", - "postpone": "Odrocz", - "postpone_duration": "Odroczenie przerwy (w minutach)", - "quit": "Wyjdź", - "save": "Zapisz", - "settings": "Ustawienia", - "short_break_duration": "Czas trwania krótkiej przerwy (w sekundach)", - "show_time_in_tray": "Pokaż czas następnej przerwy w zasobniku systemowym", - "skip": "Pomiń", - "strict_break": "Bezwzględna przerwa (ukryj przycisk pominięcia)", - "system_language": "Język systemu", - "time_to_prepare_for_break": "Czas na przygotowanie się do przerwy (w sekundach)", - "time_to_screen_lock": "Czas na pominięcie przerwy bez blokowania ekranu (w sekundach)", - "until_restart": "Do ponownego uruchomienia" - } -} diff --git a/safeeyes/config/lang/pt.json b/safeeyes/config/lang/pt.json deleted file mode 100644 index f68de6b..0000000 --- a/safeeyes/config/lang/pt.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Português", - "language_name_en": "Portuguese" - }, - "app_info": { - "description": "Safe Eyes protege seus olhos da fadiga ocular (astenopia) ao lembrá-lo de dar uma pausa enquando você trabalha por horas no computador." - }, - "exercises": { - "short_break_close_eyes": "Feche bem os olhos", - "short_break_roll_eyes": "Mexa os olhos", - "short_break_rotate_clockwise": "Gire os olhos no sentido horário", - "short_break_rotate_counter_clockwise": "Gire os olhos no sentido anti-horário", - "short_break_blink": "Pisque seus olhos", - "short_break_focus_far_distance": "Focalize seu olhar num ponto distante", - "short_break_drink_water": "Beba água", - "long_break_walk": "Caminhe um pouco", - "long_break_lean_back": "Encoste na cadeira e relaxe" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Pronto para uma pausa em {} segundos", - "ready_for_a_long_break": "Pronto para uma pausa em {} segundos", - "disabled_until_restart": "Desativado até reiniciar", - "disabled_until_x": "Desativado até {}", - "next_break_at": "Próxima pausa em {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Sobre", - "allow_postpone": "Permite o adiamento da pausa", - "audible_alert": "Alerta sonoro no fim da pausa", - "cancel": "Cancelar", - "close": "Fechar", - "disable": "Desativar Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Habilitar Safe Eyes", - "enable_screen_lock": "Bloqueie a tela após cada pausa longa", - "for_x_hour": "Por {} Hora", - "for_x_hours": "Por {} Horas", - "for_x_minutes": "Por {} Minutos", - "idle_time": "Tempo mínimo de inatividade para pausar (em minutos)", - "interval_between_two_breaks": "Intervalo entre duas pausas", - "language": "Idioma", - "license": "Licença", - "long_break_duration": "Duração de uma pausa longa (em segundos)", - "no_of_short_breaks_between_two_long_breaks": "Número de pausas curtas entre duas pausas longas", - "postpone": "Adiar", - "postpone_duration": "Duração do adiamento (em minutos)", - "quit": "Sair", - "save": "Salvar", - "settings": "Configuração", - "short_break_duration": "Duração de uma pausa curta (em segundos)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Pular", - "strict_break": "Pausa rigorosa (Esconder botão pular)", - "system_language": "Linguagem do Sistema", - "time_to_prepare_for_break": "Tempo para se preparar para a pausa (em segundos)", - "time_to_screen_lock": "Tempo máximo para saltar, ignorando a tela de bloqueio (em segundos)", - "until_restart": "Até reiniciar" - } -} diff --git a/safeeyes/config/lang/ru.json b/safeeyes/config/lang/ru.json deleted file mode 100644 index 2211935..0000000 --- a/safeeyes/config/lang/ru.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Русский", - "language_name_en": "Russian" - }, - "app_info": { - "description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." - }, - "exercises": { - "short_break_close_eyes": "Сильно зажмурьтесь", - "short_break_roll_eyes": "Закатите глаза", - "short_break_rotate_clockwise": "Поводите глазами по кругу по часовой стрелке", - "short_break_rotate_counter_clockwise": "Поводите глазами по кругу против часовой стрелки", - "short_break_blink": "Поморгайте", - "short_break_focus_far_distance": "Сфокусируйте взгляд на далёком объекте", - "short_break_drink_water": "Выпейте воды", - "long_break_walk": "Пройдитесь немного", - "long_break_lean_back": "Откиньтесь на спинку стула и расслабьтесь" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Приготовьтесь к перерыву через {} секунд", - "ready_for_a_long_break": "Приготовьтесь к перерыву через {} секунд", - "disabled_until_restart": "Отключено до перезагрузки", - "disabled_until_x": "Отключено до {}", - "next_break_at": "Следующий перерыв в {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "О программе", - "allow_postpone": "Allow postponing the breaks", - "audible_alert": "Звуковой сигнал в конце перерыва", - "cancel": "Отменить", - "close": "Close", - "disable": "Отключить Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Активировать Safe Eyes", - "enable_screen_lock": "Включить блокировку экрана", - "for_x_hour": "На {} час", - "for_x_hours": "На {} часов", - "for_x_minutes": "На {} минут", - "idle_time": "Минимальное время простоя для паузы (в минутах)", - "interval_between_two_breaks": "Интервал между двумя перерывами", - "language": "Язык", - "license": "License", - "long_break_duration": "Продолжительность длинного перерыва (в секундах)", - "no_of_short_breaks_between_two_long_breaks": "Количество коротких перерывов между двумя длинными", - "postpone": "Отложить", - "postpone_duration": "Отложить на время (в минутах)", - "quit": "Выйти", - "save": "Сохранить", - "settings": "Настройки", - "short_break_duration": "Продолжительность короткого перерыва (в секундах)", - "show_time_in_tray": "Show the next break time in system tray", - "skip": "Пропустить", - "strict_break": "Обязательный перерыв (Скрыть кнопку 'Пропустить')", - "system_language": "System Language", - "time_to_prepare_for_break": "Время подготовки в перерыву (в секундах)", - "time_to_screen_lock": "Заблокировать экран если перерыв дольше(в секундах)", - "until_restart": "До перезагрузки" - } -} diff --git a/safeeyes/config/lang/sk.json b/safeeyes/config/lang/sk.json deleted file mode 100644 index eb3ae75..0000000 --- a/safeeyes/config/lang/sk.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Slovenský", - "language_name_en": "Slovak" - }, - "app_info": { - "description": "Safe Eyes vám pripomína robiť si prestávky, keď trávite dlhé hodiny u počítača a tak chráni vaše oči pred přílišným namáhaním (asthenopií)." - }, - "exercises": { - "short_break_close_eyes": "Pevne zavri oči", - "short_break_roll_eyes": "Gúľaj očami", - "short_break_rotate_clockwise": "Otáčaj oči v smere hodinových ručičiek", - "short_break_rotate_counter_clockwise": "Otáčaj oči v protismere hodinových ručičiek", - "short_break_blink": "Žmurkaj očami", - "short_break_focus_far_distance": "Zameraj sa na bod v diaľke", - "short_break_drink_water": "Daj si trochu vody", - "long_break_walk": "Pobehaj si chvíľku", - "long_break_lean_back": "Opri sa o kreslo a relaxuj" - }, - "messages": { - "audible_alert_disabled": "Nieje možné zapnúť zvukové upozornenie", - "ready_for_a_short_break": "Priprav sa na prestávku o {} sekúnd", - "ready_for_a_long_break": "Priprav sa na prestávku o {} sekúnd", - "disabled_until_restart": "Zakázať do reštartu", - "disabled_until_x": "Zakázať do {}", - "next_break_at": "Ďalšia prestávka o {}", - "software_required": "Pro ích povolenie nainštalujte {}" - }, - "ui_controls": { - "about": "Ohľadom", - "allow_postpone": "Povoliť odkladanie prestávok", - "audible_alert": "Zvukový signál na konci prestávky", - "cancel": "Zrušiť", - "close": "Close", - "disable": "Zakázať Safe Eyes", - "disable_keyboard_shortcut": "Dlžka po kterú zakázať klávesovú skratku v sekundách.", - "enable": "Povoliť Safe Eyes", - "enable_screen_lock": "Zablokovať obrazovku po každej dlhej prestávke", - "for_x_hour": "Počas {} hodiny", - "for_x_hours": "Počas {} hodín", - "for_x_minutes": "Počas {} minút", - "idle_time": "Pozastaviť pri nečinnosti dlhšej ako (v minútach)", - "interval_between_two_breaks": "Interval medzi dvomi prestávkami", - "language": "Jazyk", - "license": "Licencie", - "long_break_duration": "Trvanie dlhej prestávky (v sekundách)", - "no_of_short_breaks_between_two_long_breaks": "Počet krátkych prestávok medzi dvomi dlhými prestávkami", - "postpone": "Odložiť", - "postpone_duration": "O koĺko odložiť (minút)", - "quit": "Koniec", - "save": "Uložiť", - "settings": "Nastavenia", - "short_break_duration": "Trvanie krátkej prestávky (v sekundách)", - "show_time_in_tray": "Zobrazit čas následujúcej prestávky v system tray", - "skip": "Preskočiť", - "strict_break": "Povinná prestávka (Skryje tlačitko Preskočiť)", - "system_language": "Jazyk systému", - "time_to_prepare_for_break": "Čas na prípravu na prestávku (v sekundách)", - "time_to_screen_lock": "O koľko sekúnd najviac preskočiť (obchádza zámok obrazovky)", - "until_restart": "Do reštartu" - } -} diff --git a/safeeyes/config/lang/ta.json b/safeeyes/config/lang/ta.json deleted file mode 100644 index ceba12a..0000000 --- a/safeeyes/config/lang/ta.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "தமிழ்", - "language_name_en": "Tamil" - }, - "app_info": { - "description": "அடிக்கடி ஓய்வெடுக்க அறிவுறுத்துவதன் மூலம் Safe Eyes கணனியில் நீண்ட நேரம் பணியாற்றுவதால் ஏற்படும் கண் சோர்வை தடுக்கிறது." - }, - "exercises": { - "short_break_close_eyes": "உங்கள் கண்களை இறுக்கமாக மூடுங்கள்", - "short_break_roll_eyes": "உங்கள் கண்களை இடம் வலமாக உருட்டுங்கள்", - "short_break_rotate_clockwise": "உங்கள் கண்களை வலஞ்சுழியாக சுழற்றுங்கள்", - "short_break_rotate_counter_clockwise": "உங்கள் கண்களை இடஞ்சுழியாக சுழற்றுங்கள்", - "short_break_blink": "உங்கள் கண்களை சிமிட்டுங்கள்", - "short_break_focus_far_distance": "தூரத்தில் உள்ள ஒரு பொருளை பாருங்கள்", - "short_break_drink_water": "கொஞ்சம் தண்ணீர் குடியுங்கள்", - "long_break_walk": "சிறிது தூரம் நடவுங்கள்", - "long_break_lean_back": "கதிைரயில் பின்பக்கமாக சாய்ந்து ஓய்வெடுங்கள்" - }, - "messages": { - "audible_alert_disabled": "ஒலிச் சமிக்ஞையை செயற்படுத்த முடியாதுள்ளது", - "ready_for_a_short_break": "{} விநாடிகளில் குறுகிய இடைவேளைக்கு தயாராகுங்கள்", - "ready_for_a_long_break": "{} விநாடிகளில் நீண்ட இடைவேளைக்கு தயாராகுங்கள்", - "disabled_until_restart": "மறுதுவக்கம் வரை நிறுத்தி வைக்கப்பட்டுள்ளது", - "disabled_until_x": "{} வரை நிறுத்தி வைக்கப்பட்டுள்ளது", - "next_break_at": "அடுத்த இடைவேளை {}", - "software_required": "இந்த வசதியை செயற்படுத்த {} ஐ நிறுவவும்" - }, - "ui_controls": { - "about": "Safe Eyes குறித்த தகவல்கள்", - "allow_postpone": "ஒத்திவைக்க அனுமதிக்கவும்", - "audible_alert": "இடைவேளையின் இறுதியில் ஒலி சமிக்ஞை", - "cancel": "ரத்து", - "close": "மூடு", - "disable": "Safe Eyes ஐ நிறுத்துக", - "disable_keyboard_shortcut": "தற்செயல் பயன்பாட்டை தவிர்ப்பதற்காக விசைப்பலகை குறுக்குவழியை நிறுத்தி வைக்கும் நேரம் (விநாடிகளில்)", - "enable": "Safe Eyes ஐ செயல்படுத்துக", - "enable_screen_lock": "நீண்ட கால இடைவேளைகளின் பின்னர் திரையை பூட்டுக", - "for_x_hour": "{} மணித்தியாலத்திற்கு", - "for_x_hours": "{} மணித்தியாலங்களுக்கு", - "for_x_minutes": "{} நிமிடங்களுக்கு", - "idle_time": "இடைநிறுத்துவதற்கான குறைந்தபட்ச செயலற்ற நேரம் (நிமிடங்களில்)", - "interval_between_two_breaks": "இரண்டு இடைவேளைகளுக்கிடையிலான இடைவெளி (நிமிடங்களில்)", - "language": "மொழி", - "license": "மென்பொருள் உரிமம்", - "long_break_duration": "நீண்ட கால இடைவேளை (விநாடிகளில்)", - "no_of_short_breaks_between_two_long_breaks": "இரண்டு நீண்ட இடைவேளைகளுக்கிடையிலான குறுகிய இடைவேளைகள்", - "postpone": "ஒத்தி வை", - "postpone_duration": "இடைவேளையை ஒத்தி வைக்கும் காலம் (நிமிடங்களில்)", - "quit": "நிறுத்து", - "save": "சேமி", - "settings": "அமைப்பு", - "short_break_duration": "குறுகிய கால இடைவேளை (விநாடிகளில்)", - "show_time_in_tray": "அடுத்த இடைவேளை நேரத்தை Safe Eyes சின்னத்திற்கு அருகில் காண்பிக்கவும்", - "skip": "தவிர்", - "strict_break": "கட்டாய இடைவேளை (தவிர்க்கும் பொத்தான் காண்பிக்கப்பட மாட்டாது)", - "system_language": "இயங்குதள மொழி", - "time_to_prepare_for_break": "இடைவேளைக்கு தயாராக தேவைப்படும் நேரம் (விநாடிகளில்)", - "time_to_screen_lock": "திரையினை பூட்டாமல் இடைவேளையை தவிர்ப்பதற்கான அதிகபட்ச நேரம் (விநாடிகளில்)", - "until_restart": "மீள ஆரம்பிக்கும் வரைை" - } -} diff --git a/safeeyes/config/lang/tr.json b/safeeyes/config/lang/tr.json deleted file mode 100644 index a130468..0000000 --- a/safeeyes/config/lang/tr.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Türkçe", - "language_name_en": "Turkish" - }, - "app_info": { - "description": "Safe Eyes uzun bir süre boyunca bilgisayar başında çalışmanız gerektiği zamanlarda size mola vermeyi hatırlatmak suretiyle gözlerinizi göz yorgunluğuna (asthenopia) karşı korur." - }, - "exercises": { - "short_break_close_eyes": "Gözlerinizi sıkıca kapatın", - "short_break_roll_eyes": "Gözlerinizi çevirin", - "short_break_rotate_clockwise": "Gözlerinizi saat yönünde döndürün", - "short_break_rotate_counter_clockwise": "Gözlerinizi saat yönünün tersine döndürün", - "short_break_blink": "Göz kapaklarınızı kapatıp açın", - "short_break_focus_far_distance": "Uzak mesafede bir noktaya odaklanın", - "short_break_drink_water": "Gidip biraz su için", - "long_break_walk": "Biraz yürüyün", - "long_break_lean_back": "Arkanıza yaslanın ve biraz gevşeyin" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "{} Saniye içerisinde bir molaya hazır olun", - "ready_for_a_long_break": "{} Saniye içerisinde bir molaya hazır olun", - "disabled_until_restart": "Tekrar başlatılana kadar devre dışı", - "disabled_until_x": "{}'e kadar devre dışı", - "next_break_at": "Bir sonraki mola zamanı: {}", - "software_required": "Bu özelliği etkinleştirmek için lütfen {} yükleyiniz." - }, - "ui_controls": { - "about": "Hakkında", - "allow_postpone": "Mola ertelemeye izin ver", - "audible_alert": "Mola sonunda sesli uyarı", - "cancel": "İptal", - "close": "Kapat", - "disable": "Safe Eyes'ı devre dışı bırak", - "disable_keyboard_shortcut": "İstem dışı geçişi engellemek için kısayol tuşunun etkisiz kalma süresi (saniye)", - "enable": "Safe Eyes'ı etkinleştir", - "enable_screen_lock": "Her uzun mola sonunda ekranı kilitle", - "for_x_hour": "{} Saat", - "for_x_hours": "{} Saat", - "for_x_minutes": "{} Dakika", - "idle_time": "Minimum boş zaman (dakika)", - "interval_between_two_breaks": "İki mola arasındaki süre", - "language": "Dil", - "license": "Lisans", - "long_break_duration": "Uzun mola süresi (saniye)", - "no_of_short_breaks_between_two_long_breaks": "İki uzun mola arasındaki kısa mola sayısı", - "postpone": "Ertele", - "postpone_duration": "Ertelme süresi (dakika)", - "quit": "Çıkış", - "save": "Kaydet", - "settings": "Ayarlar", - "short_break_duration": "Kısa mola süresi (saniye)", - "show_time_in_tray": "Bir sonraki mola zamanını sistem bildirim alanında göster", - "skip": "Geç", - "strict_break": "Kesin mola (Geç düğmesini gizler)", - "system_language": "Sistem Dili", - "time_to_prepare_for_break": "Mola için hazırlanma zamanı (saniye)", - "time_to_screen_lock": "Ekran kilitlemeyi ertelemede geçilecek maksimum süre (saniye)", - "until_restart": "Tekrar başlatılana kadar" - } -} diff --git a/safeeyes/config/lang/uk.json b/safeeyes/config/lang/uk.json deleted file mode 100644 index 6bf82a3..0000000 --- a/safeeyes/config/lang/uk.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "Українська", - "language_name_en": "Ukrainian" - }, - "app_info": { - "description": "Safe Eyes нагадує вам робити перерви, при довгій роботі за комп'ютером, захищаючи ваші очі від втоми (asthenopia)." - }, - "exercises": { - "short_break_close_eyes": "Сильно заплющіть очі", - "short_break_roll_eyes": "Закотіть очі в різні боки", - "short_break_rotate_clockwise": "Покрутіть очима за годинниковою стрілкою", - "short_break_rotate_counter_clockwise": "Покрутіть очима проти годинникової стрілки", - "short_break_blink": "Поморгайте", - "short_break_focus_far_distance": "Подивіться на далекий об'єкт", - "short_break_drink_water": "Випийте води", - "long_break_walk": "Трохи пройдіться", - "long_break_lean_back": "Відкиньтеся на спинку і розслабтеся" - }, - "messages": { - "audible_alert_disabled": "Cannot enable audible notifications", - "ready_for_a_short_break": "Приготуйтеся до перерви через {} секунд", - "ready_for_a_long_break": "Приготуйтеся до перерви через {} секунд", - "disabled_until_restart": "Відключено до перезапуску", - "disabled_until_x": "Відключено до {}", - "next_break_at": "Наступна перерва о {}", - "software_required": "To enable this feature, please install {}" - }, - "ui_controls": { - "about": "Про SafeEyes", - "allow_postpone": "Дозволити відкладати перерви", - "audible_alert": "Звуковий сигнал вкінці перерви", - "cancel": "Відмінити", - "close": "Закрити", - "disable": "Відключити Safe Eyes", - "disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)", - "enable": "Включити Safe Eyes", - "enable_screen_lock": "Блокувати екран після довгих перерв", - "for_x_hour": "На {} годину", - "for_x_hours": "На {} години", - "for_x_minutes": "На {} хвилин", - "idle_time": "Час бездіяльності для паузи (в хвилинах)", - "interval_between_two_breaks": "Інтервал між двома перервами (в хвилинах)", - "language": "Мова", - "license": "Ліцензія", - "long_break_duration": "Тривалість довгої перерви (в секундах)", - "no_of_short_breaks_between_two_long_breaks": "Кількість коротких перерв між двома довгими", - "postpone": "Відкласти", - "postpone_duration": "На скільки відкласти (в хвилинах)", - "quit": "Вийти", - "save": "Зберегти", - "settings": "Налаштування", - "short_break_duration": "Тривалість короткої перерви (в секундах)", - "show_time_in_tray": "Показувати час наступної перерви в системному треї", - "skip": "Пропустити", - "strict_break": "Обов'язкова перерва (прибрати кнопку для пропускання)", - "system_language": "Мова системи", - "time_to_prepare_for_break": "Час підготовки до перерви (в секундах)", - "time_to_screen_lock": "Найбільший час для пропуску, без блокування екрану (в секундах)", - "until_restart": "До перезапуску" - } -} diff --git a/safeeyes/config/lang/vi.json b/safeeyes/config/lang/vi.json deleted file mode 100644 index 1e186cf..0000000 --- a/safeeyes/config/lang/vi.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "meta_info": - { - "language_name": "Tiếng Việt", - "language_name_en": "Vietnamese" - }, - "app_info": - { - "description": "Safe Eyes bảo vệ đôi mắt của bạn khỏi sự mệt mỏi (asthenopia) bằng cách nhắc nhở bạn nghỉ ngơi khi bạn đang làm việc trong một thời gian dài bên máy tính." - }, - "exercises": - { - "short_break_close_eyes": "Nhắm chặt mắt lại", - "short_break_roll_eyes": "Đảo đi đảo lại đôi mắt của bạn cho mỗi bên", - "short_break_rotate_clockwise": "Xoay mắt theo chiều kim đồng hồ", - "short_break_rotate_counter_clockwise": "Xoay mắt theo chiều ngược chiều kim đồng hồ", - "short_break_blink": "Chớp chớp mắt", - "short_break_focus_far_distance": "Nhìn ra xa", - "short_break_drink_water": "Uống một ít nước", - "long_break_walk": "Đứng dậy và đi lại một lúc", - "long_break_lean_back": "Tựa lưng vào ghế và thư giãn" - }, - "messages": - { - "audible_alert_disabled": "Không thể bật thông báo âm thanh", - "ready_for_a_short_break": "Sẵn sàng để nghỉ ngơi {} giây", - "ready_for_a_long_break": "Sẵn sàng để nghỉ ngơi {} giây", - "disabled_until_restart": "Vô hiệu hóa cho đến khi khởi động lại", - "disabled_until_x": "Vô hiệu hóa cho đến khi {}", - "next_break_at": "Giờ nghỉ ngơi tiếp theo là {}", - "software_required": "Để bật tính năng này, vui lòng cài đặt {}" - }, - "ui_controls": - { - "about": "Giới thiệu", - "allow_postpone": "Cho phép trì hoãn sự nghỉ ngơi", - "audible_alert": "Âm báo sau mỗi lần nghỉ", - "cancel": "Huỷ", - "close": "Đóng", - "disable": "Tắt Safe Eyes", - "disable_keyboard_shortcut": "Th.gian vô hiệu hóa phím tắt để tránh tắt nhầm Nhắc nhở khi nó hiện lên (giây)", - "enable": "Bật Safe Eyes", - "enable_screen_lock": "Khoá màn hình ở mỗi thời điểm nghỉ ngơi dài", - "for_x_hour": "Trong {} giờ", - "for_x_hours": "Trong {} giờ", - "for_x_minutes": "Trong {} phút", - "idle_time": "Thơi gian rỗi nhỏ nhất để tạm dừng (tính bằng phút)", - "interval_between_two_breaks": "Khoảng thời gian giữa hai lần nghỉ ngơi (tính bằng phút)", - "language": "Language (Ngôn ngữ)", - "license": "Giấy phép", - "long_break_duration": "Thời gian nghỉ ngơi dài (tính bằng giây)", - "no_of_short_breaks_between_two_long_breaks": "Số lần nghỉ ngơi ngắn giữa hai lần nghỉ ngơi dài", - "postpone": "Tạm hoãn", - "postpone_duration": "Thời gian tạm hoãn (tính bằng phút)", - "quit": "Thoát", - "save": "Lưu", - "settings": "Cài đặt", - "short_break_duration": "Thời gian nghỉ ngắn (tính bằng giây)", - "show_time_in_tray": "Hiển thị thời gian nghỉ ngắn trên khay hệ thống", - "skip": "Bỏ qua", - "strict_break": "Nghỉ ngơi nghiêm túc (Không cho phép bỏ qua)", - "system_language": "System Language (Ngôn ngữ hệ thống)", - "time_to_prepare_for_break": "Thời gian để chuẩn bị nghỉ ngơi (tính bằng giây)", - "time_to_screen_lock": "Thời gian bỏ qua tối đa, không tính thời gian khóa màn hình (tính bằng giây)", - "until_restart": "Cho đến khi khởi động lại" - } -} diff --git a/safeeyes/config/lang/zh_Hans.json b/safeeyes/config/lang/zh_Hans.json deleted file mode 100644 index 7f064d9..0000000 --- a/safeeyes/config/lang/zh_Hans.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "meta_info": { - "language_name": "简体中文", - "language_name_en": "Chinese" - }, - "app_info": { - "description": "当您持续使用电脑办公太长时间时,Safe Eyes 会提醒您休息一下,通过这种方式来保护您的眼睛,使您远离视疲劳。" - }, - "exercises": { - "short_break_close_eyes": "闭上您的眼睛休息一会", - "short_break_roll_eyes": "左右滚动一下您的眼珠", - "short_break_rotate_clockwise": "顺时针方向转动您的眼珠", - "short_break_rotate_counter_clockwise": "逆时针方向转动您的眼珠", - "short_break_blink": "眨眨眼", - "short_break_focus_far_distance": "望远:将视线聚焦到远处物体上", - "short_break_drink_water": "喝一点水", - "long_break_walk": "站起来走走", - "long_break_lean_back": "靠在椅背上休息一会" - }, - "messages": { - "audible_alert_disabled": "无法开启声音提示", - "ready_for_a_short_break": "{} 秒后将开始一个短休息", - "ready_for_a_long_break": "{} 秒后将开始一个长休息", - "disabled_until_restart": "暂停直到重启", - "disabled_until_x": "暂停直到 {}", - "next_break_at": "下一次休息开始时间:{}", - "software_required": "激活本功能需要安装 {}" - }, - "ui_controls": { - "about": "关于", - "allow_postpone": "允许推迟休息", - "audible_alert": "休息结束时播放声音提示", - "cancel": "取消", - "close": "关闭", - "disable": "禁用Safe Eyes", - "disable_keyboard_shortcut": "休息界面按钮不可用时间(防止操作失误而跳过休息,单位秒)", - "enable": "启用Safe Eyes", - "enable_screen_lock": "每次长休息结束时锁定屏幕", - "for_x_hour": "{} 小时", - "for_x_hours": "{} 小时", - "for_x_minutes": "{} 分钟", - "idle_time": "电脑无操作多长时间时暂停计时(单位:分钟)", - "interval_between_two_breaks": "两次休息之间的间隔时间(单位:分钟)", - "language": "语言", - "license": "许可证", - "long_break_duration": "长休息时长(单位:秒)", - "no_of_short_breaks_between_two_long_breaks": "在两次长休息之间的短休息个数", - "postpone": "推迟休息", - "postpone_duration": "推迟时间间隔(单位:分钟)", - "quit": "退出", - "save": "保存", - "settings": "设置", - "short_break_duration": "短休息时长(单位:秒)", - "show_time_in_tray": "在系统托盘显示下一次休息时间", - "skip": "跳过", - "strict_break": "严格休息(隐藏跳过按钮)", - "system_language": "系统语言", - "time_to_prepare_for_break": "休息前的准备时长(单位:秒)", - "time_to_screen_lock": "长休息开始几秒后自动锁屏", - "until_restart": "直到重启" - } -} diff --git a/safeeyes/config/locale/en_US/LC_MESSAGES/safeeyes.po b/safeeyes/config/locale/en_US/LC_MESSAGES/safeeyes.po new file mode 100644 index 0000000..beca47c --- /dev/null +++ b/safeeyes/config/locale/en_US/LC_MESSAGES/safeeyes.po @@ -0,0 +1,310 @@ +# SAFE EYES ENGLISH TRANSLATION. +# Copyright (C) 2017 Gobinath +# Gobinath slgobinath@gmail.com, 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2017-09-17 07:59-0400\n" +"PO-Revision-Date: 2017-09-17 07:59-0400\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Generator: Poedit 1.8.7.1\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: en_US\n" + +# Short Breaks +msgid "Tightly close your eyes" +msgstr "Tightly close your eyes" + +msgid "Roll your eyes a few times to each side" +msgstr "Roll your eyes a few times to each side" + +msgid "Rotate your eyes in clockwise direction" +msgstr "Rotate your eyes in clockwise direction" + +msgid "Rotate your eyes in counterclockwise direction" +msgstr "Rotate your eyes in counterclockwise direction" + +msgid "Blink your eyes" +msgstr "Blink your eyes" + +msgid "Focus on a point in the far distance" +msgstr "Focus on a point in the far distance" + +msgid "Have some water" +msgstr "Have some water" + +# Long Breaks +msgid "Walk for a while" +msgstr "Walk for a while" + +msgid "Lean back at your seat and relax" +msgstr "Lean back at your seat and relax" + +# Commandline Arguments +msgid "show the about dialog" +msgstr "show the about dialog" + +msgid "disable the currently running safeeyes instance" +msgstr "disable the currently running safeeyes instance" + +msgid "enable the currently running safeeyes instance" +msgstr "enable the currently running safeeyes instance" + +msgid "quit the running safeeyes instance and exit" +msgstr "quit the running safeeyes instance and exit" + +msgid "show the settings dialog" +msgstr "show the settings dialog" + +msgid "start safeeyes in debug mode" +msgstr "start safeeyes in debug mode" + +# AboutDialog +msgid "Close" +msgstr "Close" + +msgid "description" +msgstr "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer." + +msgid "License" +msgstr "License" + +# BreakScreen +msgid "Skip" +msgstr "Skip" + +msgid "Postpone" +msgstr "Postpone" + +# Settings dialog +msgid "Break duration (in seconds)" +msgstr "Break duration (in seconds)" + +msgid "Interval between two breaks (in minutes)" +msgstr "Interval between two breaks (in minutes)" + +msgid "Time to prepare for a break (in seconds)" +msgstr "Time to prepare for a break (in seconds)" + +msgid "Keyboard shortcuts disabled period (in seconds)" +msgstr "Keyboard shortcuts disabled period (in seconds)" + +msgid "Postpone duration (in minutes)" +msgstr "Postpone duration (in minutes)" + +msgid "Strict break (No way to skip breaks)" +msgstr "Strict break (No way to skip breaks)" + +msgid "Allow postponing breaks" +msgstr "Allow postponing breaks" + +msgid "Persist the internal state" +msgstr "Persist the internal state" + +msgid "Long break interval must be a multiple of short break interval" +msgstr "Long break interval must be a multiple of short break interval" + +msgid "Options" +msgstr "Options" + +msgid "Short Breaks" +msgstr "Short Breaks" + +msgid "Long Breaks" +msgstr "Long Breaks" + +msgid "Break" +msgstr "Break" + +msgid "Breaks" +msgstr "Breaks" + +msgid "Plugins" +msgstr "Plugins" + +msgid "Type" +msgstr "Type" + +msgid "Short" +msgstr "Short" + +msgid "Long" +msgstr "Long" + +msgid "Image" +msgstr "Image" + +msgid "Select" +msgstr "Select" + +msgid "Please select an image" +msgstr "Please select an image" + +msgid "Duration" +msgstr "Duration" + +msgid "Override" +msgstr "Override" + +msgid "Time (in seconds)" +msgstr "Time (in seconds)" + +msgid "Break Settings" +msgstr "Break Settings" + +msgid "Plugin Settings" +msgstr "Plugin Settings" + +msgid "Plugin does not support %s desktop environment" +msgstr "Plugin does not support %s desktop environment" + +msgid "Please install the Python module '%s'" +msgstr "Please install the Python module '%s'" + +msgid "Please install the command-line tool '%s'" +msgstr "Please install the command-line tool '%s'" + +msgid "Please add the resource %(resource)s to %(config_resource)s directory" +msgstr "Please add the resource %(resource)s to %(config_resource)s directory" + +msgid "New Break" +msgstr "New Break" + +msgid "Remove" +msgstr "Remove" + +msgid "Discard" +msgstr "Discard" + +msgid "Save" +msgstr "Save" + +# plugin/audiblealert +msgid "Audible Alert" +msgstr "Audible Alert" + +msgid "Play an audible alert at the end of breaks" +msgstr "Play an audible alert at the end of breaks" + +# plugin/donotdisturb +msgid "Do Not Disturb" +msgstr "Do Not Disturb" + +msgid "Skip the break if the active window is in fullscreen mode" +msgstr "Skip the break if the active window is in fullscreen mode" + +msgid "Do not interrupt these windows anytime" +msgstr "Do not interrupt these windows anytime" + +msgid "Interrupt these windows regardless of their state" +msgstr "Interrupt these windows regardless of their state" + +msgid "Switch the interruptible windows to normal mode" +msgstr "Switch the interruptible windows to normal mode" + +# plugin/healthstats +msgid "Health Statistics" +msgstr "Health Statistics" + +msgid "Show statistics based on how you use Safe Eyes" +msgstr "Show statistics based on how you use Safe Eyes" + +# plugin/notification +msgid "Notification" +msgstr "Notification" + +msgid "Show a system notification before breaks" +msgstr "Show a system notification before breaks" + +msgid "Ready for a short break in %s seconds" +msgstr "Ready for a short break in %s seconds" + +msgid "Ready for a long break in %s seconds" +msgstr "Ready for a long break in %s seconds" + +# plugin/screensaver +msgid "Screensaver" +msgstr "Screensaver" + +msgid "Lock the screen after long breaks by starting screensaver" +msgstr "Lock the screen after long breaks by starting screensaver" + +msgid "Custom screensaver command" +msgstr "Custom screensaver command" + +msgid "Minimum seconds to skip without screensaver" +msgstr "Minimum seconds to skip without screensaver" + +# plugin/smartpause +msgid "Smart Pause" +msgstr "Smart Pause" + +msgid "Pause Safe Eyes if the system is idle" +msgstr "Pause Safe Eyes if the system is idle" + +msgid "Minimum idle time to pause Safe Eyes" +msgstr "Minimum idle time to pause Safe Eyes" + +#: plugins/trayicon +msgid "Tray Icon" +msgstr "Tray Icon" + +msgid "Show a tray icon in the notification area" +msgstr "Show a tray icon in the notification area" + +msgid "Show next break time in tray icon" +msgstr "Show next break time in tray icon" + +msgid "About" +msgstr "About" + +msgid "Disable Safe Eyes" +msgstr "Disable Safe Eyes" + +msgid "Disabled until %s" +msgstr "Disabled until %s" + +msgid "Disabled until restart" +msgstr "Disabled until restart" + +msgid "Enable Safe Eyes" +msgstr "Enable Safe Eyes" + +msgid "For %d Hour" +msgid_plural "For %d Hours" +msgstr[0] "For %d Hour" +msgstr[1] "For %d Hours" + +msgid "For %d Minute" +msgid_plural "For %d Minutes" +msgstr[0] "For %d Minute" +msgstr[1] "For %d Minutes" + +msgid "For %d Second" +msgid_plural "For %d Seconds" +msgstr[0] "For %d Second" +msgstr[1] "For %d Seconds" + +msgid "Next break at %s" +msgstr "Next break at %s" + +msgid "No Breaks Available" +msgstr "No Breaks Available" + +msgid "Settings" +msgstr "Settings" + +msgid "Take a break now" +msgstr "Take a break now" + +msgid "Until restart" +msgstr "Until restart" + +msgid "Quit" +msgstr "Quit" diff --git a/safeeyes/config/locale/ta/LC_MESSAGES/safeeyes.po b/safeeyes/config/locale/ta/LC_MESSAGES/safeeyes.po new file mode 100644 index 0000000..8bd31b2 --- /dev/null +++ b/safeeyes/config/locale/ta/LC_MESSAGES/safeeyes.po @@ -0,0 +1,310 @@ +# SAFE EYES TAMIL TRANSLATION. +# Copyright (C) 2017 Gobinath +# Gobinath slgobinath@gmail.com, 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2017-09-17 07:59-0400\n" +"PO-Revision-Date: 2017-09-17 07:59-0400\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Generator: Poedit 1.8.7.1\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: ta\n" + +# Short Breaks +msgid "Tightly close your eyes" +msgstr "உங்கள் கண்களை இறுக்கமாக மூடுங்கள்" + +msgid "Roll your eyes a few times to each side" +msgstr "உங்கள் கண்களை இடம் வலமாக உருட்டுங்கள்" + +msgid "Rotate your eyes in clockwise direction" +msgstr "உங்கள் கண்களை வலஞ்சுழியாக சுழற்றுங்கள்" + +msgid "Rotate your eyes in counterclockwise direction" +msgstr "உங்கள் கண்களை இடஞ்சுழியாக சுழற்றுங்கள்" + +msgid "Blink your eyes" +msgstr "உங்கள் கண்களை சிமிட்டுங்கள்" + +msgid "Focus on a point in the far distance" +msgstr "தூரத்தில் உள்ள ஒரு பொருளை பாருங்கள்" + +msgid "Have some water" +msgstr "கொஞ்சம் தண்ணீர் குடியுங்கள்" + +# Long Breaks +msgid "Walk for a while" +msgstr "சிறிது தூரம் நடவுங்கள்" + +msgid "Lean back at your seat and relax" +msgstr "கதிைரயில் பின்பக்கமாக சாய்ந்து ஓய்வெடுங்கள்" + +# Commandline Arguments +msgid "show the about dialog" +msgstr "தகவல் சாளரத்தை காண்பி" + +msgid "disable the currently running safeeyes instance" +msgstr "செயற்பட்டு கொண்டிருக்கும் Safe Eyes ஐ முடக்கு" + +msgid "enable the currently running safeeyes instance" +msgstr "முடக்க பட்டிருக்கும் Safe Eyes ஐ செயல்படுத்துக" + +msgid "quit the running safeeyes instance and exit" +msgstr "செயற்பட்டு கொண்டிருக்கும் Safe Eyes ஐ நிறுத்துக" + +msgid "show the settings dialog" +msgstr "அமைப்புகள் சாளரத்தை காண்பி" + +msgid "start safeeyes in debug mode" +msgstr "வழு நீக்கும் முறையில் Safe Eyes ஐ ஆரம்பிக்குக" + +# AboutDialog +msgid "Close" +msgstr "மூடு" + +msgid "description" +msgstr "அடிக்கடி ஓய்வெடுக்க அறிவுறுத்துவதன் மூலம் Safe Eyes கணனியில் நீண்ட நேரம் பணியாற்றுவதால் ஏற்படும் கண் சோர்வை தடுக்கிறது." + +msgid "License" +msgstr "மென்பொருள் உரிமம்" + +# BreakScreen +msgid "Skip" +msgstr "தவிர்" + +msgid "Postpone" +msgstr "ஒத்தி வை" + +# Settings dialog +msgid "Break duration (in seconds)" +msgstr "இடைவேளையின் கால அளவு (விநாடிகளில்)" + +msgid "Interval between two breaks (in minutes)" +msgstr "இரண்டு இடைவேளைகளுக்கிடையிலான இடைவெளி (நிமிடங்களில்)" + +msgid "Time to prepare for a break (in seconds)" +msgstr "இடைவேளைக்கு தயாராக தேவைப்படும் நேரம் (விநாடிகளில்)" + +msgid "Keyboard shortcuts disabled period (in seconds)" +msgstr "விசைப்பலகை குறுக்குவழிகள் முடக்கப்பட்டிருக்கும் காலம் (விநாடிகளில்)" + +msgid "Postpone duration (in minutes)" +msgstr "இடைவேளையை ஒத்தி வைக்கும் காலம் (நிமிடங்களில்)" + +msgid "Strict break (No way to skip breaks)" +msgstr "கட்டாய இடைவேளை (இடைவேளையை தவிர்க்க முடியாது)" + +msgid "Allow postponing breaks" +msgstr "இடைவேளையை ஒத்திவைக்க அனுமதிக்கவும்" + +msgid "Persist the internal state" +msgstr "உள்ளக நிலையை தக்கவைத்து கொள்ளவும்" + +msgid "Long break interval must be a multiple of short break interval" +msgstr "நீண்ட இடைவேளைகளுக்கிடையிலான நேரம் குறுகிய இடைவேளைகளுக்கிடையிலான நேரத்தின் மடங்காக இருக்க வேண்டும்" + +msgid "Options" +msgstr "தேர்வுகள்" + +msgid "Short Breaks" +msgstr "குறுகிய இடைவேளைகள்" + +msgid "Long Breaks" +msgstr "நீண்ட இடைவேளைகள்" + +msgid "Break" +msgstr "இடைவேளை" + +msgid "Breaks" +msgstr "இடைவேளைகள்" + +msgid "Plugins" +msgstr "நீட்சிகள்" + +msgid "Type" +msgstr "வகை" + +msgid "Short" +msgstr "குறுகியது" + +msgid "Long" +msgstr "நீண்டது" + +msgid "Image" +msgstr "படம்" + +msgid "Select" +msgstr "தேர்ந்தெடு" + +msgid "Please select an image" +msgstr "ஒரு படத்தைத் தேர்ந்தெடுக்கவும்" + +msgid "Duration" +msgstr "கால அளவு" + +msgid "Override" +msgstr "இயல்புநிலை அமைப்புகளை மீறவும்" + +msgid "Time (in seconds)" +msgstr "நேரம் (விநாடிகளில்)" + +msgid "Break Settings" +msgstr "இடைவேளை அமைப்புகள்" + +msgid "Plugin Settings" +msgstr "நீட்சி அமைப்புகள்" + +msgid "Plugin does not support %s desktop environment" +msgstr "இந்த நீட்சி %s முகத்திரை சூழலை ஆதரிக்கவில்லை" + +msgid "Please install the Python module '%s'" +msgstr "தயவுசெய்து '%s' Python தொகுப்பை நிறுவவும்" + +msgid "Please install the command-line tool '%s'" +msgstr "தயவுசெய்து '%s' முனைய கட்டளையை நிறுவுக" + +msgid "Please add the resource %(resource)s to %(config_resource)s directory" +msgstr "தயவுசெய்து %(resource)s வளத்தை %(config_resource)s கோப்புறையில் சேர்க்கவும்" + +msgid "New Break" +msgstr "புதிய இடைவேளை" + +msgid "Remove" +msgstr "அகற்று" + +msgid "Discard" +msgstr "நிராகரி" + +msgid "Save" +msgstr "சேமி" + +# plugin/audiblealert +msgid "Audible Alert" +msgstr "ஒலிச் சமிக்ஞய்" + +msgid "Play an audible alert at the end of breaks" +msgstr "இடைவேளைகளின் முடிவில் ஒலிச் சமிக்ஞய்" + +# plugin/donotdisturb +msgid "Do Not Disturb" +msgstr "தொந்தரவு செய்ய வேண்டாம்" + +msgid "Skip the break if the active window is in fullscreen mode" +msgstr "பாவனையிலுள்ள சாளரம் முழுத்திரையில் இருப்பின் இடைவேளையை தவிர்" + +msgid "Do not interrupt these windows anytime" +msgstr "இந்த சாளரங்கள் எந்த நிலையிலிருந்தாலும் குறுக்கிட வேண்டாம்" + +msgid "Interrupt these windows regardless of their state" +msgstr "இந்த சாளரங்களின் நிலையை பொருட்படுத்தாது குறுக்கிடுக" + +msgid "Switch the interruptible windows to normal mode" +msgstr "குறுக்கீடு செய்யும் போது சாளரங்களை சாதாரண நிலைக்கு மாற்றுக" + +# plugin/healthstats +msgid "Health Statistics" +msgstr "ஆரோக்கிய புள்ளிவிவரங்கள்" + +msgid "Show statistics based on how you use Safe Eyes" +msgstr "Safe Eyes பயன்பாடு அடிப்படையிலான ஆரோக்கிய புள்ளிவிவரங்கள்" + +# plugin/notification +msgid "Notification" +msgstr "முன்னறிவிப்பு" + +msgid "Show a system notification before breaks" +msgstr "இடைவேளைகளின் முன் தயார்படுத்தலுக்கான அறிவிப்பு" + +msgid "Ready for a short break in %s seconds" +msgstr "%s விநாடிகளில் குறுகிய இடைவேளைக்கு தயாராகுங்கள்" + +msgid "Ready for a long break in %s seconds" +msgstr "{} விநாடிகளில் நீண்ட இடைவேளைக்கு தயாராகுங்கள்" + +# plugin/screensaver +msgid "Screensaver" +msgstr "திரைக் காப்பு" + +msgid "Lock the screen after long breaks by starting screensaver" +msgstr "நீண்ட இடைவேளைகளின் பின் திரைக்காப்பு மென்பொருள் இயக்கம்" + +msgid "Custom screensaver command" +msgstr "மாறுபட்ட திரைக்காப்பு கட்டளை" + +msgid "Minimum seconds to skip without screensaver" +msgstr "திரைக்காப்பு மென்பொருளை செயற்படுத்தாமல் தவிர்ப்பதற்கான வினாடிகள்" + +# plugin/smartpause +msgid "Smart Pause" +msgstr "நுண்ணறிவு இடைநிறுத்தம்" + +msgid "Pause Safe Eyes if the system is idle" +msgstr "கணினி செயலற்று இருந்தால் Safe Eyesஐ இடைநிறுத்துக" + +msgid "Minimum idle time to pause Safe Eyes" +msgstr "Safe Eyesஐ இடைநிறுத்துவதற்கான குறைந்தபட்ச செயலற்ற வினாடிகள்" + +#: plugins/trayicon +msgid "Tray Icon" +msgstr "Safe Eyes சின்னம்" + +msgid "Show a tray icon in the notification area" +msgstr "அறிவிப்பு பகுதியில் Safe Eyes சின்னம்" + +msgid "Show next break time in tray icon" +msgstr "அடுத்த இடைவேளை நேரத்தை Safe Eyes சின்னத்திற்கு அருகில் காண்பிக்கவும்" + +msgid "About" +msgstr "Safe Eyes குறித்த தகவல்கள்" + +msgid "Disable Safe Eyes" +msgstr "Safe Eyes ஐ நிறுத்துக" + +msgid "Disabled until %s" +msgstr "%s வரை நிறுத்தி வைக்கப்பட்டுள்ளது" + +msgid "Disabled until restart" +msgstr "மறுதுவக்கம் வரை நிறுத்தி வைக்கப்பட்டுள்ளது" + +msgid "Enable Safe Eyes" +msgstr "Safe Eyes ஐ செயல்படுத்துக" + +msgid "For %d Hour" +msgid_plural "For %d Hours" +msgstr[0] "%d மணித்தியாலத்திற்கு" +msgstr[1] "%d மணித்தியாலங்களுக்கு" + +msgid "For %d Minute" +msgid_plural "For %d Minutes" +msgstr[0] "%d நிமிடத்திற்கு" +msgstr[1] "%d நிமிடங்களுக்கு" + +msgid "For %d Second" +msgid_plural "For %d Seconds" +msgstr[0] "%d வினாடிக்கு" +msgstr[1] "%d வினாடிகளுக்கு" + +msgid "Next break at %s" +msgstr "அடுத்த இடைவேளை %s" + +msgid "No Breaks Available" +msgstr "இடைவேளைகள் வரையறுக்கப்படவில்லை" + +msgid "Settings" +msgstr "அமைப்பு" + +msgid "Take a break now" +msgstr "சுயவிருப்பிலான இடைவேளை" + +msgid "Until restart" +msgstr "மீள ஆரம்பிக்கும் வரை" + +msgid "Quit" +msgstr "நிறுத்து" diff --git a/safeeyes/config/safeeyes.json b/safeeyes/config/safeeyes.json index 8cc7603..7feb1a8 100644 --- a/safeeyes/config/safeeyes.json +++ b/safeeyes/config/safeeyes.json @@ -1,85 +1,119 @@ { "meta": { - "config_version": "5.0.1" + "config_version": "6.0.0" }, "allow_postpone": false, "break_interval": 15, - "enable_screen_lock": false, "long_break_duration": 60, "no_of_short_breaks_per_long_break": 5, "pre_break_warning_time": 10, "short_break_duration": 15, - "idle_time": 5, - "persist_state": true, + "persist_state": false, "postpone_duration": 5, + "rpc_port": 7200, "shortcut_disable_time": 2, "shortcut_skip": 9, "shortcut_postpone": 65, - "show_time_in_tray": false, "strict_break": false, - "audible_alert": false, - "language": "system", - "time_to_screen_lock": 5, - "lock_screen_command": [], - "active_window_class": { - "skip_break": [], - "take_break": [] - }, - "disable_options": [ - { - "label": "for_x_minutes", - "time": 30, - "unit": "minute" - }, - { - "label": "for_x_hour", - "time": 1, - "unit": "hour" - }, - { - "label": "for_x_hours", - "time": 2, - "unit": "hour" - }, - { - "label": "for_x_hours", - "time": 3, - "unit": "hour" - } - ], "short_breaks": [ { - "name": "short_break_close_eyes" + "name": "Tightly close your eyes" }, { - "name": "short_break_roll_eyes" + "name": "Roll your eyes a few times to each side" }, { - "name": "short_break_rotate_clockwise" + "name": "Rotate your eyes in clockwise direction" }, { - "name": "short_break_rotate_counter_clockwise" + "name": "Rotate your eyes in counterclockwise direction" }, { - "name": "short_break_blink" + "name": "Blink your eyes" }, { - "name": "short_break_focus_far_distance" + "name": "Focus on a point in the far distance" }, { - "name": "short_break_drink_water" + "name": "Have some water" } ], "long_breaks": [ { - "name": "long_break_walk" + "name": "Walk for a while" }, { - "name": "long_break_lean_back" + "name": "Lean back at your seat and relax" } ], - "custom_exercises": { - - }, - "plugins": [] + "plugins": [ + { + "id": "donotdisturb", + "enabled": true, + "version": "0.0.1", + "settings": { + "skip_break_windows": "", + "take_break_windows": "", + "unfullscreen": true + } + }, + { + "id": "notification", + "enabled": true, + "version": "0.0.1" + }, + { + "id": "audiblealert", + "enabled": true, + "version": "0.0.1" + }, + { + "id": "trayicon", + "enabled": true, + "version": "0.0.1", + "settings": { + "show_time_in_tray": false, + "disable_options": [ + { + "time": 30, + "unit": "minute" + }, + { + "time": 1, + "unit": "hour" + }, + { + "time": 2, + "unit": "hour" + }, + { + "time": 3, + "unit": "hour" + } + ] + } + }, + { + "id": "smartpause", + "enabled": true, + "version": "0.0.1", + "settings": { + "idle_time": 3 + } + }, + { + "id": "screensaver", + "enabled": true, + "version": "0.0.1", + "settings": { + "command": "", + "min_seconds": 3 + } + }, + { + "id": "healthstats", + "enabled": false, + "version": "0.0.1" + } + ] } diff --git a/safeeyes/config/style/safeeyes_style.css b/safeeyes/config/style/safeeyes_style.css index 0ce8703..1d789e6 100644 --- a/safeeyes/config/style/safeeyes_style.css +++ b/safeeyes/config/style/safeeyes_style.css @@ -72,8 +72,24 @@ color: white; } -.panel_left { +.lbl_widget { + font-size: 9pt; + color: white; } -.panel_right { +.btn_properties { + border-radius: 15px; } + +.lbl_plugin_name { + font-weight: bold; +} + +.lbl_plugin_description { + color: #9B9B9B; +} + +.info_bar_long_break { + opacity: 0.9; + border-radius: 5px; +} \ No newline at end of file diff --git a/safeeyes/glade/about_dialog.glade b/safeeyes/glade/about_dialog.glade index 13f4013..b3f43d3 100644 --- a/safeeyes/glade/about_dialog.glade +++ b/safeeyes/glade/about_dialog.glade @@ -36,31 +36,32 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. - 400 False Safe Eyes False - center - 350 - 200 + center-always safeeyes + dialog + center True False - start - start 5 5 5 5 + True + True vertical - 2 + top True False + start + True vertical @@ -70,7 +71,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.center 10 10 - Safe Eyes 1.1.1 + Safe Eyes 2.0.0 center @@ -91,6 +92,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer. fill True + 60 60 @@ -118,14 +120,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. True True + True + True False word - fill text_buffer_license False - False + True True 3 @@ -135,10 +138,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.http://slgobinath.github.io/SafeEyes True True + False True center none - False http://slgobinath.github.io/SafeEyes @@ -171,9 +174,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. True False - end + start 5 - 10 end diff --git a/safeeyes/glade/break_screen.glade b/safeeyes/glade/break_screen.glade index dab685f..f5b7aae 100644 --- a/safeeyes/glade/break_screen.glade +++ b/safeeyes/glade/break_screen.glade @@ -34,162 +34,166 @@ center - + True False + True + True - + True False - start - start - 10 - 10 - 10 - - True - True - 0 + 0 + 0 - + True False - center - center - 10 + vertical + 10 - + True False center center - True - 15 + 10 - + True False - Hello World - center - 0 0 - 3 - + True False center - 0.20000000298023224 + center + True + 15 - + True False - 00 + Hello World + center + + 0 + 0 + 3 + + + + + True + False + center + 0.20000000298023224 + + + True + False + 00 + + + + + + 1 + 1 + + + + + True + False + center + 50 + True + + + + + + + + + 1 + 3 + + + + + + + + + + + + + + + + + + + + + + - 1 + 0 1 + 3 - - - True - False - center - 50 - True - - - - - - - - - 1 - 3 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 1 - 3 + False + True + 0 - + True False + Widget + 0.25 + - 0 - 0 + True + True + end + 1 - False - True - 1 - - - - - True - False - end - start - 10 - 10 - 10 - - - - True - True - 2 + 0 + 1 + 2 diff --git a/safeeyes/glade/item_bool.glade b/safeeyes/glade/item_bool.glade new file mode 100644 index 0000000..cbc57d3 --- /dev/null +++ b/safeeyes/glade/item_bool.glade @@ -0,0 +1,61 @@ + + + + + + + True + False + 5 + 5 + 5 + 5 + 10 + + + True + False + center + label + 0 + + + True + True + 0 + + + + + True + True + end + center + + + False + False + end + 1 + + + + diff --git a/safeeyes/glade/item_break.glade b/safeeyes/glade/item_break.glade new file mode 100644 index 0000000..9396a22 --- /dev/null +++ b/safeeyes/glade/item_break.glade @@ -0,0 +1,70 @@ + + + + + + + True + False + 5 + 5 + 5 + 5 + + + True + False + center + label + 0 + + + True + True + 0 + + + + + True + True + True + center + center + img_properties + True + + + + False + True + 1 + + + + + True + False + gtk-properties + + diff --git a/safeeyes/glade/item_int.glade b/safeeyes/glade/item_int.glade new file mode 100644 index 0000000..ce557ea --- /dev/null +++ b/safeeyes/glade/item_int.glade @@ -0,0 +1,66 @@ + + + + + + + 100 + 1 + 10 + + + True + False + 5 + 5 + 5 + 5 + 10 + + + True + False + center + label + 0 + + + True + True + 0 + + + + + True + True + end + center + adjustment_value + + + False + True + 1 + + + + diff --git a/safeeyes/glade/item_plugin.glade b/safeeyes/glade/item_plugin.glade new file mode 100644 index 0000000..3080117 --- /dev/null +++ b/safeeyes/glade/item_plugin.glade @@ -0,0 +1,146 @@ + + + + + + + True + False + gtk-properties + + + True + False + 5 + 5 + 5 + 5 + + + True + False + center + center + gtk-about + + + False + True + 0 + + + + + True + False + 5 + vertical + + + True + False + start + end + Plugin Name + 0.05000000074505806 + 1 + + + + True + True + 0 + + + + + True + False + start + start + Plugin Description + 0.05000000074505806 + 0 + + + + True + True + 1 + + + + + True + True + 1 + + + + + True + False + 5 + + + True + True + end + center + + + False + True + 0 + + + + + True + True + True + center + center + img_properties + True + + + + False + False + 1 + + + + + False + True + 2 + + + + diff --git a/safeeyes/glade/item_text.glade b/safeeyes/glade/item_text.glade new file mode 100644 index 0000000..c8eedf9 --- /dev/null +++ b/safeeyes/glade/item_text.glade @@ -0,0 +1,60 @@ + + + + + + + True + False + 5 + 5 + 5 + 5 + 10 + + + True + False + center + label + 0 + + + True + True + 0 + + + + + True + True + end + center + + + False + True + 1 + + + + diff --git a/safeeyes/glade/new_break.glade b/safeeyes/glade/new_break.glade new file mode 100644 index 0000000..66716cc --- /dev/null +++ b/safeeyes/glade/new_break.glade @@ -0,0 +1,214 @@ + + + + + + + 100 + 1 + 10 + + + + + + + + + Short + + + Long + + + + + False + Properties + False + True + center-on-parent + 500 + 50 + True + safeeyes + dialog + True + center + + + + True + False + 10 + 10 + 10 + 10 + vertical + 5 + + + True + False + 0 + + + True + False + 12 + + + True + False + 10 + 5 + 10 + vertical + 3 + + + True + True + center + 64 + + + False + True + 0 + + + + + True + False + 10 + + + True + False + start + center + Type + + + False + True + 0 + + + + + True + False + lst_break_types + 0 + 0 + + + + 0 + + + + + False + True + end + 1 + + + + + False + True + 1 + + + + + + + + + + + + True + False + Break + + + + + False + False + 0 + + + + + True + False + 10 + True + top + end + + + Discard + True + True + True + + + + True + True + 0 + + + + + Save + True + True + True + True + + + + True + True + 1 + + + + + False + False + 3 + + + + + + diff --git a/safeeyes/glade/settings_break.glade b/safeeyes/glade/settings_break.glade new file mode 100644 index 0000000..f23ac84 --- /dev/null +++ b/safeeyes/glade/settings_break.glade @@ -0,0 +1,480 @@ + + + + + + + 100 + 1 + 10 + + + True + False + gtk-missing-image + + + + + + + + + Short + + + Long + + + + + False + Properties + False + True + center-on-parent + 500 + 50 + True + safeeyes + dialog + True + center + + + + True + False + 10 + 10 + 10 + 10 + vertical + 5 + + + True + False + 0 + + + True + False + 12 + + + True + False + 10 + 5 + 10 + vertical + 3 + + + True + True + center + + + False + True + 0 + + + + + True + False + 10 + + + True + False + start + center + Type + + + False + True + 0 + + + + + True + False + lst_break_types + 0 + 0 + + + + 0 + + + + + False + True + end + 1 + + + + + False + True + 1 + + + + + True + False + 10 + + + True + False + start + center + Image + + + False + True + 0 + + + + + Select + True + True + True + img_break + True + + + + False + True + end + 1 + + + + + False + True + 2 + + + + + + + + + True + False + Break + + + + + False + False + 0 + + + + + True + False + 0 + + + True + False + 12 + + + True + False + 10 + 5 + 10 + vertical + 3 + + + True + False + 10 + + + True + False + start + center + Override + + + False + True + 0 + + + + + True + True + end + center + + + False + True + end + 1 + + + + + False + True + 0 + + + + + True + False + 100 + + + True + False + start + center + Time (in seconds) + + + False + True + 0 + + + + + True + True + end + center + adjustment_duration + + + False + True + end + 1 + + + + + False + True + 1 + + + + + + + + + True + False + Duration + + + + + False + False + 1 + + + + + True + False + 0 + + + True + False + 12 + + + True + False + 5 + 10 + vertical + 3 + + + True + False + 10 + 5 + 10 + 10 + + + True + False + start + center + Override + + + False + True + 0 + + + + + True + True + end + center + + + False + True + end + 1 + + + + + False + True + 0 + + + + + True + False + 10 + 10 + True + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + 1 + + + + + + + + + True + False + Plugins + + + + + False + False + 2 + + + + + True + False + top + end + + + Remove + True + True + True + + + + True + True + 0 + + + + + False + False + 3 + + + + + + diff --git a/safeeyes/glade/settings_dialog.glade b/safeeyes/glade/settings_dialog.glade index 94d3370..d680249 100644 --- a/safeeyes/glade/settings_dialog.glade +++ b/safeeyes/glade/settings_dialog.glade @@ -21,28 +21,19 @@ --> - - 1 - 60 - 1 - 5 - - - 1 - 60 + + 15 1 5 1 - 120 + 600 5 10 - - 1 - 20 - 1 + + 120 1 5 @@ -52,19 +43,13 @@ 1 5 - - 0 - 15 - 1 - 5 - 1 60 1 5 - + 1 60 1 @@ -76,502 +61,794 @@ 1 5 + + True + False + gtk-add + - 400 False Safe Eyes - False center - 440 - 250 + 450 + 600 safeeyes + center - + True - False - 5 - 5 - 5 - 5 - vertical - 2 + True + False - + True - True False - 5 - 5 - True - True - 20 - 10 + 10 + 10 + 10 + 10 + vertical + 15 - + True False - start - center - Short break duration (in seconds) + 0 + in + + + True + False + 12 + + + True + False + 10 + 10 + 10 + vertical + 15 + True + + + True + False + 5 + + + True + False + Interval between two breaks (in minutes) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_short_break_interval + 1 + True + if-valid + 1 + + + + False + False + end + 1 + + + + + False + True + 0 + + + + + True + False + 5 + + + True + False + Break duration (in seconds) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_short_break_duration + 1 + True + if-valid + 1 + + + False + False + end + 1 + + + + + False + True + 1 + + + + + + + + + True + False + Short Breaks + + - 0 - 0 + False + True + 0 - + True False - start - center - Long break duration (in seconds) + 0 + in + + + True + False + 12 + + + True + False + 10 + 10 + 10 + vertical + 15 + True + + + True + False + True + True + + + + + False + 6 + start + + + + + + False + False + 0 + + + + + False + 16 + + + True + False + gtk-info + + + False + True + 0 + + + + + True + False + Long break interval must be a multiple of short break interval + 0 + + + False + True + 1 + + + + + False + False + 0 + + + + + + True + True + 0 + + + + + True + False + 5 + + + True + False + Interval between two breaks (in minutes) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_long_break_interval + 1 + True + True + True + if-valid + 1 + + + + False + False + end + 1 + + + + + False + True + 1 + + + + + True + False + 5 + + + True + False + Break duration (in seconds) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_long_break_duration + 1 + True + if-valid + 1 + + + False + False + end + 1 + + + + + False + True + 2 + + + + + + + + + True + False + Long Breaks + + - 0 - 1 + False + True + 1 - + True False - start - center - Interval between two breaks (in minutes) + 0 + in + + + True + False + 12 + + + True + False + 10 + 10 + 10 + vertical + 15 + True + + + True + False + 5 + + + True + False + Time to prepare for a break (in seconds) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_time_to_prepare + 1 + True + if-valid + 1 + + + False + False + end + 1 + + + + + False + True + 0 + + + + + True + False + 5 + + + True + False + Strict break (No way to skip breaks) + + + False + True + 0 + + + + + True + True + end + center + + + False + False + end + 1 + + + + + False + True + 1 + + + + + True + False + 5 + + + True + False + Allow postponing breaks + + + False + True + 0 + + + + + True + True + end + center + + + False + False + end + 1 + + + + + False + True + 2 + + + + + True + False + 5 + + + True + False + Postpone duration (in minutes) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_postpone_duration + 1 + True + if-valid + 1 + + + False + False + end + 1 + + + + + False + True + 3 + + + + + True + False + 5 + + + True + False + Keyboard shortcuts disabled period (in seconds) + + + False + True + 0 + + + + + True + True + end + center + 1 + number + adjust_disable_keyboard_shortcut_duration + 1 + True + if-valid + 1 + + + False + False + end + 1 + + + + + False + True + 4 + + + + + True + False + 5 + + + True + False + Persist the internal state + + + False + True + 0 + + + + + True + True + end + center + + + False + False + end + 1 + + + + + False + True + 5 + + + + + + + + + True + False + Options + + - 0 - 2 - - - - - True - False - start - center - Number of short breaks between two long breaks - - - 0 - 3 - - - - - True - False - start - center - Time to prepare for break (in seconds) - - - 0 - 4 - - - - - True - False - start - center - Minimum idle time to pause (in minutes) - - - 0 - 5 - - - - - True - False - start - center - Postpone duration (in minutes) - - - 0 - 10 - - - - - True - False - start - center - Show the next break time in system tray icon - - - 0 - 7 - - - - - True - False - start - center - Strict break (hide skip button) - - - 0 - 8 - - - - - True - True - end - center - Time in minutes - number - adjust_short_break_duration - 1 - True - if-valid - - - 1 - 0 - - - - - True - True - end - center - Time in minutes - number - adjust_long_break_duration - 1 - True - if-valid - - - 1 - 1 - - - - - True - True - end - center - 1 - number - adjust_interval_between_breaks - 1 - True - if-valid - 1 - - - 1 - 2 - - - - - True - True - end - center - 1 - number - adjust_no_short_per_long - 1 - True - if-valid - 1 - - - 1 - 3 - - - - - True - True - end - center - 1 - number - adjust_time_to_prepare - 1 - True - if-valid - 1 - - - 1 - 4 - - - - - True - True - end - center - 1 - number - adjust_idle_time_to_pause - 1 - True - if-valid - 5 - - - 1 - 5 - - - - - True - True - end - center - 1 - number - adjust_postpone_duration - 1 - True - if-valid - 5 - - - 1 - 10 - - - - - True - True - end - center - - - 1 - 7 - - - - - True - True - end - center - - - 1 - 8 - - - - - True - True - end - center - - - 1 - 11 - - - - - True - False - start - center - Audible alert at the end of break - - - 0 - 11 - - - - - True - False - - - 1 - 14 - - - - - True - False - start - center - Language - - - 0 - 14 - - - - - True - False - start - center - Lock the screen after every long break - - - 0 - 12 - - - - - True - True - end - - - 1 - 12 - - - - - True - False - start - center - Maximum time to skip, bypassing the lock screen (in seconds) - - - 0 - 13 - - - - - True - True - end - center - 1 - number - adjust_time_to_lock_screen - 1 - True - if-valid - 1 - - - 1 - 13 - - - - - True - False - start - center - Allow postponing the breaks - - - 0 - 9 - - - - - True - True - end - - - 1 - 9 - - - - - True - False - start - center - Disable keyboard shortcut - - - 0 - 6 - - - - - True - True - end - center - 1 - number - adjust_disable_keyboard_shortcut_duration - 1 - True - if-valid - 1 - - - 1 - 6 + False + True + 2 - True - True - 0 + True - - + + True False - 5 - 5 + Settings - False - True - 1 + lbl_settings + True + False - + True False - 10 - end + vertical - - Cancel + True True - True - + never + + + True + False + none + + + True + False + 10 + 10 + 10 + 10 + vertical + + + True + False + vertical + + + True + True + True + + + True + False + vertical + 5 + + + + + + + + + + + + + + True + False + Short Breaks + + + + + False + True + 0 + + + + + True + False + 10 + + + False + True + 1 + + + + + True + True + + + True + False + vertical + 5 + + + + + + + + + + + + + + True + False + Long Breaks + + + + + False + True + 2 + + + + + True + True + 0 + + + + + + True @@ -580,24 +857,122 @@ - - Save + True - True - True - + False + 5 + 5 + 10 + 10 + end + + + True + True + True + end + img_add + + + + True + True + 1 + + - True + False True + end 1 + + + True + False + 10 + + + False + True + 2 + + + + + 1 + True + + + + + True + False + Breaks + + + 1 + True + False + + + + + True + True + never + in + + + True + False + + + True + False + 10 + 10 + 10 + 10 + vertical + 5 + + + + + + + + + + + + + + + + + + + - False - True 2 + True + + + + + True + False + Plugins + + + 2 + True + False diff --git a/safeeyes/glade/settings_plugin.glade b/safeeyes/glade/settings_plugin.glade new file mode 100644 index 0000000..02b0d33 --- /dev/null +++ b/safeeyes/glade/settings_plugin.glade @@ -0,0 +1,59 @@ + + + + + + + False + Properties + False + True + center-on-parent + 400 + 10 + True + safeeyes + dialog + True + center + + + + True + False + 10 + 10 + vertical + 15 + True + + + + + + + + + + + + + diff --git a/safeeyes/model.py b/safeeyes/model.py new file mode 100644 index 0000000..5f810b4 --- /dev/null +++ b/safeeyes/model.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# 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. +""" + +from distutils.version import LooseVersion +from enum import Enum + +from safeeyes import Utility + + +class Break(object): + """ + An entity class which represents a break. + """ + def __init__(self, break_type, name, time, image, plugins): + self.type = break_type + self.name = name + self.time = time + self.image = image + self.plugins = plugins + + def __str__(self): + return 'Break: {{name: "{}", type: {}, time: {}}}\n'.format(self.name, self.type, self.time) + + def __repr__(self): + return str(self) + + def is_long_break(self): + """ + 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. + """ + return self.type == BreakType.SHORT_BREAK + + def plugin_enabled(self, plugin_id, is_plugin_enabled): + """ + Check whether this break supports the given plugin. + """ + if self.plugins: + return plugin_id in self.plugins + else: + return is_plugin_enabled + + +class BreakType(Enum): + """ + Type of Safe Eyes breaks. + """ + SHORT_BREAK = 1 + LONG_BREAK = 2 + + +class State(Enum): + """ + Possible states of Safe Eyes. + """ + START = 0, + WAITING = 1, + PRE_BREAK = 2, + BREAK = 3, + STOPPED = 4, + QUIT = 5 + + +class EventHook(object): + """ + Hook to attach and detach listeners to system events. + """ + def __init__(self): + self.__handlers = [] + + def __iadd__(self, handler): + self.__handlers.append(handler) + return self + + def __isub__(self, handler): + self.__handlers.remove(handler) + return self + + def fire(self, *args, **keywargs): + """ + Fire all listeners attached with. + """ + for handler in self.__handlers: + if not handler(*args, **keywargs): + return False + return True + + +class Config(object): + """ + The configuration of Safe Eyes. + """ + def __init__(self): + # 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 self.__user_config is None: + Utility.initialize_safeeyes() + self.__user_config = self.__system_config + self.save() + else: + system_config_version = self.__system_config['meta']['config_version'] + meta_obj = self.__user_config.get('meta', None) + if meta_obj is None: + # Corrupted user config + self.__user_config = self.__system_config + else: + user_config_version = str(meta_obj.get('config_version', '0.0.0')) + if LooseVersion(user_config_version) != LooseVersion(system_config_version): + # Update the user config + self.__merge_dictionary(self.__user_config, self.__system_config) + self.__user_config = self.__system_config + + Utility.merge_plugins(self.__user_config) + self.save() + + def __merge_dictionary(self, old_dict, new_dict): + """ + Merge the dictionaries. + """ + for key in new_dict: + if key == "meta": + continue + if key in old_dict: + new_value = new_dict[key] + old_value = old_dict[key] + if type(new_value) is type(old_value): + # Both properties have same type + if isinstance(new_value, dict): + self.__merge_dictionary(old_value, new_value) + else: + new_dict[key] = old_value + + def save(self): + """ + 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. + """ + 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. + """ + self.__user_config[key] = value diff --git a/safeeyes/plugins/audiblealert/config.json b/safeeyes/plugins/audiblealert/config.json new file mode 100644 index 0000000..68c0263 --- /dev/null +++ b/safeeyes/plugins/audiblealert/config.json @@ -0,0 +1,16 @@ +{ + "meta": { + "name": "Audible Alert", + "description": "Play an audible alert at the end of breaks", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": ["aplay"], + "operating_systems": [], + "desktop_environments": [], + "resources": ["alert.wav"] + }, + "settings": [], + "break_override_allowed": true +} \ No newline at end of file diff --git a/safeeyes/plugins/audiblealert/icon.png b/safeeyes/plugins/audiblealert/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..17c1c6db2c698e5a13ca8a533c1301a3adf65161 GIT binary patch literal 444 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA%@6PiaRt(Y8El_y=K^$}NlB1j zFaskK8wW3+ppdAzf})bLrJZkFaz+E?8)^FId_4uhYw51;us3%*la)^@4l%H&@N2uD8TH@BOcj7D;1!UZUS3mbqZf zd)JgFUjsIzWwEv%z9TbFVdQ&MBb@0Gys}XaE2J literal 0 HcmV?d00001 diff --git a/safeeyes/plugins/audiblealert/plugin.py b/safeeyes/plugins/audiblealert/plugin.py new file mode 100644 index 0000000..ad7cd77 --- /dev/null +++ b/safeeyes/plugins/audiblealert/plugin.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# 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. +""" + +import logging +from safeeyes import Utility + +context = None + + +def init(ctx, safeeyes_config, plugin_config): + """ + Initialize the plugin. + """ + global context + logging.debug('Initialize Audible Alert plugin') + context = ctx + + +def on_stop_break(): + """ + After the break, play the alert sound + """ + # Do not play if the break is skipped or postponed + if context['skipped'] or context['postponed']: + return + + logging.info('Playing audible alert') + try: + # Open the sound file + path = Utility.get_resource_path('alert.wav') + if path is None: + return + Utility.execute_command('aplay', ['-q', path]) + + except BaseException: + logging.error('Failed to play audible alert') diff --git a/safeeyes/plugins/donotdisturb/config.json b/safeeyes/plugins/donotdisturb/config.json new file mode 100644 index 0000000..0074e46 --- /dev/null +++ b/safeeyes/plugins/donotdisturb/config.json @@ -0,0 +1,35 @@ +{ + "meta": { + "name": "Do Not Disturb", + "description": "Skip the break if the active window is in fullscreen mode", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": ["xprop"], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [ + { + "id": "skip_break_windows", + "label": "Do not interrupt these windows anytime", + "type": "TEXT", + "default": "" + }, + { + "id": "take_break_windows", + "label": "Interrupt these windows regardless of their state", + "type": "TEXT", + "default": "" + }, + { + "id": "unfullscreen", + "label": "Switch the interruptible windows to normal mode", + "type": "BOOL", + "default": true + } + ], + "break_override_allowed": true +} \ No newline at end of file diff --git a/safeeyes/plugins/donotdisturb/icon.png b/safeeyes/plugins/donotdisturb/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d19bc115663131a3b0a44ee792faa75143792e8 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA83*`;xB_WX!D3dolR!NJB|(0{ z46Ho7dWI$Sa~EvgzH`sM-}#^Xrvl|1JzX3_G|nd{9AK5WS^3j|LEl=Lc{xMafrAJ7 zW1HqXGO#b$#KOHxJi@JuMOs@b;jnbrof}7YO3bL-&{=G3IDv;D;-;{1my_dRkmEdE L{an^LB{Ts59auAb literal 0 HcmV?d00001 diff --git a/safeeyes/plugins/donotdisturb/plugin.py b/safeeyes/plugins/donotdisturb/plugin.py new file mode 100644 index 0000000..94d78d5 --- /dev/null +++ b/safeeyes/plugins/donotdisturb/plugin.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# 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. +NOTE: Do not remove the unused import 'GdkX11' becuase it is required in Ubuntu 14.04 +""" + +import logging +import re +import subprocess + +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk +from gi.repository import GdkX11 + +context = None +skip_break_window_classes = [] +take_break_window_classes = [] +unfullscreen_allowed = True + + +def is_active_window_skipped(): + """ + Check for full-screen applications. + This method must be executed by the main thread. If not, it will cause to random failure. + """ + 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'] + + try: + stdout = subprocess.check_output(cmdlist).decode('utf-8') + except subprocess.CalledProcessError: + logging.warning('Error in finding full-screen application') + else: + if stdout: + is_fullscreen = 'FULLSCREEN' in stdout + # Extract the process name + process_names = re.findall('"(.+?)"', stdout) + if process_names: + process = process_names[1].lower() + if process in skip_break_window_classes: + return True + elif process in take_break_window_classes: + if is_fullscreen and unfullscreen_allowed: + try: + active_window.unfullscreen() + except BaseException: + logging.error('Error in unfullscreen the window ' + process) + return False + + return is_fullscreen + + return False + + +def init(ctx, safeeyes_config, plugin_config): + global context + global skip_break_window_classes + global take_break_window_classes + global unfullscreen_allowed + logging.debug('Initialize Skip Fullscreen plugin') + context = ctx + skip_break_window_classes = plugin_config['skip_break_windows'].split() + take_break_window_classes = plugin_config['take_break_windows'].split() + unfullscreen_allowed = plugin_config['unfullscreen'] + + +def on_start_break(break_obj): + """ + """ + return is_active_window_skipped() diff --git a/safeeyes/plugins/healthstats/config.json b/safeeyes/plugins/healthstats/config.json new file mode 100644 index 0000000..3aca74b --- /dev/null +++ b/safeeyes/plugins/healthstats/config.json @@ -0,0 +1,16 @@ +{ + "meta": { + "name": "Health Statistics", + "description": "Show statistics based on how you use Safe Eyes", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": [], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [], + "break_override_allowed": true +} \ No newline at end of file diff --git a/safeeyes/plugins/healthstats/icon.png b/safeeyes/plugins/healthstats/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f2c1bed99a2172bdc240f2ac64d8026cbcff299e GIT binary patch literal 607 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAJqhp$aRt)D9*7bh`5B5oPv^;u8FCcxwVb0ouiYRyN`chP-Jv$TuORIeqlv*eQR5H zU;m5+i&k&lzU%1m6K5{od-UYR>$e|2fBosczV0~C*!!L?jv*3~OAo%bVhUtvyI8L6 zdeL!(2-nk^t~p+36149Bz29$JJ;kx#{@Nq{Wp}(a(bs!7UQgo#8lH#fQ6h01t7fxurb+mevY(0GmtODQ_H(J)>SH{w gFG(3c{ru-R`{9M2TZ}~7DnZG_)78&qol`;+0J|8UeE. +""" +Show health statistics on the break screen. +""" + +import logging + +context = None +no_of_skipped_breaks = 0 +no_of_breaks = 0 +no_of_cycles = -1 +session = None + + +def init(ctx, safeeyes_config, plugin_config): + """ + Initialize the plugin. + """ + global context + global session + global no_of_skipped_breaks + global no_of_breaks + global no_of_cycles + logging.debug('Initialize Health Stats plugin') + context = ctx + if session is None: + session = context['session']['plugin'].get('healthstats', None) + if session is None: + session = {'no_of_skipped_breaks': 0, 'no_of_breaks': 0, 'no_of_cycles': -1} + context['session']['plugin']['healthstats'] = session + no_of_skipped_breaks = session.get('no_of_skipped_breaks', 0) + no_of_breaks = session.get('no_of_breaks', 0) + no_of_cycles = session.get('no_of_cycles', -1) + + +def on_stop_break(): + """ + After the break, play the alert sound + """ + global no_of_skipped_breaks + if context['skipped']: + no_of_skipped_breaks += 1 + session['no_of_skipped_breaks'] = no_of_skipped_breaks + + +def get_widget_title(break_obj): + """ + Return the widget title. + """ + global no_of_breaks + global no_of_cycles + no_of_breaks += 1 + if context['new_cycle']: + no_of_cycles += 1 + session['no_of_breaks'] = no_of_breaks + session['no_of_cycles'] = no_of_cycles + return _('Health Statistics') + + +def get_widget_content(break_obj): + """ + Return the statistics. + """ + return 'BREAKS: {}\tSKIPPED: {}\tCYCLES: {}'.format(no_of_breaks, no_of_skipped_breaks, no_of_cycles) diff --git a/safeeyes/plugins/notification/config.json b/safeeyes/plugins/notification/config.json new file mode 100644 index 0000000..94f6839 --- /dev/null +++ b/safeeyes/plugins/notification/config.json @@ -0,0 +1,15 @@ +{ + "meta": { + "name": "Notification", + "description": "Show a system notification before breaks", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": [], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [] +} \ No newline at end of file diff --git a/safeeyes/plugins/notification/icon.png b/safeeyes/plugins/notification/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..df0a1835c0b0af08b684ce440e9956b9d850c1a5 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaN3?zjj6;1;w^#Gp`S0GIcxGVH?D^MLzNswPK z0~@!nh=D_FVrKs9FApCi0mY*{T^vIsrY0x6jACWgmXXLgci^C4n-t4)mcueE&qb`B zGcZehNGS4i^oe7BCgaWAEaQ{P&U(. + +import logging + +import gi +from safeeyes.model import BreakType + +gi.require_version('Notify', '0.7') +from gi.repository import Notify + +""" +Safe Eyes Notification plugin +""" + +APPINDICATOR_ID = 'safeeyes' +notification = None +context = None + +Notify.init(APPINDICATOR_ID) + + +def init(ctx, safeeyes_config, plugin_config): + """ + Initialize the plugin. + """ + global context + logging.debug('Initialize Notification plugin') + context = ctx + + +def on_pre_break(break_obj): + """ + Show the notification + """ + # Construct the message based on the type of the next break + global notification + logging.info('Show the notification') + message = '\n' + warning_time = 10 + if break_obj.type == BreakType.SHORT_BREAK: + message += (_('Ready for a short break in %s seconds') % warning_time) + else: + message += (_('Ready for a long break in %s seconds') % warning_time) + + notification = Notify.Notification.new('Safe Eyes', message, icon='safeeyes_enabled') + try: + notification.show() + except BaseException: + logging.error('Failed to show the notification') + + +def on_start_break(break_obj): + """ + Close the notification. + """ + global notification + logging.info('Close pre-break notification') + if notification: + try: + notification.close() + notification = None + except BaseException: + # Some operating systems automatically close the notification. + pass + + +def on_exit(): + """ + Uninitialize the registered notificaion. + """ + logging.debug('Stop Notification plugin') + Notify.uninit() diff --git a/safeeyes/plugins/screensaver/config.json b/safeeyes/plugins/screensaver/config.json new file mode 100644 index 0000000..13185b4 --- /dev/null +++ b/safeeyes/plugins/screensaver/config.json @@ -0,0 +1,29 @@ +{ + "meta": { + "name": "Screensaver", + "description": "Lock the screen after long breaks by starting screensaver", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": [], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [ + { + "id": "command", + "label": "Custom screensaver command", + "type": "TEXT", + "default": "" + }, + { + "id": "min_seconds", + "label": "Minimum seconds to skip without screensaver", + "type": "INT", + "default": 3 + } + ], + "break_override_allowed": true +} \ No newline at end of file diff --git a/safeeyes/plugins/screensaver/icon.png b/safeeyes/plugins/screensaver/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5fcc5781d153d4dec11db3b4b4a7497f1175ec1a GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAbp`l@xB}@x0)&}3^np&%EeY}q zW?*DuW@TgN. +""" +Screensaver plugin locks the desktop using native screensaver application, after long breaks. +""" + +import logging +import os + +from safeeyes import Utility + +context = None +lock_screen = False +lock_screen_command = None +min_seconds = 0 +seconds_passed = 0 + + +def __lock_screen_command(): + """ + 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'] + XFCE: ['xflock4'] + Otherwise: None + """ + 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'): + 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'): + # Gnome 2 + return ['gnome-screensaver-command', '--lock'] + return None + + +def init(ctx, safeeyes_config, plugin_config): + """ + Initialize the screensaver plugin. + """ + global context + global lock_screen_command + global min_seconds + logging.debug('Initialize Screensaver plugin') + context = ctx + min_seconds = plugin_config['min_seconds'] + 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. + """ + global lock_screen + global seconds_passed + seconds_passed = 0 + if lock_screen_command: + lock_screen = break_obj.is_long_break() + + +def on_countdown(countdown, seconds): + """ + 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. + """ + if lock_screen and seconds_passed >= min_seconds: + Utility.execute_command(lock_screen_command) diff --git a/safeeyes/plugins/smartpause/config.json b/safeeyes/plugins/smartpause/config.json new file mode 100644 index 0000000..42fc5a7 --- /dev/null +++ b/safeeyes/plugins/smartpause/config.json @@ -0,0 +1,22 @@ +{ + "meta": { + "name": "Smart Pause", + "description": "Pause Safe Eyes if the system is idle", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": ["xprintidle"], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [ + { + "id": "idle_time", + "label": "Minimum idle time to pause Safe Eyes", + "type": "INT", + "default": 3 + } + ] +} \ No newline at end of file diff --git a/safeeyes/plugins/smartpause/icon.png b/safeeyes/plugins/smartpause/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6896fb86d94d4df400d87d6cf3fb10b54b369b GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA-3agraRt)D5`=%)6$SK|dr6RA zFaskKGbPRMV7RO+GPsMX*x(BEF6_Z&qkcT+BaJ q(6T1fSLv&7YG;XSuApJ)(JS_q=QY;p8SF>`MY*S|pUXO@geCy41BjCV literal 0 HcmV?d00001 diff --git a/safeeyes/plugins/smartpause/plugin.py b/safeeyes/plugins/smartpause/plugin.py new file mode 100644 index 0000000..43191d4 --- /dev/null +++ b/safeeyes/plugins/smartpause/plugin.py @@ -0,0 +1,154 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import datetime +import logging +import subprocess +import threading + +from safeeyes import Utility +from safeeyes.model import State + +""" +Safe Eyes smart pause plugin +""" + +context = None +idle_condition = threading.Condition() +lock = threading.Lock() +active = False +idle_time = 0 +enable_safe_eyes = None +disable_safe_eyes = None +smart_pause_activated = False +idle_start_time = None +next_break_time = None +break_interval = 0 + +def __system_idle_time(): + """ + Get system idle time in minutes. + Return the idle time if xprintidle is available, otherwise return 0. + """ + try: + return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 60000 # Convert to minutes + except BaseException: + return 0 + + +def __is_active(): + """ + Thread safe function to see if this plugin is active or not. + """ + is_active = False + with lock: + is_active = active + return is_active + + +def __set_active(is_active): + """ + 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. + """ + global context + global enable_safe_eyes + global disable_safe_eyes + global idle_time + global break_interval + logging.debug('Initialize Smart Pause plugin') + context = ctx + enable_safe_eyes = context['api']['enable_safeeyes'] + disable_safe_eyes = context['api']['disable_safeeyes'] + idle_time = plugin_config['idle_time'] + break_interval = safeeyes_config.get('break_interval') * 60 + + +def __start_idle_monitor(): + """ + Continuously check the system idle time and pause/resume Safe Eyes based on it. + """ + global smart_pause_activated + global idle_start_time + while __is_active(): + # Wait for 2 seconds + idle_condition.acquire() + idle_condition.wait(2) + idle_condition.release() + + 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: + smart_pause_activated = True + idle_start_time = datetime.datetime.now() + logging.info('Pause Safe Eyes due to system idle') + disable_safe_eyes() + elif system_idle_time < idle_time and context['state'] == State.STOPPED: + logging.info('Resume Safe Eyes due to user activity') + smart_pause_activated = False + idle_period = (datetime.datetime.now() - idle_start_time) + if idle_period.total_seconds() < break_interval: + next_break = next_break_time + idle_period + enable_safe_eyes(next_break.timestamp()) + else: + enable_safe_eyes() + + +def on_start(): + """ + 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') + __set_active(True) + Utility.start_thread(__start_idle_monitor) + + +def on_stop(): + """ + 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') + __set_active(False) + idle_condition.acquire() + idle_condition.notify_all() + idle_condition.release() + +def update_next_break(dateTime): + """ + Update the next break time. + """ + global next_break_time + next_break_time = dateTime diff --git a/safeeyes/plugins/trayicon/config.json b/safeeyes/plugins/trayicon/config.json new file mode 100644 index 0000000..5be28e1 --- /dev/null +++ b/safeeyes/plugins/trayicon/config.json @@ -0,0 +1,45 @@ +{ + "meta": { + "name": "Tray Icon", + "description": "Show a tray icon in the notification area", + "version": "0.0.1" + }, + "dependencies": { + "python_modules": [], + "shell_commands": [], + "operating_systems": [], + "desktop_environments": [], + "resources": [] + }, + "settings": [ + { + "id": "show_time_in_tray", + "label": "Show next break time in tray icon", + "type": "BOOL", + "default": false + }, + { + "id": "disable_options", + "label": "Disable options", + "type": "HIDDEN", + "default": [ + { + "time": 30, + "unit": "minute" + }, + { + "time": 1, + "unit": "hour" + }, + { + "time": 2, + "unit": "hour" + }, + { + "time": 3, + "unit": "hour" + } + ] + } + ] +} \ No newline at end of file diff --git a/safeeyes/plugins/trayicon/icon.png b/safeeyes/plugins/trayicon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..87739da355320a196d0898767292b888019de8ea GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA%?$7faRt(Y6DZ4OO$EBls3gcQ zn1PXllZ%^2SVUS@K~YIr&BWZxCm<*#r>3sHZ~BZ`%T{mLvUS(t<0np?yLkEK>$mSe ze$I|ziUaDN=;`7ZA|bi<;BBGC1PRs)z8Xa*6rK2{2E5e&{a4;kO6uwC!ere)NjLK5 zq;AZ9{Ub$njf&VkE5_^^URCAP!@lm0!G9)gWBegf(cn3evt5bZ&{goA$kPrDu8j*b zdKF!34=jGbv0(Nm?{y0e(k2^yFiCR0;GB7I)@Kp@=t&P#_U+dyy?K@=#P7)})`Oo6 c4%pi;e3|05HYEL^1}M-yUHx3vIVCg!03Q!(l>h($ literal 0 HcmV?d00001 diff --git a/safeeyes/plugins/trayicon/plugin.py b/safeeyes/plugins/trayicon/plugin.py new file mode 100644 index 0000000..9708f1b --- /dev/null +++ b/safeeyes/plugins/trayicon/plugin.py @@ -0,0 +1,406 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import datetime +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('AppIndicator3', '0.1') +from gi.repository import AppIndicator3 as appindicator +from gi.repository import Gtk +import logging +from safeeyes import Utility +import threading + +""" +Safe Eyes tray icon plugin +""" + +APPINDICATOR_ID = 'safeeyes_2' +context = None +tray_icon = None +safeeyes_config = None + + +class TrayIcon(object): + """ + 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.on_enable = context['api']['enable_safeeyes'] + self.on_disable = context['api']['disable_safeeyes'] + self.take_break = context['api']['take_break'] + self.has_breaks = context['api']['has_breaks'] + 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() + + # Construct the tray icon + self.indicator = appindicator.Indicator.new( + APPINDICATOR_ID, "safeeyes_enabled", appindicator.IndicatorCategory.APPLICATION_STATUS) + self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + + # Construct the context menu + self.menu = Gtk.Menu() + + # Next break info menu item + self.item_info = Gtk.ImageMenuItem() + img_timer = Gtk.Image() + img_timer.set_from_icon_name("safeeyes_timer", 16) + self.item_info.set_image(img_timer) + + self.item_separator = Gtk.SeparatorMenuItem() + + self.item_enable = Gtk.MenuItem() + self.item_enable.connect('activate', self.on_enable_clicked) + + self.item_disable = Gtk.MenuItem() + self.item_disable.connect('activate', self.on_disable_clicked) + + self.sub_menu_disable = Gtk.Menu() + self.sub_menu_items = [] + + # Read disable options and build the sub menu + for disable_option in plugin_config['disable_options']: + time_in_minutes = disable_option['time'] + label = [] + # 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)) + continue + time_unit = disable_option['unit'].lower() + if time_unit == 'seconds' or time_unit == 'second': + time_in_minutes = int(time_in_minutes / 60) + label = ['For %d Second', 'For %d Seconds'] + elif time_unit == 'minutes' or time_unit == 'minute': + time_in_minutes = int(time_in_minutes * 1) + label = ['For %d Minute', 'For %d Minutes'] + elif time_unit == 'hours' or time_unit == 'hour': + time_in_minutes = int(time_in_minutes * 60) + label = ['For %d Hour', 'For %d Hours'] + else: + # Invalid unit + logging.error('Invalid unit in disable option: ' + str(disable_option)) + continue + + # Create submenu + sub_menu_item = Gtk.MenuItem() + sub_menu_item.connect('activate', self.on_disable_clicked, time_in_minutes) + self.sub_menu_items.append([sub_menu_item, label, disable_option['time']]) + self.sub_menu_disable.append(sub_menu_item) + + # Disable until restart submenu + self.sub_menu_item_until_restart = Gtk.MenuItem() + self.sub_menu_item_until_restart.connect('activate', self.on_disable_clicked, -1) + self.sub_menu_disable.append(self.sub_menu_item_until_restart) + + # Add the sub menu to the enable/disable menu + self.item_disable.set_submenu(self.sub_menu_disable) + + # Settings menu item + self.item_manual_break = Gtk.MenuItem() + self.item_manual_break.connect('activate', self.on_manual_break_clicked) + + # Settings menu item + self.item_settings = Gtk.MenuItem() + self.item_settings.connect('activate', self.show_settings) + + # About menu item + self.item_about = Gtk.MenuItem() + self.item_about.connect('activate', self.show_about) + + # Quit menu item + self.item_quit = Gtk.MenuItem() + self.item_quit.connect('activate', self.quit_safe_eyes) + + self.set_labels() + + # At startup, no need for activate menu + self.item_enable.set_sensitive(False) + + # Append all menu items and show the menu + self.menu.append(self.item_info) + self.menu.append(self.item_separator) + self.menu.append(self.item_enable) + self.menu.append(self.item_disable) + self.menu.append(self.item_manual_break) + self.menu.append(self.item_settings) + self.menu.append(self.item_about) + self.menu.append(self.item_quit) + self.menu.show_all() + + self.indicator.set_menu(self.menu) + + def initialize(self, plugin_config): + """ + Initialize the tray icon by setting the config. + """ + self.plugin_config = plugin_config + self.set_labels() + + def set_labels(self): + """ + Update the text of menu items based on the selected language. + """ + for entry in self.sub_menu_items: + # print(self.context['locale'].ngettext('For %d Hour', 'For %d Hours', 1) % 1) + entry[0].set_label(self.context['locale'].ngettext(entry[1][0], entry[1][1], entry[2]) % entry[2]) + + self.sub_menu_item_until_restart.set_label(_('Until restart')) + self.item_enable.set_label(_('Enable Safe Eyes')) + self.item_disable.set_label(_('Disable Safe Eyes')) + + breaks_found = self.has_breaks() + if breaks_found: + if self.active: + if self.date_time: + self.__set_next_break_info() + self.indicator.set_icon("safeeyes_enabled") + else: + if self.wakeup_time: + self.item_info.set_label(_('Disabled until %s') % Utility.format_time(self.wakeup_time)) + else: + self.item_info.set_label(_('Disabled until restart')) + self.indicator.set_label('', '') + self.indicator.set_icon("safeeyes_disabled") + else: + self.item_info.set_label(_('No Breaks Available')) + self.indicator.set_label('', '') + self.indicator.set_icon("safeeyes_disabled") + self.item_info.set_sensitive(breaks_found and self.active) + self.item_enable.set_sensitive(breaks_found and not self.active) + self.item_disable.set_sensitive(breaks_found and self.active) + self.item_manual_break.set_sensitive(breaks_found and self.active) + + self.item_manual_break.set_label(_('Take a break now')) + self.item_settings.set_label(_('Settings')) + self.item_about.set_label(_('About')) + self.item_quit.set_label(_('Quit')) + + def show_icon(self): + """ + Show the tray icon. + """ + self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + + def hide_icon(self): + """ + Hide the tray icon. + """ + self.indicator.set_status(appindicator.IndicatorStatus.PASSIVE) + + def quit_safe_eyes(self, *args): + """ + Handle Quit menu action. + This action terminates the application. + """ + self.quit() + with self.lock: + self.active = True + # Notify all schedulers + self.idle_condition.acquire() + self.idle_condition.notify_all() + self.idle_condition.release() + + def show_settings(self, *args): + """ + Handle Settings menu action. + This action shows the Settings dialog. + """ + self.on_show_settings() + + def show_about(self, *args): + """ + 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. + """ + logging.info("Update next break information") + self.date_time = dateTime + self.__set_next_break_info() + + def __set_next_break_info(self): + """ + A private method to be called within this class to update the next break information using self.dateTime. + """ + formatted_time = Utility.format_time(self.date_time) + message = _('Next break at %s') % (formatted_time) + # Update the menu item label + Utility.execute_main_thread(self.item_info.set_label, message) + # Update the tray icon label + if self.plugin_config.get('show_time_in_tray', False): + self.indicator.set_label(formatted_time, '') + else: + self.indicator.set_label('', '') + + def on_manual_break_clicked(self, *args): + """ + Trigger a break manually. + """ + self.take_break() + + def on_enable_clicked(self, *args): + """ + Handle 'Enable Safe Eyes' menu action. + This action enables the application if it is currently disabled. + """ + # active = self.item_enable.get_active() + if not self.active: + with self.lock: + self.enable_ui() + self.on_enable() + # Notify all schedulers + self.idle_condition.acquire() + self.idle_condition.notify_all() + self.idle_condition.release() + + def on_disable_clicked(self, *args): + """ + Handle the menu actions of all the sub menus of 'Disable Safe Eyes'. + This action disables the application if it is currently active. + """ + # active = self.item_enable.get_active() + if self.active and len(args) > 1: + self.disable_ui() + self.on_disable() + + time_to_wait = args[1] + if time_to_wait <= 0: + self.wakeup_time = None + self.item_info.set_label(_('Disabled until restart')) + else: + self.wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait) + Utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait) + self.item_info.set_label(_('Disabled until %s') % Utility.format_time(self.wakeup_time)) + + def lock_menu(self): + """ + This method is called by the core to prevent user from disabling Safe Eyes after the notification. + """ + if self.active: + self.menu.set_sensitive(False) + + def unlock_menu(self): + """ + This method is called by the core to activate the menu after the the break. + """ + if self.active: + self.menu.set_sensitive(True) + + def disable_ui(self): + """ + Change the UI to disabled state. + """ + if self.active: + logging.info('Disable Safe Eyes') + self.active = False + self.indicator.set_icon("safeeyes_disabled") + self.item_info.set_label(_('Disabled until restart')) + self.indicator.set_label('', '') + self.item_info.set_sensitive(False) + self.item_enable.set_sensitive(True) + self.item_disable.set_sensitive(False) + self.item_manual_break.set_sensitive(False) + + def enable_ui(self): + """ + Change the UI to enabled state. + """ + if not self.active: + logging.info('Enable Safe Eyes') + self.active = True + self.indicator.set_icon("safeeyes_enabled") + self.item_info.set_sensitive(True) + self.item_enable.set_sensitive(False) + self.item_disable.set_sensitive(True) + self.item_manual_break.set_sensitive(True) + + def __schedule_resume(self, time_minutes): + """ + Schedule a local timer to enable Safe Eyes after the given timeout. + """ + self.idle_condition.acquire() + self.idle_condition.wait(time_minutes * 60) # Convert to seconds + self.idle_condition.release() + + with self.lock: + if not self.active: + Utility.execute_main_thread(self.item_enable.activate) + + +def init(ctx, safeeyes_cfg, plugin_config): + """ + Initialize the tray icon. + """ + global context + global tray_icon + global safeeyes_config + logging.debug('Initialize Tray Icon plugin') + context = ctx + safeeyes_config = safeeyes_cfg + if not tray_icon: + tray_icon = TrayIcon(context, plugin_config) + else: + tray_icon.initialize(plugin_config) + + +def update_next_break(dateTime): + """ + Update the next break time. + """ + tray_icon.next_break_time(dateTime) + + +def on_pre_break(break_obj): + """ + Disable the menu if strict_break is enabled + """ + if safeeyes_config.get('strict_break'): + tray_icon.lock_menu() + threading.Timer(safeeyes_config.get('pre_break_warning_time'), __unlock_menu).start() + + +def __unlock_menu(): + """ + Unlock the menu + """ + Utility.execute_main_thread(tray_icon.unlock_menu) + +def on_start(): + """ + Enable the tray icon. + """ + tray_icon.enable_ui() + +def on_stop(): + """ + Disable the tray icon. + """ + tray_icon.disable_ui() \ No newline at end of file diff --git a/safeeyes/resource/ic_plugin.png b/safeeyes/resource/ic_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..34632241c4040e560fb2078a5c0d6e8e58dcb406 GIT binary patch literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA%?a=caRt(Y7X0Y-xd(KdaY>M0 zFaskK3o9EZ7dIcjfUtX7q^go)O zmzg+n`F*TktXeVSR9;=kRjKX8LVCt|XEYfX*sS-yd3MLdwJFwGQ*K#o`Vl)(z<_cjwtTM&VH%8^6be k{J+X)&ld51rkO|`L&jID(ECl-?|d!|mGJ8!|F zt=mtWx_Rr)y_a8q|23_fvKMH~c25__5DCfBgWXDp5*QpFUe0REaGDgoZm009M|=KP z|J*(;s+GwV z<$o(uvv{&!tvGgPr6bd8HuXPU$JnGdWE%9%cscua)3lw&ULTL#;d?V{w#kfL8$KAy zq=}u=RJpJB)9b`ti>r-&!Pi-Qy)~Cin{}XhsYsCb_X}muHeKIcwCwEUsd6XRm^}J$ fnJaSo|33_W*Exv4i>$8!dV#^y)z4*}Q$iB}o^R^9 literal 0 HcmV?d00001 diff --git a/safeeyes/rpc.py b/safeeyes/rpc.py new file mode 100644 index 0000000..0d9331c --- /dev/null +++ b/safeeyes/rpc.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2017 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +RPC server and client implementation. +""" + +import logging +from threading import Thread +from safeeyes import Utility +from xmlrpc.server import SimpleXMLRPCServer +from xmlrpc.client import ServerProxy + +class RPCServer(object): + """ + An aynchronous 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(lambda: Utility.execute_main_thread(context['api']['show_settings']), 'show_settings') + self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['show_about']), 'show_about') + self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['enable_safeeyes']), 'enable_safeeyes') + self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['disable_safeeyes']), 'disable_safeeyes') + self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['take_break']), 'take_break') + self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['quit']), 'quit') + + def start(self): + """ + Start the RPC server. + """ + if not self.__running: + self.__running = True + logging.info('Start the RPC server') + server_thread = Thread(target=self.__server.serve_forever) + server_thread.start() + + def stop(self): + """ + Stop the server. + """ + if self.__running: + logging.info('Stop the RPC server') + self.__running = False + self.__server.shutdown() + +class RPCClient(object): + """ + An RPC client to communicate with the RPC server. + """ + def __init__(self, port): + self.port = port + + def show_settings(self): + """ + Show the settings dialog. + """ + with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy: + proxy.show_settings() + + def show_about(self): + """ + Show the about dialog. + """ + with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy: + return proxy.show_about() + + + def enable_safeeyes(self): + """ + Enable Safe Eyes. + """ + with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy: + return proxy.enable_safeeyes() + + def disable_safeeyes(self): + """ + Disable Safe Eyes. + """ + with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy: + return proxy.disable_safeeyes() + + def take_break(self): + """ + Take a break now. + """ + with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy: + return proxy.take_break() + + def quit(self): + """ + Quit Safe Eyes. + """ + with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy: + return proxy.quit() \ No newline at end of file diff --git a/safeeyes/settings.py b/safeeyes/settings.py new file mode 100644 index 0000000..3aaf94f --- /dev/null +++ b/safeeyes/settings.py @@ -0,0 +1,542 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import math +import os + +import gi +from safeeyes import Utility + +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_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(object): + """ + 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('break_interval') + self.initializing = True + self.infobar_long_break_shown = False + + 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') + box_plugins = builder.get_object('box_plugins') + for short_break in config.get('short_breaks'): + self.__create_break_item(short_break, True) + for long_break in config.get('long_breaks'): + self.__create_break_item(long_break, False) + + for plugin_config in Utility.load_plugins_config(config): + box_plugins.pack_start(self.__create_plugin_item(plugin_config), False, False, 0) + + 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_postpone = builder.get_object('switch_postpone') + self.switch_persist = builder.get_object('switch_persist') + self.info_bar_long_break = builder.get_object("info_bar_long_break") + self.info_bar_long_break.hide() + + # Set the current values of input fields + 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('break_interval')) + self.spin_long_break_interval.set_value(config.get('no_of_short_breaks_per_long_break') * config.get('break_interval')) + self.spin_time_to_prepare.set_value(config.get('pre_break_warning_time')) + self.spin_postpone_duration.set_value(config.get('postpone_duration')) + self.spin_disable_keyboard_shortcut.set_value(config.get('shortcut_disable_time')) + self.switch_strict_break.set_active(config.get('strict_break')) + self.switch_postpone.set_active(config.get('allow_postpone') and not config.get('strict_break')) + self.switch_persist.set_active(config.get('persist_state')) + + # Update relative states + # GtkSwitch state-set signal is available only from 3.14 + if Gtk.get_minor_version() >= 14: + self.switch_strict_break.connect('state-set', self.on_switch_strict_break_activate) + self.switch_postpone.connect('state-set', self.on_switch_postpone_activate) + self.on_switch_strict_break_activate(self.switch_strict_break, self.switch_strict_break.get_active()) + self.on_switch_postpone_activate(self.switch_postpone, self.switch_postpone.get_active()) + self.initializing = False + + def __create_break_item(self, break_config, is_short): + """ + Create an entry for break to be listed in the break tab. + """ + parent_box = self.box_long_breaks + if is_short: + parent_box = self.box_short_breaks + builder = Utility.create_gtk_builder(SETTINGS_BREAK_ITEM_GLADE) + box = builder.get_object('box') + lbl_name = builder.get_object('lbl_name') + lbl_name.set_label(_(break_config['name'])) + btn_properties = builder.get_object('btn_properties') + btn_properties.connect( + 'clicked', + lambda button: self.__show_break_properties_dialog( + break_config, + is_short, + self.config, + lambda: 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) + ) + ) + box.set_visible(True) + parent_box.pack_start(box, False, False, 0) + return box + + def __create_plugin_item(self, plugin_config): + """ + Create an entry for plugin to be listed in the plugin tab. + """ + builder = Utility.create_gtk_builder(SETTINGS_PLUGIN_ITEM_GLADE) + lbl_plugin_name = builder.get_object('lbl_plugin_name') + lbl_plugin_description = builder.get_object('lbl_plugin_description') + switch_enable = builder.get_object('switch_enable') + btn_properties = builder.get_object('btn_properties') + lbl_plugin_name.set_label(_(plugin_config['meta']['name'])) + switch_enable.set_active(plugin_config['enabled']) + if plugin_config['error']: + lbl_plugin_description.set_label(_(plugin_config['meta']['description'])) + lbl_plugin_name.set_sensitive(False) + lbl_plugin_description.set_sensitive(False) + switch_enable.set_sensitive(False) + else: + lbl_plugin_description.set_label(_(plugin_config['meta']['description'])) + self.plugin_switches[plugin_config['id']] = switch_enable + if plugin_config.get('break_override_allowed', False): + self.plugin_map[plugin_config['id']] = plugin_config['meta']['name'] + if plugin_config['icon']: + builder.get_object('img_plugin_icon').set_from_file(plugin_config['icon']) + if plugin_config['settings']: + btn_properties.set_sensitive(True) + btn_properties.connect('clicked', lambda button: self.__show_plugins_properties_dialog(plugin_config)) + else: + btn_properties.set_sensitive(False) + box = builder.get_object('box') + box.set_visible(True) + return box + + def __show_plugins_properties_dialog(self, plugin_config): + """ + Show the PluginProperties dialog + """ + dialog = PluginSettingsDialog(plugin_config) + dialog.show() + + def __show_break_properties_dialog(self, break_config, is_short, parent, on_remove, on_close, on_add): + """ + Show the BreakProperties dialog + """ + dialog = BreakSettingsDialog(break_config, is_short, parent, self.plugin_map, on_remove, on_close, on_add) + dialog.show() + + def show(self): + """ + Show the SettingsDialog. + """ + self.window.show_all() + + def on_switch_strict_break_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. + """ + strict_break_enable = state + self.switch_postpone.set_sensitive(not strict_break_enable) + self.spin_disable_keyboard_shortcut.set_sensitive(not strict_break_enable) + + 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. + """ + 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. + """ + 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, 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.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. + """ + 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. + """ + self.info_bar_long_break.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)) + 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('break_interval', self.spin_short_break_interval.get_value_as_int()) + self.config.set('no_of_short_breaks_per_long_break', math.floor(self.spin_long_break_interval.get_value_as_int() / self.spin_short_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('allow_postpone', self.switch_postpone.get_active()) + self.config.set('persist_state', self.switch_persist.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(object): + """ + Builds a settings dialog based on the configuration of a plugin. + """ + def __init__(self, config): + self.config = config + self.property_controls = [] + + builder = Utility.create_gtk_builder(SETTINGS_DIALOG_PLUGIN_GLADE) + builder.connect_signals(self) + self.window = builder.get_object('dialog_settings_plugin') + box_settings = builder.get_object('box_settings') + self.window.set_title(_('Plugin Settings')) + for setting in config.get('settings'): + if setting['type'].upper() == 'INT': + box_settings.pack_start(self.__load_int_item(setting['label'], setting['id'], setting['safeeyes_config']), 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): + """ + 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') + spin_value.set_value(settings[key]) + box = builder.get_object('box') + box.set_visible(True) + 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. + """ + builder = Utility.create_gtk_builder(SETTINGS_ITEM_TEXT_GLADE) + 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.set_visible(True) + 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. + """ + builder = Utility.create_gtk_builder(SETTINGS_ITEM_BOOL_GLADE) + 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.set_visible(True) + 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. + """ + for property_control in self.property_controls: + property_control['settings'][property_control['key']] = property_control['value']() + self.window.destroy() + + def show(self): + """ + Show the Properties dialog. + """ + self.window.show_all() + + +class BreakSettingsDialog(object): + """ + Builds a settings dialog based on the configuration of a plugin. + """ + def __init__(self, break_config, is_short, parent_config, plugin_map, on_remove, on_close, on_add): + self.break_config = break_config + self.parent_config = parent_config + self.plugin_check_buttons = {} + self.on_remove = on_remove + self.on_close = on_close + self.is_short = is_short + self.on_add = on_add + + 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_duration = builder.get_object('switch_override_duration') + self.switch_override_plugins = builder.get_object('switch_override_plugins') + self.spin_duration = builder.get_object('spin_duration') + self.img_break = builder.get_object('img_break') + self.cmb_type = builder.get_object('cmb_type') + # User cannot remove the last break + not_last_break = (len(parent_config.get('short_breaks')) + len(parent_config.get('long_breaks'))) > 1 + builder.get_object('btn_remove').set_sensitive(not_last_break) + + grid_plugins = builder.get_object('grid_plugins') + list_types = builder.get_object('lst_break_types') + + 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.switch_override_duration.set_active(duration_overriden) + self.switch_override_plugins.set_active(plugins_overriden) + self.cmb_type.set_active(0 if is_short else 1) + list_types[0][0] = _(list_types[0][0]) + list_types[1][0] = _(list_types[1][0]) + + if duration_overriden: + self.spin_duration.set_value(break_config['duration']) + else: + if is_short: + self.spin_duration.set_value(parent_config.get('short_break_duration')) + else: + self.spin_duration.set_value(parent_config.get('long_break_duration')) + row = 0 + col = 0 + for plugin_id in plugin_map.keys(): + chk_button = Gtk.CheckButton(_(plugin_map[plugin_id])) + 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']) + else: + chk_button.set_active(True) + row += 1 + if row > 2: + col += 1 + row = 0 + # GtkSwitch state-set signal is available only from 3.14 + if Gtk.get_minor_version() >= 14: + 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_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_duration_activate(self, switch_button, state): + """ + 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. + """ + 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)) + + png_filter = Gtk.FileFilter() + png_filter.set_name("PNG files") + png_filter.add_mime_type("image/png") + png_filter.add_pattern("*.png") + dialog.add_filter(png_filter) + + 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.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) + + dialog.destroy() + + def remove_break(self, button): + """ + Remove the break + """ + if self.is_short: + self.parent_config.get('short_breaks').remove(self.break_config) + else: + self.parent_config.get('long_breaks').remove(self.break_config) + self.on_remove() + self.window.destroy() + + def on_window_delete(self, *args): + """ + Event handler for Properties dialog close action. + """ + break_name = self.txt_break.get_text().strip() + if break_name: + self.break_config['name'] = break_name + if self.switch_override_duration.get_active(): + self.break_config['duration'] = int(self.spin_duration.get_value()) + else: + 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 + else: + 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.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.on_remove() + self.on_add(not self.is_short, self.break_config) + else: + self.on_close(self.break_config) + self.window.destroy() + + def show(self): + """ + Show the Properties dialog. + """ + self.window.show_all() + + +class NewBreakDialog(object): + """ + Builds a new break dialog. + """ + def __init__(self, parent_config, on_add): + self.parent_config = parent_config + self.on_add = 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') + + 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')) + + def discard(self, button): + """ + 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()} + + if self.cmb_type.get_active() == 0: + 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.on_add(False, break_config) + self.window.destroy() + + def on_window_delete(self, *args): + """ + Event handler for dialog close action. + """ + self.window.destroy() + + def show(self): + """ + Show the Properties dialog. + """ + self.window.show_all() diff --git a/setup.py b/setup.py index 5a72e2e..3fe549d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import os +import subprocess import setuptools @@ -7,27 +8,57 @@ requires = [ 'psutil', 'babel'] -extras = { - 'audible_alert': ['pyaudio'] -} +_ROOT = os.path.abspath(os.path.dirname(__file__)) - -here = os.path.abspath(os.path.dirname(__file__)) - -with open(os.path.join(here, 'README.md')) as f: +with open(os.path.join(_ROOT, 'README.md')) as f: long_description = '\n' + 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]] + 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'] + for po_file in po_files: + filename, ext = os.path.splitext(po_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(path): + """ + Collect the data files. + """ for root, dirs, files in os.walk(path): if not files: continue yield (os.path.join('/usr', root), [os.path.join(root, f) for f in files]) +def __package_files(directory): + """ + Collect the package files. + """ + paths = [] + for (path, dirs, filenames) in os.walk(directory): + for filename in filenames: + paths.append(os.path.join('..', path, filename)) + return paths + +__compile_po_files() +__package_data = ['glade/*.glade', 'resource/*'] +__package_data.extend(__package_files('safeeyes/config')) +__package_data.extend(__package_files('safeeyes/plugins')) +__data_files = list(_data_files('share')) setuptools.setup( name="safeeyes", - version="1.2.2", + version="2.0.0", description="Protect your eyes from eye strain using this continuous breaks reminder.", long_description=long_description, author="Gobinath Loganathan", @@ -35,25 +66,9 @@ setuptools.setup( url="https://github.com/slgobinath/SafeEyes", download_url="https://github.com/slgobinath/SafeEyes/archive/v1.2.2.tar.gz", packages=setuptools.find_packages(), - package_data={'safeeyes': ['config/*.json', - 'config/style/*.css', - 'config/lang/*.json', - 'glade/*.glade', - 'resource/*']}, - data_files=[('/usr/share/applications', ['share/applications/safeeyes.desktop']), - ('/usr/share/icons/hicolor/16x16/apps', ['share/icons/hicolor/16x16/apps/safeeyes.png']), - ('/usr/share/icons/hicolor/24x24/apps', ['share/icons/hicolor/24x24/apps/safeeyes.png']), - ('/usr/share/icons/hicolor/48x48/apps', ['share/icons/hicolor/48x48/apps/safeeyes.png']), - ('/usr/share/icons/hicolor/32x32/apps', ['share/icons/hicolor/32x32/apps/safeeyes.png']), - ('/usr/share/icons/hicolor/64x64/apps', ['share/icons/hicolor/64x64/apps/safeeyes.png']), - ('/usr/share/icons/hicolor/128x128/apps', ['share/icons/hicolor/128x128/apps/safeeyes.png']), - ('/usr/share/icons/hicolor/48x48/status', ['share/icons/hicolor/48x48/status/safeeyes_enabled.png', 'share/icons/hicolor/48x48/status/safeeyes_disabled.png']), - ('/usr/share/icons/hicolor/32x32/status', ['share/icons/hicolor/32x32/status/safeeyes_enabled.png', 'share/icons/hicolor/32x32/status/safeeyes_disabled.png']), - ('/usr/share/icons/hicolor/24x24/status', ['share/icons/hicolor/24x24/status/safeeyes_enabled.png', 'share/icons/hicolor/24x24/status/safeeyes_disabled.png', 'share/icons/hicolor/24x24/status/safeeyes_timer.png']), - ('/usr/share/icons/hicolor/16x16/status', ['share/icons/hicolor/16x16/status/safeeyes_enabled.png', 'share/icons/hicolor/16x16/status/safeeyes_disabled.png', 'share/icons/hicolor/16x16/status/safeeyes_timer.png']) - ], + package_data={'safeeyes': __package_data}, + data_files=__data_files, install_requires=requires, - extras_require=extras, entry_points={'console_scripts': ['safeeyes = safeeyes.__main__:main']}, keywords='linux utility health eye-strain safe-eyes', classifiers=[