Resolved the conflicts with the Turkish Language file.

Merge remote-tracking branch 'upstream/master'
This commit is contained in:
Serdar ARSLAN 2017-05-26 17:26:49 +03:00
commit c68d910e1a
71 changed files with 2920 additions and 1375 deletions

94
.gitignore vendored Normal file
View File

@ -0,0 +1,94 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject

2
MANIFEST.in Normal file
View File

@ -0,0 +1,2 @@
include LICENSE
include README.md

219
README.md
View File

@ -1,177 +1,92 @@
# Safe Eyes
Protect your eyes from eye strain using this continuous breaks reminder. A Free and Open Source Linux alternative for EyeLeo.
Protect your eyes from eye strain using this simple and beautiful, yet extensible break reminder. A Free and Open Source Linux alternative to EyeLeo.
For more details: [SafeEyes Protects You From Eye Strain When Working On The Computer](http://www.webupd8.org/2016/10/safeeyes-protects-you-from-eye-strain.html)
Visit to the official site: http://slgobinath.github.io/SafeEyes/ for more details.
## Installation
## Installation guide
Safe Eyes is available in Ubuntu PPA, Arch AUR and Python PyPI. You can choose any installation source and install on any Linux system with Python 3. To see how to install Safe Eyes, visit [Getting Started](http://slgobinath.github.io/SafeEyes/#introduction)
### Ubuntu:
1: Add the PPA: `sudo add-apt-repository ppa:slgobinath/safeeyes`
### Compile from source
Ensure to meet the following dependencies when compiling from source:
2: Download the package list: `sudo apt update`
- gir1.2-appindicator3-0.1
- gir1.2-notify-0.7
- libappindicator-gtk3
- python3-pyaudio
- python3-psutil
- xprintidle (optional)
3: Install Safe Eyes: `sudo apt install safeeyes`
## 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)
4: Start Safe Eyes from start menu.
### Arch:
Install SafeEyes via [AUR](https://aur.archlinux.org/packages/safeeyes/). Credits to [Yamakaky](https://github.com/Yamakaky)
### Other Linux:
1: Install the dependencies:
* Arch: `hicolor-icon-theme`, `libappindicator-gtk3`, `xorg-xprop`, `python2-xlib`, `python2-gobject`, `python2-dbus`, `python2-babel`, `xprintidle` and `mpg123`
* Debian: `gir1.2-appindicator3-0.1`, `python-xlib`, `python-gobject`, `python-gi`, `python-dbus`, `gir1.2-notify-0.7`, `python-gtk2`, `python-babel`, `xprintidle` and `mpg123`
* Fedora 24: `libappindicator-gtk3`, `python-xlib`, `python-gobject`, `xorg-x11-utils`, `python-dbus`, `python-babel`, `xprintidle` and `mpg123`
2: Download and extract [safeeyes.tar.gz](https://github.com/slgobinath/SafeEyes/releases/download/v1.1.4/safeeyes.tar.gz) into `/`: `sudo tar -xzvf safeeyes.tar.gz -C /`
4: Start Safe Eyes using this command: `/opt/safeeyes/safeeyes`
Once started, Safe Eyes will copy the desktop file to `~/.config/autostart` and the configurations to `~/.config/safeeyes`. Therefore, from next time onwards, it should start with the system.
## Configuring Safe Eyes
Just install and forget; Safe Eyes will take care of your eyes. To customize the preferences, go to Settings from Safe Eyes tray icon.
You can change the look and feel of the break screen in `~/.config/safeeyes/style/safeeyes_style.css`.
## Uninstalling Safe Eyes
Use the following commands to uninstall SafeEyes from your system.
```
sudo apt remove safeeyes
rm -r ~/.config/safeeyes
rm ~/.config/autostart/safeeyes.desktop
```
## 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)
## Features
General Features:
- Short breaks with eye exercises
- Long breaks to change physical position and to warm up
- Strict break for those who are addicted to computer
- Highly customizable
- Do not disturb when working with fullscreen applications( Eg: Watching movies)
- Disable the keyboard during break
- Notifications before every break
- Smart pause and resume based on system idle time
- Optional audible alert at the end of 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
- Elegant and customizable design
- Multi-language support
- Elegant and customizable design
## Contributing
**Are you a user?**
Optional Features:
Please test Safe Eyes on your system and report any issues [here](https://github.com/slgobinath/SafeEyes/issues)
- 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
**Are you a developer?**
For more details: [SafeEyes Features](http://slgobinath.github.io/SafeEyes/#features)
1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request
## Currently available translations
* [Č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)
* [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)
* [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)
**Are you using a different Linux system?**
Please test Safe Eyes and create installers for your operating system
**Found a bug?**
Please report them [here](https://github.com/slgobinath/SafeEyes/issues)
**Can you translate English to your mother tongue (or whatever the language)?**
Show your support by translating Safe Eyes to a new language or by improving the existing translations.
**How else can you show your support?**
- Vote for Safe Eyes in [alternativeto.net](http://alternativeto.net/software/eyeleo/?platform=linux).
- Suggest any improvements.
- Share with your friends.
## Translating Safe Eyes
From version 1.1.0, Safe Eyes supports translation. Translation files for each langauges must be placed in `/opt/safeeyes/config/lang` directory. The language file name must follow [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code standard. For example, the language file of English must be `en.json`. Follow these steps to translate Safe Eyes to your language.
1. Copy `/opt/safeeyes/config/lang/en.json` to `/opt/safeeyes/config/lang/<iso-639-1-language-code>.json`
2. Provide `language_name` in the language itself and `language_name_en` in English.
3. Translate other property values to the selected language.
4. Translate the comment in [safeeyes.desktop](https://github.com/slgobinath/SafeEyes/blob/master/safeeyes/share/applications/safeeyes.desktop) file.
**Note 1:** The `{}` used in property values will be replaced by runtime variables related to those commands. For example the `{}` in `Next break at {}` will be replaced by time at the runtime.
**Note 2:** Use Unicode when translating Safe Eyes.
**Note 3:** To change the language of Safe Eyes, change the `language` property in `~/.config/safeeyes/safeeyes.json` to the ISO 639-1 code of your language and restart the Safe Eyes.
For more details, have a look at existing language files: [lang](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang)
### Currently available translations
* [Čeština](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/cz.json)
* [Deutsch](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/de.json)
* [English](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/en.json)
* [Español](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/es.json)
* [Français](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/fr.json)
* [Magyar](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/hu.json)
* [Português](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/pt.json)
* [Русский](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/ru.json)
* [Slovenský](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/sk.json)
* [தமிழ்](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/safeeyes/config/lang/ta.json)
## History
Version 1.1.3:
* Bug fix for no audible alert
Version 1.1.3:
* Optional audible alert after breaks
* Pause Safe Eyes if the system is idle for a given time. (Resume when user is active)
* Bug fix for no break after fullscreen apps found
* Dependency fix for Kubuntu
Version 1.1.2:
* Bug fix for no break
Version 1.1.1:
* About dialog
* UI control to select the language
* Fixed bug in disable option after suspend
Version 1.1.0:
* Multi-language support
* Fixed bug in multi-screen support
* Fixed bug in break screen transparency
* Next break information in tray menu
Version 1.0.9:
* Multi-screen support
* Handling system suspend (Stop and restart during system suspend)
Version 1.0.8:
* Bug fix for Ubuntu Mate
Version 1.0.7:
* Removed python-apscheduler dependency
* Installation directory is restructured
* Bug fixes:
* Supporting Ubuntu 16.10
* Symlink for autostart instead of copying the desktop file
Version 1.0.6:
* Latest stable release
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
* Ubuntu 14.04
* Ubuntu 16.04
* Ubuntu 16.10
* Linux Mint 18
* Ubuntu Mate 16.04
* Kubuntu 16.10
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
## License

View File

@ -1,4 +1,32 @@
safeeyes (1.1.4-1) xenial; urgency=medium
safeeyes (1.2.1-1) xenial; urgency=low
* Support postponing the break
* Handle configuration update efficiently
* Move to Python 3
* Add plugin support
* Add custom breaks
* Add optional break image feature
* Add lock screen support
* Prevent disabling Safe Eyes after notification
* Fix random crash
* Use system language if available
* Fix disable menu issue in Elementary OS
* Fix long breaks in hours
* Fix locale time format issue
* Advanced configurations and disable for given time
* Optional audible alert and pause Safe Eyes if system is idle
@ -11,23 +39,23 @@ safeeyes (1.1.4-1) xenial; urgency=medium
* Add next break information to tray menu
* Support translation
* Adding multiscreen support & handling system suspend
* Fixing bug in Ubuntu MATE environment
* Removing apscheduler dependency
* Fixing seconds instead of minutes bug
* Bug fixes for Ubuntu 14.04 and keyboard lock during break
* Reducing minimal Python requirement
* Fixing appindicator version mismatch
* Fixing apscheduler version mismatch
* Initial release
-- Loganathan Gobinath <slgobinath@gmail.com> Sat, 15 Oct 2016 06:28:40 +0530
-- Gobinath Loganathan <slgobinath@gmail.com> Sat, 15 Oct 2016 06:28:40 +0530

View File

@ -1,14 +1,15 @@
Source: safeeyes
Section: utils
Priority: optional
Maintainer: Loganathan Gobinath <slgobinath@gmail.com>
Build-Depends: debhelper (>= 9)
Maintainer: Gobinath Loganathan <slgobinath@gmail.com>
Build-Depends: debhelper (>= 9), python3, python3-setuptools
Standards-Version: 3.9.6
X-Python3-Version: >= 3.4
Homepage: https://github.com/slgobinath/SafeEyes/
Package: safeeyes
Architecture: any
Depends: gir1.2-appindicator3-0.1, python (>= 2.7.0), python-xlib, python-gi, python-dbus, gir1.2-notify-0.7, python-gtk2, python-babel, xprintidle, mpg123
Architecture: all
Depends: ${misc:Depends}, ${python3:Depends}, gir1.2-appindicator3-0.1, python3 (>= 3.4.0), python3-xlib, python3-dbus, gir1.2-notify-0.7, python3-babel, x11-utils, xprintidle, python3-pyaudio, python3-psutil
Description: Safe Eyes
Safe Eyes is a simple tool to remind you to take periodic breaks for your eyes. This is essential for anyone spending more time on the computer to avoid eye strain and other physical problems.
.
@ -16,10 +17,11 @@ Description: Safe Eyes
- Short breaks with eye exercises
- Long breaks to change physical position and to warm up
- Strict break for those who are addicted to computer
- Highly customizable
- Do not disturb when working with fullscreen applications( Eg: Watching movies)
- Do not disturb when working with full-screen applications( Eg: Watching movies)
- Notifications before every break
- Optional audible alert at the end of break
- Option to lock screen after long breaks
- Smart pause and resume based on system idle time
- Multi-monitor support
- Plugins to utilize Safe Eyes
- Elegant and customizable design

View File

@ -3,7 +3,7 @@ Upstream-Name: uget-chrome-wrapper
Source: https://github.com/slgobinath/SafeEyes/
Files: *
Copyright: 2016 Loganathan Gobinath <slgobinath@gmail.com>
Copyright: 2016 Gobinath Loganathan <slgobinath@gmail.com>
License: GPL-3.0+
License: GPL-3.0+

3
debian/rules vendored Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/make -f
%:
dh $@ --with python3 --buildsystem=pybuild

View File

@ -30,12 +30,15 @@ class AboutDialog:
"""
Read the about_dialog.glade and build the user interface.
"""
def __init__(self, glade_file, version):
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'])
# Set the version at the runtime
builder.get_object("lbl_app_name").set_label("Safe Eyes " + version)

View File

@ -16,11 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gi
import signal
import sys
import threading
import logging
import gi, signal, sys, threading, logging
from Xlib import Xatom, Xutil
from Xlib.display import Display, X
gi.require_version('Gtk', '3.0')
@ -35,8 +31,9 @@ class BreakScreen:
"""
Read the break_screen.glade and build the user interface.
"""
def __init__(self, on_skip, glade_file, style_sheet_path):
def __init__(self, on_skip, on_postpone, glade_file, style_sheet_path):
self.on_skip = on_skip
self.on_postpone = on_postpone
self.is_pretified = False
self.key_lock_condition = threading.Condition()
self.windows = []
@ -55,7 +52,9 @@ class BreakScreen:
def initialize(self, config, language):
logging.info("Initialize the break screen")
self.skip_button_text = language['ui_controls']['skip']
self.strict_break = config['strict_break']
self.postpone_button_text = language['ui_controls']['postpone']
self.strict_break = config.get('strict_break', False)
self.enable_postpone = config.get('allow_postpone', False)
"""
@ -72,10 +71,20 @@ class BreakScreen:
"""
def on_skip_clicked(self, button):
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()
"""
Postpone button press event handler.
"""
def on_postpone_clicked(self, button):
logging.info("User postponed the break")
self.on_postpone()
self.close()
"""
Show/update the count down on all screens.
"""
@ -86,8 +95,8 @@ class BreakScreen:
"""
Show the break screen with the given message on all displays.
"""
def show_message(self, message):
GLib.idle_add(lambda: self.__show_break_screen(message))
def show_message(self, message, image_path, plugins_data):
GLib.idle_add(lambda: self.__show_break_screen(message, image_path, plugins_data))
"""
@ -104,7 +113,7 @@ class BreakScreen:
"""
Show an empty break screen on all screens.
"""
def __show_break_screen(self, message):
def __show_break_screen(self, message, image_path, plugins_data):
# Lock the keyboard
thread = threading.Thread(target=self.__lock_keyboard)
thread.start()
@ -125,11 +134,36 @@ class BreakScreen:
window = builder.get_object("window_main")
lbl_message = builder.get_object("lbl_message")
lbl_count = builder.get_object("lbl_count")
btn_skip = builder.get_object("btn_skip")
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")
# 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 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)
# Set values
if image_path:
img_break.set_from_file(image_path)
lbl_message.set_label(message)
btn_skip.set_label(self.skip_button_text)
btn_skip.set_visible(not self.strict_break)
lbl_left.set_markup(plugins_data['left']);
lbl_right.set_markup(plugins_data['right']);
self.windows.append(window)
self.count_labels.append(lbl_count)
@ -167,7 +201,7 @@ class BreakScreen:
while self.lock_keyboard:
self.key_lock_condition.wait()
self.key_lock_condition.release()
# Ungrap the keyboard
logging.info("Unlock the keyboard")
display.ungrab_keyboard(X.CurrentTime)

View File

@ -16,46 +16,46 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gi
import logging
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
import gi, logging
gi.require_version('Notify', '0.7')
from gi.repository import Gtk, Gdk, GLib, GdkX11
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Notify
from safeeyes import Utility
APPINDICATOR_ID = 'safeeyes'
"""
This class is responsible for the notification to the user before the break.
"""
class Notification:
"""
This class is responsible for the notification to the user before the break.
"""
"""
Initialize the notification.
"""
def __init__(self, language):
logging.info("Initialize the notification")
"""
Initialize the notification.
"""
logging.info('Initialize the notification')
Notify.init(APPINDICATOR_ID)
self.language = language
"""
Show the notification"
"""
def show(self, warning_time):
logging.info("Show pre-break notification")
self.notification = Notify.Notification.new("Safe Eyes", "\n" + self.language['messages']['ready_for_a_break'].format(warning_time), icon="safeeyes_enabled")
self.notification.show()
"""
Show the notification
"""
logging.info('Show pre-break notification')
self.notification = Notify.Notification.new('Safe Eyes', '\n' + self.language['messages']['ready_for_a_break'].format(warning_time), icon='safeeyes_enabled')
try:
self.notification.show()
except Exception as e:
logging.exception('Error in showing notification', e)
"""
Close the notification if it is not closed by the system already.
"""
def close(self):
logging.info("Close pre-break notification")
"""
Close the notification if it is not closed by the system already.
"""
logging.info('Close pre-break notification')
try:
self.notification.close()
except:
@ -63,9 +63,9 @@ class Notification:
pass
"""
Uninitialize the notification. Call this method when closing the application.
"""
def quite(self):
logging.info("Uninitialize Safe Eyes notification")
GLib.idle_add(lambda: Notify.uninit())
"""
Uninitialize the notification. Call this method when closing the application.
"""
logging.info('Uninitialize Safe Eyes notification')
Utility.execute_main_thread(Notify.uninit)

150
safeeyes/Plugins.py Normal file
View File

@ -0,0 +1,150 @@
# 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 <http://www.gnu.org/licenses/>.
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:
"""
This class manages imports the plugins and calls the methods defined in those plugins.
"""
def __init__(self, config):
"""
Load the plugins.
"""
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

View File

@ -17,7 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time, datetime, threading, sys, subprocess, logging, Utility
import time, datetime, threading, sys, subprocess, logging
from safeeyes import Utility
"""
@ -28,12 +29,11 @@ class SafeEyesCore:
"""
Initialize the internal variables of the core.
"""
def __init__(self, show_notification, start_break, end_break, on_countdown, update_next_break_info):
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.skipped = False
self.active = False
self.running = False
self.show_notification = show_notification
@ -44,6 +44,9 @@ class SafeEyesCore:
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
"""
@ -60,31 +63,53 @@ class SafeEyesCore:
self.short_break_duration = config['short_break_duration']
self.break_interval = config['break_interval']
self.idle_time = config['idle_time']
self.postpone_duration = config['postpone_duration']
self.skip_break_window_classes = [x.lower() for x in config['active_window_class']['skip_break']]
self.take_break_window_classes = [x.lower() for x in config['active_window_class']['take_break']]
self.custom_exercises = config['custom_exercises']
# Enable idle time pause only if xprintidle is available
self.context['idle_pause_enabled'] = Utility.command_exist('xprintidle')
exercises = language['exercises']
for short_break_config in config['short_breaks']:
name = language['exercises'][short_break_config['name']]
# break_time = short_break_config['time']
exercise_name = short_break_config['name']
name = None
if exercise_name in self.custom_exercises:
name = self.custom_exercises[exercise_name]
else:
name = exercises[exercise_name]
break_time = short_break_config.get('time', self.short_break_duration)
audible_alert = short_break_config.get('audible_alert', config['audible_alert'])
image = short_break_config.get('image')
# Validate time value
if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in short break: ' + str(short_break_config))
continue
self.short_break_exercises.append([name, break_time])
self.short_break_exercises.append([name, break_time, audible_alert, image])
for long_break_config in config['long_breaks']:
name = language['exercises'][long_break_config['name']]
break_time = long_break_config.get('time', self.short_break_duration)
# Validate time value
if not break_time:
break_time = self.short_break_duration
elif not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in short break: ' + str(long_break_config))
continue
exercise_name = long_break_config['name']
name = None
if exercise_name in self.custom_exercises:
name = self.custom_exercises[exercise_name]
else:
break_time = break_time * 60 # Convert to seconds
self.long_break_exercises.append([name, break_time])
name = exercises[exercise_name]
break_time = long_break_config.get('time', self.long_break_duration)
audible_alert = long_break_config.get('audible_alert', config['audible_alert'])
image = long_break_config.get('image')
# Validate time value
if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in long break: ' + str(long_break_config))
continue
self.long_break_exercises.append([name, break_time, audible_alert, image])
"""
@ -97,7 +122,8 @@ class SafeEyesCore:
self.active = True
self.running = True
Utility.start_thread(self.__scheduler_job)
Utility.start_thread(self.__start_idle_monitor)
if self.context['idle_pause_enabled']:
Utility.start_thread(self.__start_idle_monitor)
"""
@ -151,7 +177,13 @@ class SafeEyesCore:
User skipped the break using Skip button
"""
def skip_break(self):
self.skipped = True
self.context['skipped'] = True
"""
User postponed the break using Postpone button
"""
def postpone_break(self):
self.context['postponed'] = True
"""
@ -161,14 +193,31 @@ class SafeEyesCore:
if not self.__is_running():
return
next_break_time = datetime.datetime.now() + datetime.timedelta(minutes=self.break_interval)
time_to_wait = self.break_interval # In minutes
if self.context['postponed']:
# Reduce the break count by 1 to show the same break again
if self.break_count == 0:
self.break_count = -1
else:
self.break_count = ((self.break_count - 1) % self.no_of_short_breaks_per_long_break)
if self.__is_long_break():
self.long_break_message_index = (self.long_break_message_index - 1) % len(self.long_break_exercises)
else:
self.short_break_message_index = (self.short_break_message_index - 1) % len(self.short_break_exercises)
# Wait until the postpone time
time_to_wait = self.postpone_duration
self.context['postponed'] = False
next_break_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
self.update_next_break_info(next_break_time)
# Wait for the pre break warning period
logging.info("Pre-break waiting for {} minutes".format(self.break_interval))
logging.info("Pre-break waiting for {} minutes".format(time_to_wait))
self.notification_condition.acquire()
self.notification_condition.wait(self.break_interval * 60) # In minutes
self.notification_condition.wait(time_to_wait * 60) # Convert to seconds
self.notification_condition.release()
logging.info("Pre-break waiting is over")
@ -178,29 +227,17 @@ class SafeEyesCore:
logging.info("Ready to show the break")
Utility.execute_main_thread(self.__process_job)
"""
Used to process the job in default thread because __is_full_screen_app_found must be run by default thread
"""
def __process_job(self):
if Utility.is_full_screen_app_found():
# If full screen app found, do not show break screen
logging.info("Found a full-screen application. Skip the break")
if self.__is_running():
# Schedule the break again
Utility.start_thread(self.__scheduler_job)
return
self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break)
Utility.start_thread(self.__notify_and_start_break)
self.is_before_break = False
Utility.execute_main_thread(self.__check_active_window)
"""
Show notification and start the break after given number of seconds
Show the notification and start the break after the notification.
"""
def __notify_and_start_break(self):
# Show a notification
def __show_notification(self):
# Show the notification
self.show_notification()
logging.info("Wait for {} seconds which is the time to prepare".format(self.pre_break_warning_time))
@ -209,26 +246,68 @@ class SafeEyesCore:
self.notification_condition.wait(self.pre_break_warning_time)
self.notification_condition.release()
self.is_before_break = True
Utility.execute_main_thread(self.__check_active_window)
"""
Check the active window for full-screen and user defined exceptions.
"""
def __check_active_window(self):
# 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
# Execute the post-operation
if self.is_before_break:
Utility.start_thread(self.__start_break)
else:
Utility.start_thread(self.__show_notification)
"""
Start the break screen.
"""
def __start_break(self):
# 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]
self.context['break_type'] = 'long'
else:
logging.info("Count is {}; get a short beak message".format(self.break_count))
self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises)
message = self.short_break_exercises[self.short_break_message_index][0]
seconds = self.short_break_exercises[self.short_break_message_index][1]
audible_alert = self.short_break_exercises[self.short_break_message_index][2]
image = self.short_break_exercises[self.short_break_message_index][3]
self.context['break_type'] = 'short'
self.context['break_length'] = seconds
self.context['audible_alert'] = audible_alert
total_break_time = seconds
# Show the break screen
self.start_break(message)
self.start_break(message, image)
# Use self.active instead of self.__is_running to avoid idle pause interrupting the break
while seconds and self.active and not self.skipped:
while seconds and self.active and not self.context['skipped'] and not self.context['postponed']:
self.context['count_down'] = total_break_time - seconds
mins, secs = divmod(seconds, 60)
timeformat = '{:02d}:{:02d}'.format(mins, secs)
self.on_countdown(timeformat)
@ -236,16 +315,18 @@ class SafeEyesCore:
seconds -= 1
# Loop terminated because of timeout (not skipped) -> Close the break alert
if not self.skipped:
logging.info("Break wasn't skipped. Automatically terminating the break")
self.end_break()
if not self.context['skipped'] and not self.context['postponed']:
logging.info("Break is terminated automatically")
self.end_break(audible_alert)
# Reset the skipped flag
self.context['skipped'] = False
# Resume
if self.__is_running():
# Schedule the break again
Utility.start_thread(self.__scheduler_job)
self.skipped = False
"""

View File

@ -19,12 +19,13 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkX11, GObject
from safeeyes import Utility
class SettingsDialog:
"""
Create and initialize SettingsDialog instance.
"""
def __init__(self, config, language, languages, on_save_settings, glade_file):
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 = []
@ -33,6 +34,7 @@ class SettingsDialog:
builder.add_from_file(glade_file)
builder.connect_signals(self)
# 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')
@ -40,68 +42,154 @@ class SettingsDialog:
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.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_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'])
self.spin_postpone_duration.set_value(config['postpone_duration'])
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'])
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(Utility.command_exist('xprintidle'))
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())
# Initialize the language combobox
language_list_store = Gtk.ListStore(GObject.TYPE_STRING)
language_index = 0
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 == config['language']:
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)
"""
Show the SettingsDialog.
"""
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_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['show_time_in_tray'] = self.switch_show_time_in_tray.get_active()
self.config['strict_break'] = self.switch_strict_break.get_active()
self.config['audible_alert'] = self.switch_audible_alert.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()
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()

262
safeeyes/TrayIcon.py Normal file
View File

@ -0,0 +1,262 @@
# 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 <http://www.gnu.org/licenses/>.
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:
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):
self.config = config
def set_labels(self, 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):
Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.ACTIVE)
def hide_icon(self):
Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.PASSIVE)
def quit_safe_eyes(self, *args):
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):
self.on_show_settings()
def show_about(self, *args):
self.on_show_about()
def next_break_time(self, dateTime):
logging.info("Update next break information")
self.dateTime = dateTime
self.__set_next_break_info()
def __set_next_break_info(self):
formatted_time = Utility.format_time(self.dateTime)
message = self.language['messages']['next_break_at'].format(formatted_time)
# Update the tray icon label
if self.config.get('show_time_in_tray', False):
self.indicator.set_label(formatted_time, '')
else:
self.indicator.set_label('', '')
# Update the menu item label
Utility.execute_main_thread(self.item_info.set_label, message)
def on_enable_clicked(self, *args):
# 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):
# 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)))
"""
This method is called by the core to prevent user from disabling Safe Eyes after the notification.
"""
def lock_menu(self):
if self.active:
# self.item_disable.set_sensitive(False)
# self.item_settings.set_sensitive(False)
# self.item_quit.set_sensitive(False)
self.menu.set_sensitive(False)
"""
This method is called by the core to activate the disable menu after the the break.
"""
def unlock_menu(self):
if self.active:
# self.item_disable.set_sensitive(True)
# self.item_settings.set_sensitive(True)
# self.item_quit.set_sensitive(True)
self.menu.set_sensitive(True)
def __schedule_resume(self, time_minutes):
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)

455
safeeyes/Utility.py Normal file
View File

@ -0,0 +1,455 @@
# 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 <http://www.gnu.org/licenses/>.
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, GLib
from html.parser import HTMLParser
from distutils.version import LooseVersion
from logging.handlers import RotatingFileHandler
import babel.dates, os, errno, re, subprocess, threading, logging, locale, json, shutil, pyaudio, 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')
def play_notification():
"""
Play the alert.wav
"""
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)
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 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
def start_thread(target_function, **args):
"""
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())
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 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)
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
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_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 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']
return languages
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 not 'deprecated' in os.environ.get('GNOME_DESKTOP_SESSION_ID') and command_exist('gnome-screensaver-command'):
# Gnome 2
return ['gnome-screensaver-command', '--lock']
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 command_exist(command):
"""
Check whether the given command exist in the system or not.
"""
if shutil.which(command):
return True
else:
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
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')
# Remove the ~/.config/safeeyes directory
shutil.rmtree(config_directory, ignore_errors=True)
# Remove the startup file
try:
os.remove(os.path.join(home_directory, os.path.join(startup_dir_path, 'safeeyes.desktop')))
except:
pass
# 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 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 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
# Configure logging.
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)
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
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 handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)

0
safeeyes/__init__.py Normal file
View File

278
safeeyes/__main__.py Executable file
View File

@ -0,0 +1,278 @@
#!/usr/bin/env python3
# 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 <http://www.gnu.org/licenses/>.
import os, gi, json, dbus, logging, operator, psutil, sys
from threading import Timer
from dbus.mainloop.glib import DBusGMainLoop
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.1"
"""
Listen to tray icon Settings action and send the signal to Settings dialog.
"""
def show_settings():
logging.info("Show Settings dialog")
able_to_lock_screen = False
if system_lock_command:
able_to_lock_screen = True
settings_dialog = SettingsDialog(config, language, Utility.read_lang_files(), able_to_lock_screen, save_settings, settings_dialog_glade)
settings_dialog.show()
"""
Listen to tray icon About action and send the signal to About dialog.
"""
def show_about():
logging.info("Show About dialog")
about_dialog = AboutDialog(about_dialog_glade, SAFE_EYES_VERSION, language)
about_dialog.show()
"""
Receive the signal from core and pass it to the Notification.
"""
def show_notification():
if config['strict_break']:
Utility.execute_main_thread(tray_icon.lock_menu)
plugins.pre_notification(context)
notification.show(config['pre_break_warning_time'])
"""
Receive the break signal from core and pass it to the break screen.
"""
def show_alert(message, image_name):
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)
"""
Receive the stop break signal from core and pass it to the break screen.
"""
def close_alert(audible_alert_on):
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)
"""
Listen to the tray menu quit action and stop the core, notification and the app itself.
"""
def on_quit():
logging.info("Quit Safe Eyes")
plugins.exit(context)
core.stop()
notification.quite();
Gtk.main_quit()
"""
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.
"""
def handle_suspend_callback(sleeping):
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")
"""
Setup system suspend listener.
"""
def handle_system_suspend():
DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus.add_signal_receiver(handle_suspend_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
"""
Listen to break screen Skip action and send the signal to core.
"""
def on_skipped():
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)
"""
Listen to break screen Postpone action and send the signal to core.
"""
def on_postponed():
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()
"""
Listen to Settings dialog Save action and write to the config file.
"""
def save_settings(config):
global language
logging.info("Saving settings to safeeyes.json")
# Stop the Safe Eyes core
if is_active:
core.stop()
# Write the configuration to file
with open(Utility.config_file_path, 'w') as config_file:
json.dump(config, config_file, indent=4, sort_keys=True)
# Reload the language translation
language = Utility.load_language(config['language'])
tray_icon.initialize(config)
tray_icon.set_labels(language)
logging.info("Initialize SafeEyesCore with modified settings")
# Restart the core and intialize the components
core.initialize(config, language)
break_screen.initialize(config, language)
if is_active:
# 1 sec delay is required to give enough time for core to be stopped
Timer(1.0, core.start).start()
"""
Listen to tray icon enable action and send the signal to core.
"""
def enable_safeeyes():
global is_active
is_active = True
core.start()
"""
Listen to tray icon disable action and send the signal to core.
"""
def disable_safeeyes():
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 main():
"""
Start the Safe Eyes.
"""
# Initialize the logging
Utility.intialize_logging()
logging.info("Starting Safe Eyes")
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
tray_icon = TrayIcon(config, language, show_settings, show_about, enable_safeeyes, disable_safeeyes, on_quit)
break_screen = BreakScreen(on_skipped, on_postponed, break_screen_glade, Utility.style_sheet_path)
break_screen.initialize(config, language)
notification = Notification(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 __name__ == '__main__':
main()

View File

@ -0,0 +1,58 @@
{
"meta_info": {
"language_name": "Čeština",
"language_name_en": "Czech"
},
"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": "Zavřete oči",
"short_break_roll_eyes": "Kroužete očima",
"short_break_rotate_clockwise": "Kroužete očima ve směru hodinových ručiček",
"short_break_rotate_counter_clockwise": "Kroužete očima proti směru hodinových ručiček",
"short_break_blink": "Mrkejte",
"short_break_focus_far_distance": "Soustřeďte se na bod v dálce",
"short_break_drink_water": "Dejte si nějakou vodu",
"long_break_walk": "Na chvíli se projděte",
"long_break_lean_back": "Opřete se do židle a relaxujte"
},
"messages": {
"ready_for_a_break": "Připravte se na přestávku za {} sekund",
"disabled_until_restart": "Pozastaveno do restartu",
"disabled_until_x": "Pozastaveno na {}",
"next_break_at": "Příští přestávka v {}"
},
"ui_controls": {
"about": "O aplikaci",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Zvukové upozornění na konec přestávky",
"cancel": "Zrušit",
"close": "Close",
"disable": "Pozastavit Safe Eyes",
"enable": "Zapnout Safe Eyes",
"enable_screen_lock": "Po každé dlouhé přestávce uzamknout obrazovku",
"for_x_hour": "Na {} hodinu",
"for_x_hours": "Na {} hodiny",
"for_x_minutes": "Na {} minut",
"idle_time": "Pozastavit při nečinnosti delší než (minut)",
"interval_between_two_breaks": "Interval mezi dvěma přestávkami",
"language": "Jazyk",
"license": "License",
"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": "Show the next break time in system tray",
"skip": "Přeskočit",
"strict_break": "Povinná přestávka (skrýt tlačítko pro přeskočení)",
"system_language": "Systémový jazyk",
"time_to_prepare_for_break": "Čas k přípravě na přestávku (v sekundách)",
"time_to_screen_lock": "O kolik nejvýše přeskočit, obcházející zámek obrazovky (v sekundách)",
"until_restart": "Do restartu"
}
}

View File

@ -3,44 +3,56 @@
"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": "Zwinkern Sie",
"short_break_focus_far_distance": "Fokussieren Sie sich auf einen weit entfernten Punkt",
"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": {
"ready_for_a_break": "Nächste Pause in {} Sekunden",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"disabled_until_restart": "Deaktiviert bis zum Neustart",
"disabled_until_x": "Deaktiviert bis {}",
"next_break_at": "Nächste Pause um {}"
},
"ui_controls": {
"skip": "Überspringen",
"short_break_duration": "Kleine Pause Intervall (in Sekunden)",
"long_break_duration": "Lange Pause Intervall (in Sekunden)",
"interval_between_two_breaks": "Intervall zwischen zwei Pausen",
"no_of_short_breaks_between_two_long_breaks": "Anzahl von kleinen Pausen zwischen zwei langen Pausen",
"time_to_prepare_for_break": "Zeit zur Vorbereitung für die Pause (in Sekunden)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Strikte Pause (Überspringen nicht möglich)",
"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",
"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",
"enable": "Safe Eyes Aktivieren",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Einstellungen",
"about": "About",
"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",
"cancel": "Abbrechen"
"settings": "Einstellungen",
"short_break_duration": "Kleine-Pause-Intervall (in Sekunden)",
"show_time_in_tray": "Show the next break time in system tray",
"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"
}
}

View File

@ -3,9 +3,12 @@
"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",
"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",
@ -21,26 +24,35 @@
"next_break_at": "Next break at {}"
},
"ui_controls": {
"skip": "Skip",
"short_break_duration": "Short break duration (in seconds)",
"long_break_duration": "Long break duration (in seconds)",
"interval_between_two_breaks": "Interval between two breaks (in minutes)",
"no_of_short_breaks_between_two_long_breaks": "Number of short breaks between two long breaks",
"time_to_prepare_for_break": "Time to prepare for break (in seconds)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Strict break (Hide skip button)",
"about": "About",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Audible alert at the end of break",
"language": "Language",
"enable": "Enable Safe Eyes",
"cancel": "Cancel",
"close": "Close",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"enable": "Enable Safe Eyes",
"enable_screen_lock": "Lock the screen after every long break",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Settings",
"about": "About",
"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",
"cancel": "Cancel"
"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"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_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 {}"
},
"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",
"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"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_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 à {}"
},
"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",
"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": "Show the next break time in system tray",
"skip": "Ignorer",
"strict_break": "Pause stricte (cacher le bouton Ignorer)",
"system_language": "Langue du système ",
"time_to_prepare_for_break": "Temps de préparation à une pause (en secondes)",
"time_to_screen_lock": "Temps maximal pour ignorer, en passant outre le verrouillage (en secondes)",
"until_restart": "Jusqu'au redémarrage"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_break": "მოემზადეთ შესვენებისთვის {} წამში",
"disabled_until_restart": "გავაუქმოთ შემდეგ რესტარტამდე",
"disabled_until_x": "გაუქმებულია {} მდე",
"next_break_at": "შემდეგი შესვენება {}"
},
"ui_controls": {
"about": "პროგრამის შესახებ",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "ხმოვანი შეტყობინება შესვენების დამთავრებისას",
"cancel": "უარყოფა",
"close": "Close",
"disable": "Safe Eyes გამორთვა",
"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": "შემდეგ რესტარტამდე"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_break": "{} पलों में आराम",
"disabled_until_restart": "अगले आरंभ तक बंद",
"disabled_until_x": "{} तक बंद",
"next_break_at": "अगला आराम {} में"
},
"ui_controls": {
"about": "हमारे बारे में",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "आराम के ख़तम के ख़तम होने पर आवाज़",
"cancel": "रखना नहीं",
"close": "Close",
"disable": "सेफ आईज बंद",
"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": "अगले आरम्भ तक"
}
}

View File

@ -3,6 +3,9 @@
"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",
@ -21,26 +24,35 @@
"next_break_at": "A következő szünet {}"
},
"ui_controls": {
"skip": "Átugrás",
"short_break_duration": "Rövid szünet hossza (másodpercekben)",
"long_break_duration": "Hosszú szünet hossza (másodpercekben)",
"interval_between_two_breaks": "Két szünet közötti idő",
"no_of_short_breaks_between_two_long_breaks": "Hány rövid szünet legyen a hosszabbak között?",
"time_to_prepare_for_break": "Szünet előtti figyelmeztetés (másodperc)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Kötelezők a szünetek? (nincs átugrás gomb)",
"about": "Ról ről",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Audible alert at the end of break",
"language": "Nyelv",
"enable": "Safe Eyes Bekapcsolása",
"cancel": "Mégse",
"close": "Close",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"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",
"until_restart": "Until restart",
"settings": "Beállítások",
"about": "Ról ről",
"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",
"cancel": "Mégse"
"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"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_break": "Bersiap beristirahat dalam {} detik",
"disabled_until_restart": "Dimatikan hingga dijalankan ulang",
"disabled_until_x": "Dimatikan hingga {}",
"next_break_at": "Istirahat selanjutnya pada {}"
},
"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",
"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"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_break": "Пауза за {} секунди",
"disabled_until_restart": "Оневозможено до рестарт",
"disabled_until_x": "Оневозможено до {}",
"next_break_at": "Следна пауза во {}"
},
"ui_controls": {
"about": "За",
"allow_postpone": "Овозможи одложување на паузи",
"audible_alert": "Звучен оглас на крај на пауза",
"cancel": "Откажи",
"close": "Затвори",
"disable": "Оневозможете го Safe Eyes",
"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": "До рестарт"
}
}

View File

@ -0,0 +1,58 @@
{
"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": {
"ready_for_a_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 {}"
},
"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",
"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": "Show the next break time in system tray",
"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"
}
}

View File

@ -3,6 +3,9 @@
"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",
@ -16,31 +19,40 @@
},
"messages": {
"ready_for_a_break": "Pronto para uma pausa em {} segundos",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"disabled_until_restart": "Desativado até reiniciar",
"disabled_until_x": "Desativado até {}",
"next_break_at": "Próxima pausa em {}"
},
"ui_controls": {
"skip": "Pular",
"short_break_duration": "Duração de uma pausa curta (em segundos)",
"long_break_duration": "Duração de uma pausa longa (em segundos)",
"interval_between_two_breaks": "Intervalo entre duas pausas",
"no_of_short_breaks_between_two_long_breaks": "Número de pausas curtas entre duas pausas longas",
"time_to_prepare_for_break": "Tempo para se preparar para a pausa (em segundos)",
"idle_time": "Tempo mínimo de inatividade para pausar (em minutos)",
"strict_break": "Pausa rigorosa (Esconder botão pular)",
"audible_alert": "Alerta sonoro no fim da pausa",
"language": "Idioma",
"enable": "Habilitar Safe Eyes",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Configuração",
"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",
"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",
"cancel": "Cancelar"
"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"
}
}

View File

@ -3,6 +3,9 @@
"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": "Закатите глаза",
@ -16,31 +19,40 @@
},
"messages": {
"ready_for_a_break": "Приготовьтесь к перерыву через {} секунд",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"disabled_until_restart": "Отключено до перезагрузки",
"disabled_until_x": "Отключено до {}",
"next_break_at": "Следующий перерыв в {}"
},
"ui_controls": {
"skip": "Пропустить",
"short_break_duration": "Продолжительность короткого перерыва (в секундах)",
"long_break_duration": "Продолжительность длинного перерыва (в секундах)",
"interval_between_two_breaks": "Интервал между двумя перерывами",
"no_of_short_breaks_between_two_long_breaks": "Количество коротких перерывов между двумя длинными",
"time_to_prepare_for_break": "Время подготовки в перерыву (в секундах)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Обязательный перерыв (Скрыть кнопку 'Пропустить')",
"audible_alert": "Audible alert at the end of break",
"language": "Язык",
"enable": "Активировать Safe Eyes",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Настройки",
"about": "О программе",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Звуковой сигнал в конце перерыва",
"cancel": "Отменить",
"close": "Close",
"disable": "Отключить Safe Eyes",
"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": "Сохранить",
"cancel": "Отменить"
"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": "До перезагрузки"
}
}

View File

@ -3,44 +3,56 @@
"language_name": "Slovenský",
"language_name_en": "Slovak"
},
"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": "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": "Focus on a point in the far distance",
"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": {
"ready_for_a_break": "Priprav sa na prestávku o {} sekúnd",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"disabled_until_restart": "Zakázať do reštartu",
"disabled_until_x": "Zakázať do {}",
"next_break_at": "Ďalšia prestávka o {}"
},
"ui_controls": {
"skip": "Preskočiť",
"short_break_duration": "Trvanie krátkej prestávky (v sekundách)",
"long_break_duration": "Trvanie dlhej prestávky (v sekundách)",
"interval_between_two_breaks": "Interval medzi dvomi prestávkami",
"no_of_short_breaks_between_two_long_breaks": "Počet krátkych prestávok medzi dvomi dlhými prestávkami",
"time_to_prepare_for_break": "Čas na prípravu na prestávku (v sekundách)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Povinná prestávka (Skryje tlačitko Preskočiť)",
"audible_alert": "Audible alert at the end of break",
"language": "Jazyk",
"enable": "Povoliť Safe Eyes",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Nastavenia",
"about": "Ohľadom",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Zvukový signál na konci prestávky",
"cancel": "Zrušiť",
"close": "Close",
"disable": "Zakázať Safe Eyes",
"enable": "Povoliť Safe Eyes",
"enable_screen_lock": "Lock the screen after every long break",
"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": "License",
"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": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"quit": "Koniec",
"save": "Uložiť",
"cancel": "Zrušiť"
"settings": "Nastavenia",
"short_break_duration": "Trvanie krátkej prestávky (v sekundách)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Preskočiť",
"strict_break": "Povinná prestávka (Skryje tlačitko Preskočiť)",
"system_language": "System Language",
"time_to_prepare_for_break": "Čas na prípravu na prestávku (v sekundách)",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"until_restart": "Do reštartu"
}
}

View File

@ -3,6 +3,9 @@
"language_name": "தமிழ்",
"language_name_en": "Tamil"
},
"app_info": {
"description": "அடிக்கடி ஓய்வெடுக்க அறிவுறுத்துவதன் மூலம் Safe Eyes கணனியில் நீண்ட நேரம் பணியாற்றுவதால் ஏற்படும் கண் சோர்வை தடுக்கிறது."
},
"exercises": {
"short_break_close_eyes": "உங்கள் கண்களை இறுக்கமாக மூடுங்கள்",
"short_break_roll_eyes": "உங்கள் கண்களை இடம் வலமாக உருட்டுங்கள்",
@ -21,26 +24,35 @@
"next_break_at": "அடுத்த இடைவேளை {}"
},
"ui_controls": {
"about": "Safe Eyes குறித்த தகவல்கள்",
"allow_postpone": "ஒத்திவைக்க அனுமதிக்கவும்",
"audible_alert": "இடைவேளையின் இறுதியில் ஒலி சமிக்கை",
"cancel": "ரத்து",
"enable": "Safe Eyes ஐ செயல்படுத்துக",
"close": "மூடு",
"disable": "Safe Eyes ஐ நிறுத்துக",
"for_x_minutes": "{} நிமிடங்களுக்கு",
"enable": "Safe Eyes ஐ செயல்படுத்துக",
"enable_screen_lock": "நீண்ட கால இடைவேளைகளின் பின்னர் திரையை பூட்டுக",
"for_x_hour": "{} மணித்தியாலத்திற்கு",
"for_x_hours": "{} மணித்தியாலங்களுக்கு",
"until_restart": "மீள ஆரம்பிக்கும் வரைை",
"interval_between_two_breaks": "இரண்டு இடைவேளைகளுக்கிடையிலான இடைவெளி (நிமிடங்களில்)",
"long_break_duration": "நீண்ட கால இடைவேளை (விநாடிகளில்)",
"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": "நிறுத்து",
"about": "Safe Eyes குறித்த தகவல்கள்",
"save": "சேமி",
"settings": "அமைப்பு",
"short_break_duration": "குறுகிய கால இடைவேளை (விநாடிகளில்)",
"show_time_in_tray": "அடுத்த இடைவேளை நேரத்தை Safe Eyes சின்னத்திற்கு அருகில் காண்பிக்கவும்",
"skip": "தவிர்",
"strict_break": "கட்டாய இடைவேளை (தவிர்க்கும் பொத்தான் காண்பிக்கப்பட மாட்டாது)",
"audible_alert": "இடைவேளையின் இறுதியில் ஒலி சமிக்கை",
"language": "மொழி",
"time_to_prepare_for_break": "இடைவேளைக்கு தயாராக தேவைப்படும் நேரம் (விநாடிகளில்)"
"system_language": "இயங்குதள மொழி",
"time_to_prepare_for_break": "இடைவேளைக்கு தயாராக தேவைப்படும் நேரம் (விநாடிகளில்)",
"time_to_screen_lock": "திரையினை பூட்டாமல் இடைவேளையை தவிர்ப்பதற்கான அதிகபட்ச நேரம் (விநாடிகளில்)",
"until_restart": "மீள ஆரம்பிக்கும் வரைை"
}
}

View File

@ -0,0 +1,81 @@
{
"meta": {
"config_version": "5.0.1"
},
"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,
"postpone_duration": 5,
"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": "short_break_roll_eyes"
},
{
"name": "short_break_rotate_clockwise"
},
{
"name": "short_break_rotate_counter_clockwise"
},
{
"name": "short_break_blink"
},
{
"name": "short_break_focus_far_distance"
},
{
"name": "short_break_drink_water"
}
],
"long_breaks": [
{
"name": "long_break_walk"
},
{
"name": "long_break_lean_back"
}
],
"custom_exercises": {
},
"plugins": []
}

View File

@ -25,7 +25,8 @@
.btn_skip {
color: white;
border-radius: 20px;
font-size: 10pt;
border-radius: 25px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 25px;
@ -41,13 +42,38 @@
color: black;
}
.btn_postpone{
color: white;
font-size: 10pt;
border-radius: 25px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 25px;
padding-right: 25px;
border-color: white;
background: transparent;
border-width: 2px;
border-image: none;
}
.btn_postpone:hover {
background: white;
color: black;
}
.lbl_message {
font-size: 16pt;
font-size: 22pt;
color: white;
font-weight: bold;
}
.lbl_count {
font-size: 10pt;
font-size: 12pt;
color: white;
}
.panel_left {
}
.panel_right {
}

View File

@ -1,2 +0,0 @@
safeeyes /opt
share /usr

View File

@ -1,3 +0,0 @@
#!/usr/bin/make -f
%:
dh $@

View File

@ -41,7 +41,7 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="title" translatable="yes">Safe Eyes</property>
<property name="resizable">False</property>
<property name="window_position">center</property>
<property name="default_width">400</property>
<property name="default_width">350</property>
<property name="default_height">200</property>
<property name="icon_name">safeeyes</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
@ -88,10 +88,10 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">4</property>
<property name="label" translatable="yes">Safe Eyes is an open source Linux alternative for Eye Leo to protect your eyes
from eye strain (asthenopia) by reminding you to take breaks while you're
working long hours at the computer.</property>
<property name="label" translatable="yes">Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer.</property>
<property name="justify">fill</property>
<property name="wrap">True</property>
<property name="max_width_chars">60</property>
</object>
<packing>
<property name="expand">False</property>
@ -118,9 +118,9 @@ working long hours at the computer.</property>
<object class="GtkTextView" id="txt_license">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="resize_mode">queue</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
<property name="justification">fill</property>
<property name="buffer">text_buffer_license</property>
<property name="accepts_tab">False</property>
</object>
@ -132,14 +132,14 @@ working long hours at the computer.</property>
</child>
<child>
<object class="GtkLinkButton" id="btn_url">
<property name="label" translatable="yes">https://github.com/slgobinath/SafeEyes</property>
<property name="label" translatable="yes">http://slgobinath.github.io/SafeEyes</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<property name="uri">https://github.com/slgobinath/SafeEyes</property>
<property name="uri">http://slgobinath.github.io/SafeEyes</property>
</object>
<packing>
<property name="expand">False</property>

View File

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkWindow" id="window_main">
<property name="can_focus">False</property>
<property name="window_position">center</property>
<property name="hide_titlebar_when_maximized">True</property>
<property name="icon_name">safeeyes</property>
<property name="skip_taskbar_hint">True</property>
<property name="urgency_hint">True</property>
<property name="focus_on_map">False</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="lbl_left">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="margin_left">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<style>
<class name="panel_left"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="row_spacing">10</property>
<child>
<object class="GtkGrid" id="grid_parent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="row_spacing">15</property>
<child>
<object class="GtkLabel" id="lbl_message">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hello World</property>
<property name="justify">center</property>
<style>
<class name="lbl_message"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">3</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="yscale">0.20000000298023224</property>
<child>
<object class="GtkLabel" id="lbl_count">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">00</property>
<style>
<class name="lbl_count"/>
</style>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box_buttons">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="spacing">50</property>
<property name="homogeneous">True</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="height">3</property>
</packing>
</child>
<child>
<object class="GtkImage" id="img_break">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_right">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<style>
<class name="panel_right"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<style>
<class name="window_main"/>
</style>
</object>
</interface>

View File

@ -21,6 +21,12 @@
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkAdjustment" id="adjust_idle_time_to_pause">
<property name="lower">1</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_interval_between_breaks">
<property name="lower">1</property>
<property name="upper">60</property>
@ -40,19 +46,25 @@
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_postpone_duration">
<property name="lower">1</property>
<property name="upper">15</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_short_break_duration">
<property name="lower">1</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_time_to_prepare">
<property name="lower">10</property>
<object class="GtkAdjustment" id="adjust_time_to_lock_screen">
<property name="lower">1</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_idle_time_to_pause">
<object class="GtkAdjustment" id="adjust_time_to_prepare">
<property name="lower">1</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
@ -79,7 +91,7 @@
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkGrid" id="layoutt_grid">
<object class="GtkGrid" id="layout_grid">
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
@ -167,6 +179,32 @@
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_postpone_duration">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Postpone duration (in minutes)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_show_time_in_tray">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Show the next break time in system tray icon</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_strict_break">
<property name="visible">True</property>
@ -175,37 +213,11 @@
<property name="valign">center</property>
<property name="label" translatable="yes">Strict break (hide skip button)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audible_alert">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Audible alert at the end of break</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_language">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Language</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_short_break_duration">
<property name="visible">True</property>
@ -319,9 +331,30 @@
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_strict_break">
<object class="GtkSpinButton" id="spin_postpone_duration">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="text" translatable="yes">1</property>
<property name="input_purpose">number</property>
<property name="adjustment">adjust_postpone_duration</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
<property name="update_policy">if-valid</property>
<property name="value">5</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_show_time_in_tray">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
</object>
<packing>
<property name="left_attach">1</property>
@ -329,20 +362,140 @@
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_audible_alert">
<object class="GtkSwitch" id="switch_strict_break">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_audible_alert">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audible_alert">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Audible alert at the end of break</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="cmb_language">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">13</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_language">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Language</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">13</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_enable_screen_lock">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Lock the screen after every long break</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_screen_lock">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_lock_screen_after">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Maximum time to skip, bypassing the lock screen (in seconds)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_time_to_screen_lock">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="text" translatable="yes">1</property>
<property name="input_purpose">number</property>
<property name="adjustment">adjust_time_to_lock_screen</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
<property name="update_policy">if-valid</property>
<property name="value">1</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_allow_postpone">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Allow postponing the breaks</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_postpone">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">8</property>

BIN
safeeyes/resource/alert.wav Normal file

Binary file not shown.

View File

@ -1,209 +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 <http://www.gnu.org/licenses/>.
import gi, logging, threading, datetime, Utility
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
# Global variables
APPINDICATOR_ID = 'safeeyes'
class TrayIcon:
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.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()
# Enable/Disable menu item
self.item_enable = Gtk.MenuItem()
self.item_enable.connect('activate', self.on_toogle_enable)
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_toogle_enable, 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_toogle_enable, -1)
self.sub_menu_disable.append(self.sub_menu_item_until_restart)
# Add the sub menu to the enable/disable menu
self.item_enable.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)
# 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_settings)
self.menu.append(self.item_about)
self.menu.append(self.item_quit)
self.menu.show_all()
self.indicator.set_menu(self.menu)
def set_labels(self, 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_info.set_label(self.language['messages']['disabled_until_restart'])
self.item_enable.set_label(self.language['ui_controls']['disable'])
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):
Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.ACTIVE)
def hide_icon(self):
Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.PASSIVE)
def quit_safe_eyes(self, *args):
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):
self.on_show_settings()
def show_about(self, *args):
self.on_show_about()
def next_break_time(self, dateTime):
logging.info("Update next break information")
self.dateTime = dateTime
self.set_next_break_info(self.dateTime)
def set_next_break_info(self, dateTime):
formatted_time = Utility.format_time(dateTime)
message = self.language['messages'][
'next_break_at'].format(formatted_time)
Utility.execute_main_thread(self.item_info.set_label, message)
def on_toogle_enable(self, *args):
# active = self.item_enable.get_active()
if args[0] == self.item_enable and 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.on_enable()
self.item_enable.set_label(self.language['ui_controls']['disable'])
self.item_enable.set_submenu(self.sub_menu_disable)
# Notify all schedulers
self.idle_condition.acquire()
self.idle_condition.notify_all()
self.idle_condition.release()
elif args[0] != self.item_enable and self.active:
logging.info('Disable Safe Eyes')
self.active = False
self.indicator.set_icon("safeeyes_disabled")
self.item_info.set_sensitive(False)
self.on_disable()
self.item_enable.set_label(self.language['ui_controls']['enable'])
self.item_enable.set_submenu(None)
time_to_wait = args[1]
if time_to_wait <= 0:
self.item_info.set_label(self.language['messages']['disabled_until_restart'])
else:
wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
Utility.start_thread(self.__schedule_resume, args={'time_minutes': time_to_wait})
self.item_info.set_label(self.language['messages']['disabled_until_x'].format(Utility.format_time(wakeup_time)))
def __schedule_resume(self, time_minutes):
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)

View File

@ -1,103 +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 <http://www.gnu.org/licenses/>.
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, GLib
import babel.dates, os, errno, subprocess, threading, logging, locale
"""
Play the alert.mp3
"""
def play_notification():
logging.info("Playing audible alert")
try:
subprocess.Popen(['mpg123', '-q', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resource/alert.mp3')])
except:
pass
"""
Get system idle time in minutes.
Return the idle time if xprintidle is available, otherwise return 0.
"""
def system_idle_time():
try:
return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 60000 # Convert to minutes
except:
return 0
"""
Execute the function in a separate thread.
"""
def start_thread(target_function, args=None):
thread = threading.Thread(target=target_function, kwargs=args)
thread.start()
"""
Execute the given function in main thread.
"""
def execute_main_thread(target_function, args=None):
if args:
GLib.idle_add(lambda: target_function(args))
else:
GLib.idle_add(lambda: target_function())
"""
Check for full-screen applications.
"""
def is_full_screen_app_found():
logging.info("Searching for full-screen application")
screen = Gdk.Screen.get_default()
active_xid = str(screen.get_active_window().get_xid())
cmdlist = ['xprop', '-root', '-notype','-id',active_xid, '_NET_WM_STATE']
try:
stdout = subprocess.check_output(cmdlist)
except subprocess.CalledProcessError:
logging.warning("Error in finding full-screen application")
pass
else:
if stdout:
return 'FULLSCREEN' in stdout
"""
Format time based on the system time.
"""
def format_time(time):
system_locale = locale.setlocale(locale.LC_ALL, '')
if not system_locale:
system_locale = 'en_US.UTF-8'
return babel.dates.format_time(time, format='short', locale=system_locale)
"""
Create directory if not exists.
"""
def mkdir(path):
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

View File

@ -1,46 +0,0 @@
{
"meta_info": {
"language_name": "Čeština",
"language_name_en": "Czech"
},
"exercises": {
"short_break_close_eyes": "Zavřete oči",
"short_break_roll_eyes": "Koulejte očima",
"short_break_rotate_clockwise": "Kružte očima ve směru hodinových ručiček",
"short_break_rotate_counter_clockwise": "Kružte očima proti směru hodinových ručiček",
"short_break_blink": "Mrkejte",
"short_break_focus_far_distance": "Soustřeďte se na bod v dálce",
"short_break_drink_water": "Dejte si nějakou vodu",
"long_break_walk": "Na chvíli se projděte",
"long_break_lean_back": "Opřete se do křesla a relaxujte"
},
"messages": {
"ready_for_a_break": "Připravte se na přestávku za {} sekund",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"next_break_at": "Příští přestávka v {}"
},
"ui_controls": {
"skip": "Přeskočit",
"short_break_duration": "Trvání krátké přestávky (v sekundách)",
"long_break_duration": "Trvání dlouhé přestávky (v sekundách)",
"interval_between_two_breaks": "Interval mezi dvěma přestávkami",
"no_of_short_breaks_between_two_long_breaks": "Počet krátkých přestávek mezi dvěma dlouhými přestávkami",
"time_to_prepare_for_break": "Čas k přípravě na přestávku (v sekundách)",
"idle_time": "Pozastavit při nečinnosti delší než (v minutách)",
"strict_break": "Povinná přestávka (Skrýt tlačítko pro přeskočení)",
"audible_alert": "Zvukové upozornění na konec přestávky",
"language": "Jazyk",
"enable": "Povolit Safe Eyes",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Nastavení",
"about": "O programu",
"quit": "Ukončit",
"save": "Uložit",
"cancel": "Zrušit"
}
}

View File

@ -1,46 +0,0 @@
{
"meta_info": {
"language_name": "Español",
"language_name_en": "Spanish"
},
"exercises": {
"short_break_close_eyes": "Cierra bien tus ojos",
"short_break_roll_eyes": "Mueve tus ojos en círculos",
"short_break_rotate_clockwise": "Mueve tus ojos en círculos en el sentido horario",
"short_break_rotate_counter_clockwise": "Mueve tus ojos en círculos en el sentido antihorario",
"short_break_blink": "Parpadea tus ojos",
"short_break_focus_far_distance": "Fijarse en un punto lejano",
"short_break_drink_water": "Bebe un poco de agua",
"long_break_walk": "Vete a andar un poco",
"long_break_lean_back": "Reclínate sobre tu silla y relájate"
},
"messages": {
"ready_for_a_break": "Listo para una pausa en {} segundos",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"next_break_at": "Próxima pausa a las {}"
},
"ui_controls": {
"skip": "Cancelar",
"short_break_duration": "Duración de una pausa corta (en segundos)",
"long_break_duration": "Duración de una pausa larga (en segundos)",
"interval_between_two_breaks": "Tiempo entre dos pausas",
"no_of_short_breaks_between_two_long_breaks": "Número de pausas cortas entre dos pausas largas",
"time_to_prepare_for_break": "Tiempo para prepararse para una pausa (en segundos)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Pausa estricta (Sin botón Cancelar)",
"audible_alert": "Audible alert at the end of break",
"language": "Idioma",
"enable": "Activar Safe Eyes",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Preferencias",
"about": "Acerca de",
"quit": "Salir",
"save": "Guardar",
"cancel": "Cancelar"
}
}

View File

@ -1,46 +0,0 @@
{
"meta_info": {
"language_name": "Français",
"language_name_en": "French"
},
"exercises": {
"short_break_close_eyes": "Fermez bien vos yeux",
"short_break_roll_eyes": "Roulez vos yeux",
"short_break_rotate_clockwise": "Faites rouler vos yeux dans le sens des aiguilles d'une montre",
"short_break_rotate_counter_clockwise": "Faites rouler vos yeux dans le sens contraire des aiguilles d'une montre",
"short_break_blink": "Clignez des yeux",
"short_break_focus_far_distance": "Focus sur un point dans la distance lointaine",
"short_break_drink_water": "Buvez de l'eau",
"long_break_walk": "Allez marcher un peu",
"long_break_lean_back": "Reposez-vous un moment dans votre siège"
},
"messages": {
"ready_for_a_break": "Prêt pour une pause dans {} secondes",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"next_break_at": "Prochaine pause à {}"
},
"ui_controls": {
"skip": "Annuler",
"short_break_duration": "Durée d'une pause courte (en secondes)",
"long_break_duration": "Durée d'une pause longue (en secondes)",
"interval_between_two_breaks": "Intervalle entre deux pauses",
"no_of_short_breaks_between_two_long_breaks": "Nombre de pauses courtes entre deux pauses longues",
"time_to_prepare_for_break": "Temps pour se préparer avant une pause (en secondes)",
"idle_time": "Minimum idle time to pause (in minutes)",
"strict_break": "Pause non annulable (Cacher le bouton Annuler)",
"audible_alert": "Audible alert at the end of break",
"language": "La language",
"enable": "Activer Safe Eyes",
"disable": "Disable Safe Eyes",
"for_x_minutes": "For {} Minutes",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"until_restart": "Until restart",
"settings": "Réglages",
"about": "À propos",
"quit": "Quitter",
"save": "Enregistrer",
"cancel": "Annuler"
}
}

View File

@ -1,67 +0,0 @@
{
"meta": {
"config_version": 3
},
"break_interval": 15,
"long_break_duration": 60,
"no_of_short_breaks_per_long_break": 5,
"pre_break_warning_time": 10,
"short_break_duration": 15,
"idle_time": 5,
"strict_break": false,
"audible_alert": false,
"language": "en",
"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": "short_break_roll_eyes"
},
{
"name": "short_break_rotate_clockwise"
},
{
"name": "short_break_rotate_counter_clockwise"
},
{
"name": "short_break_blink"
},
{
"name": "short_break_focus_far_distance"
},
{
"name": "short_break_drink_water"
}
],
"long_breaks": [
{
"name": "long_break_walk"
},
{
"name": "long_break_lean_back"
}
]
}

View File

@ -1,118 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkWindow" id="window_main">
<property name="can_focus">False</property>
<!-- <property name="type">popup</property> -->
<property name="window_position">center</property>
<property name="hide_titlebar_when_maximized">True</property>
<property name="icon_name">safeeyes</property>
<property name="skip_taskbar_hint">True</property>
<property name="urgency_hint">True</property>
<property name="focus_on_map">False</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkGrid" id="grid_parent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="row_spacing">5</property>
<property name="row_homogeneous">True</property>
<child>
<object class="GtkLabel" id="lbl_message">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hello World</property>
<style>
<class name="lbl_message"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">3</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="yscale">0.20000000298023224</property>
<child>
<object class="GtkLabel" id="lbl_count">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">00</property>
<style>
<class name="lbl_count"/>
</style>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_skip">
<property name="label" translatable="yes">Skip</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_skip_clicked" swapped="no"/>
<style>
<class name="btn_skip"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<style>
<class name="window_main"/>
</style>
</object>
</interface>

Binary file not shown.

View File

@ -1,289 +0,0 @@
#!/usr/bin/env python2
# 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 <http://www.gnu.org/licenses/>.
import os, gi, json, shutil, dbus, logging, operator, Utility
from threading import Timer
from dbus.mainloop.glib import DBusGMainLoop
from BreakScreen import BreakScreen
from TrayIcon import TrayIcon
from SettingsDialog import SettingsDialog
from AboutDialog import AboutDialog
from SafeEyesCore import SafeEyesCore
from Notification import Notification
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Define necessary paths
bin_directory = os.path.dirname(os.path.realpath(__file__))
config_file_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/safeeyes.json')
style_sheet_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/style/safeeyes_style.css')
log_file_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/safeeyes.log')
break_screen_glade = os.path.join(bin_directory, "glade/break_screen.glade")
settings_dialog_glade = os.path.join(bin_directory, "glade/settings_dialog.glade")
about_dialog_glade = os.path.join(bin_directory, "glade/about_dialog.glade")
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")
system_language_directory = os.path.join(bin_directory, "config/lang")
is_active = True
CONFIGURATION_VERSION = 3
SAFE_EYES_VERSION = "1.1.4"
"""
Listen to tray icon Settings action and send the signal to Settings dialog.
"""
def show_settings():
logging.info("Show Settings dialog")
settings_dialog = SettingsDialog(config, language, read_lang_files(), save_settings, settings_dialog_glade)
settings_dialog.show()
"""
Listen to tray icon About action and send the signal to About dialog.
"""
def show_about():
logging.info("Show About dialog")
about_dialog = AboutDialog(about_dialog_glade, SAFE_EYES_VERSION)
about_dialog.show()
"""
Receive the signal from core and pass it to the Notification.
"""
def show_notification():
notification.show(config['pre_break_warning_time'])
"""
Receive the break signal from core and pass it to the break screen.
"""
def show_alert(message):
logging.info("Show the break screen")
notification.close()
break_screen.show_message(message)
"""
Receive the stop break signal from core and pass it to the break screen.
"""
def close_alert():
logging.info("Close the break screen")
break_screen.close()
if config['audible_alert']:
Utility.play_notification()
"""
Receive the count from core and pass it to the break screen.
"""
def on_countdown(count):
break_screen.show_count_down(count)
"""
Listen to the tray menu quit action and stop the core, notification and the app itself.
"""
def on_quit():
logging.info("Quit Safe Eyes")
core.stop()
notification.quite();
Gtk.main_quit()
"""
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.
"""
def handle_suspend_callback(sleeping):
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")
"""
Setup system suspend listener.
"""
def handle_system_suspend():
DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus.add_signal_receiver(handle_suspend_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
"""
Listen to break screen Skip action and send the signal to core.
"""
def on_skipped():
logging.info("User skipped the break")
core.skip_break()
"""
Listen to Settings dialog Save action and write to the config file.
"""
def save_settings(config):
global language
logging.info("Saving settings to safeeyes.json")
# Stop the Safe Eyes core
if is_active:
core.stop()
# Write the configuration to file
with open(config_file_path, 'w') as config_file:
json.dump(config, config_file, indent=4, sort_keys=True)
# Reload the language translation
language_file_path = os.path.join(system_language_directory, str(config['language']) + '.json')
with open(language_file_path) as language_file:
language = json.load(language_file)
tray_icon.set_labels(language)
logging.info("Initialize SafeEyesCore with modified settings")
# Restart the core and intialize the components
core.initialize(config, language)
break_screen.initialize(config, language)
if is_active:
# 1 sec delay is required to give enough time for core to be stopped
Timer(1.0, core.start).start()
"""
Listen to tray icon enable action and send the signal to core.
"""
def enable_safeeyes():
global is_active
is_active = True
core.start()
"""
Listen to tray icon disable action and send the signal to core.
"""
def disable_safeeyes():
global is_active
is_active = False
core.stop()
"""
Initialize the configuration directory and copy the files to ~/.config directory.
"""
def initialize_config():
global config
config_dir_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/style')
startup_dir_path = os.path.join(os.path.expanduser('~'), '.config/autostart')
Utility.mkdir(config_dir_path)
if not os.path.isfile(config_file_path):
shutil.copy2(system_config_file_path, config_file_path)
Utility.mkdir(startup_dir_path)
# Add to startup for the first time only
try:
os.symlink("/usr/share/applications/safeeyes.desktop", os.path.join(startup_dir_path, "safeeyes.desktop"))
except OSError as exc:
pass
if not os.path.isfile(style_sheet_path):
shutil.copy2(system_style_sheet_path, style_sheet_path)
# Read the configuration
with open(config_file_path) as config_file:
config = json.load(config_file)
"""
Configuration file has a version config_version.
It is used to overwrite the exsiting config file if there is an update.
Earlier versions did not have this attribute so the following method
checks the version and if it mismatches, it will overwrite the exsiting
config files. If the version property is not available, the file is
considered as an older one and replaced by the new configuration file.
"""
def validate_config():
version_mismatch = False
try:
# Check the config version
config_version = config['meta']['config_version']
version_mismatch = config_version is not CONFIGURATION_VERSION
except:
version_mismatch = True
if version_mismatch:
# Remove ~/.config/safeeyes directory
try:
shutil.rmtree(os.path.join(os.path.expanduser('~'), '.config/safeeyes'), ignore_errors=False)
except:
pass
# Remove startup script
try:
os.remove(os.path.join(os.path.expanduser('~'), '.config/autostart/safeeyes.desktop'))
except:
pass
# Create config files again
initialize_config()
"""
Read all the language translations and build a key-value mapping of language names
in English and ISO 639-1 (Filename without extension).
"""
def read_lang_files():
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']
return languages
def main():
initialize_config()
# Configure logging. Reset with every restart
logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', filename=log_file_path, filemode='w', level=logging.INFO)
logging.info("Starting Safe Eyes")
validate_config()
global break_screen
global core
global notification
global tray_icon
global language
language_file_path = os.path.join(system_language_directory, str(config['language']) + '.json')
with open(language_file_path) as language_file:
language = json.load(language_file)
tray_icon = TrayIcon(config, language, show_settings, show_about, enable_safeeyes, disable_safeeyes, on_quit)
break_screen = BreakScreen(on_skipped, break_screen_glade, style_sheet_path)
break_screen.initialize(config, language)
core = SafeEyesCore(show_notification, show_alert, close_alert, on_countdown, tray_icon.next_break_time)
core.initialize(config, language)
core.start()
notification = Notification(language)
handle_system_suspend()
Gtk.main()
main()

View File

@ -1,16 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Name=Safe Eyes
Comment=Protect your eyes from eye strain
Comment[de]=Schützt die Augen vor Überanstrengung
Comment[cs]=Chraňte své oči před únavou
Comment[fr]=Protégez vos yeux de la fatigue
Comment[ta]=உங்கள் கண்களை சோர்வடையாது பாதுகாத்திடுங்கள்
Comment[pt]=Proteja seus olhos da tensão ocular
Comment[tr]=Gözünüzü yorgunluğa karşı koruyun
Exec=/opt/safeeyes/safeeyes
Icon=safeeyes
Version=1.1.4
Terminal=false
Type=Application
Categories=Utility;

2
setup.cfg Normal file
View File

@ -0,0 +1,2 @@
[metadata]
description-file = README.md

63
setup.py Normal file
View File

@ -0,0 +1,63 @@
import os
import setuptools
requires = [
'python-xlib',
'pyaudio',
'psutil',
'babel']
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.md')) as f:
long_description = '\n' + f.read()
def _data_files(path):
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])
setuptools.setup(
name="safeeyes",
version="1.2.1",
description="Protect your eyes from eye strain using this continuous breaks reminder.",
long_description=long_description,
author="Gobinath Loganathan",
author_email="slgobinath@gmail.com",
url="https://github.com/slgobinath/SafeEyes",
download_url="https://github.com/slgobinath/SafeEyes/archive/v1.2.1.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'])
],
install_requires=requires,
entry_points={'console_scripts': ['safeeyes = safeeyes.__main__:main']},
keywords='linux utility health eye-strain safe-eyes',
classifiers=[
"Operating System :: POSIX :: Linux",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Development Status :: 3 - Alpha",
"Environment :: X11 Applications :: GTK",
"Intended Audience :: End Users/Desktop",
"Topic :: Utilities"] + [
('Programming Language :: Python :: %s' % x) for x in
'3 3.4 3.5 3.6'.split()]
)

View File

@ -0,0 +1,21 @@
[Desktop Entry]
Name=Safe Eyes
Comment=Protect your eyes from eye strain
Comment[ge]=დაიცავით თქვენი თვალები დაღლილობისაგან
Comment[de]=Schützt die Augen vor Überanstrengung
Comment[cs]=Chraňte své oči před únavou
Comment[fr]=Protégez vos yeux de la fatigue
Comment[id]=Melindungi mata Anda dari kelelahan
Comment[ta]=உங்கள் கண்களை சோர்வடையாது பாதுகாத்திடுங்கள்
Comment[pt]=Proteja seus olhos da tensão ocular
Comment[tr]=Gözünüzü yorgunluğa karşı koruyun
Comment[hi]=तनाव से आंखों की रक्षा
Comment[es]=Protege tus ojos de la fatiga ocular
Comment[ru]=Защитите свои глаза от зрительного перенапряжения
Comment[pl]=Chroń oczy przed zmęczeniem
Comment[mk]=Заштитете се од замор на очите
Exec=env GDK_BACKEND=x11 safeeyes
Icon=safeeyes
Terminal=false
Type=Application
Categories=Utility;

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 649 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 902 B

After

Width:  |  Height:  |  Size: 902 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB