Safeeyes 2.0.0 (#194)

This commit is contained in:
Gobinath 2017-10-07 09:10:31 -04:00 committed by GitHub
parent 4b5f13878e
commit 5de6869309
79 changed files with 6565 additions and 4179 deletions

8
.gitignore vendored
View File

@ -6,6 +6,9 @@ __pycache__/
# C extensions
*.so
# Glade temporary files
*.glade~
# Distribution / packaging
.Python
env/
@ -91,4 +94,7 @@ ENV/
.spyderproject
# Rope project settings
.ropeproject
.ropeproject
# Visual Studio Code settings
.vscode/

127
README.md
View File

@ -1,9 +1,4 @@
# Safe Eyes
[![GitHub version](https://badge.fury.io/gh/slgobinath%2FSafeEyes.svg)](https://badge.fury.io/gh/slgobinath%2FSafeEyes)
[![PyPI version](https://badge.fury.io/py/safeeyes.svg)](https://badge.fury.io/py/safeeyes)
[![Translation status](https://hosted.weblate.org/widgets/safe-eyes/-/translations/svg-badge.svg)](https://hosted.weblate.org/engage/safe-eyes/?utm_source=widget)
Protect your eyes from eye strain using this simple and beautiful, yet extensible break reminder. A Free and Open Source Linux alternative to EyeLeo.
Visit to the official site: http://slgobinath.github.io/SafeEyes/ for more details.
@ -17,87 +12,65 @@ Ensure to meet the following dependencies when compiling from source:
- gir1.2-appindicator3-0.1
- gir1.2-notify-0.7
- libappindicator-gtk3
- python3-pyaudio
- python3-psutil
- xprintidle (optional)
## Customizing options
One of the key advantage of Safe Eyes over other similar products is its highly customizable design. You can change almost everything in Safe Eyes. A detailed documentation is available in the official site: [Customize Safe Eyes](http://slgobinath.github.io/SafeEyes/#customize)
## Writing Plug-in for Safe Eyes
A plugin is a combination of two files: `plugin.py` and `config.json`. These two files must be placed in a directory: `~/.config/safeeyes/plugins/<plugin-id>`. Optionally a plugin also can have an image file `icon.png` which is used to represent the plugin in the Settings dialog.
## Contribute
I started this project for my own use and later released it as an open source alternative to EyeLeo and progressively reached to the current state with the great support of open source community. Most of the creative ideas were suggested and implemented by users. You can always add more to Safe Eyes. I have listed some possible ways here: [How to contribute](http://slgobinath.github.io/SafeEyes/#contribute)
For example, a Weather plugin may have the following file structure:
```
~
└── .config
└── safeeyes
└── plugins
└── weather
├── config.json
├── icon.png
└── plugin.py
```
## Features
The `icon.png` must be `24x24` pixels size. If `icon.png` is not available, the default gear icon <img src="https://github.com/slgobinath/SafeEyes/raw/safeeyes-2.0.0/safeeyes/resource/ic_plugin.png" width="16" height="16"/> will be shown in the Settings dialog.
General Features:
A sample `config.json` is provided below:
```json
{
"meta": {
"name": "Weather",
"description": "Show the current weather on break screen",
"version": "0.0.1"
},
"dependencies": {
"python_modules": ["pyowm"],
"shell_commands": [],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": [
{
"id": "api",
"label": "OpenWeatherMap API Key",
"type": "TEXT",
"default": ""
},
{
"id": "location",
"label": "Location",
"type": "TEXT",
"default": ""
}
],
"break_override_allowed": true
}
```
- Short breaks with eye exercises
- Long breaks to change physical position and to warm up
- Disable the keyboard during break
- Notifications before every break
- Do not disturb when working with full-screen applications( Eg: Watching movies)
- Smart pause and resume based on system idle time (Require `xprintidle`)
- Multi-monitor support
- Multi-language support
- Elegant and customizable design
The `meta` properties must provide the name of the plugin, a short description and the current version of the plugin.
Optional Features:
- Strict break for those who are addicted to computer
- Postpone break
- Skip or take break based on active windows (Regardless of full-screen-mode)
- Customize individual break time
- Define your own custom exercise
- Audible alert at the end of break
- Turn on/off audible alert for individual breaks
- Customize disable time period
- Lock screen after long breaks
- Add images to breaks
- Plug-in support to extend Safe Eyes
For more details: [SafeEyes Features](http://slgobinath.github.io/SafeEyes/#features)
## Currently available translations
* [Català](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ca.json)
* [Čeština](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/cs.json)
* [Deutsch](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/de.json)
* [English](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/en.json)
* [Español](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/es.json)
* [فارسی](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/fa.json)
* [Français](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/fr.json)
* [ქართული](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ge.json)
* [हिंदी](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/hi.json)
* [Magyar](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/hu.json)
* [Bahasa Indonesia](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/id.json)
* [Македонски](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/mk.json)
* [Polski](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/pl.json)
* [Português](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/pt.json)
* [Русский](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ru.json)
* [Slovenský](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/sk.json)
* [தமிழ்](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/ta.json)
* [Türkçe](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/tr.json)
* [Українська](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/uk.json)
* [Tiếng Việt](https://github.com/slgobinath/SafeEyes/tree/master/safeeyes/config/lang/vi.json)
Do you want to see your language here? Please translate Safe Eyes to whatever the languages you know. Visit to **Translate Safe Eyes** in [Customize Safe Eyes](http://slgobinath.github.io/SafeEyes/#customize) to see how to translate.
## Tested Environments
Core functionalities of Safe Eyes are tested by the developer in the following environments:
* Antergos 17.4
* Elementary OS Loki
* Fedora 25
* Kubuntu 17.04
* Linux Mint 18.1
* Manjaro 16.10.3
* Ubuntu 14.04
* Ubuntu 16.04
* Ubuntu 16.10
* Ubuntu Budgie 17.04
* Ubuntu Mate 16.04
* Xubuntu 16.10
The `dependencies` property defines various dependency constraints of the plugin. The dependencies can be Python modules, commandline tools, desktop environments or Safe Eyes resources. The `operating_systems` property is reserved for operating system dependency but not checked for at the moment.
If a dependency is not available, the Safe Eyes will not load the plugin. The Settings dialog will show a warning symbol <img src="https://github.com/slgobinath/SafeEyes/raw/safeeyes-2.0.0/safeeyes/resource/ic_warning.png" width="16" height="16"> and a message to install/check the missing dependencies. Dependencies are checked in the order of *Desktop Environment*, *Python Modules*, *Commandline Tools* and *Resources*. If a dependency is not available, Safe Eyes will stop looking for the rest.
The configurations related to the plugin must be defined in `settings`. Each setting must have an `id`, `label`, `type` and a default value matching the `type`. Safe Eyes 2.0.0 supports only the following types: `INT`, `TEXT` and `BOOL`. According to the types, Settings dialog will show a *Spin*, *Text Field* or *Switch Button* as the input field.
## License
GNU General Public License v3

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
@ -15,44 +16,47 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module creates the AboutDialog which shows the version and license.
"""
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import os
from safeeyes import Utility
ABOUT_DIALOG_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/about_dialog.glade")
class AboutDialog:
"""
AboutDialog reads the about_dialog.glade and build the user interface using that file.
It shows the application name with version, a small description, license and the GitHub url.
"""
class AboutDialog(object):
"""
AboutDialog reads the about_dialog.glade and build the user interface using that file.
It shows the application name with version, a small description, license and the GitHub url.
"""
def __init__(self, glade_file, version, language):
builder = Gtk.Builder()
builder.add_from_file(glade_file)
builder.connect_signals(self)
self.window = builder.get_object("window_about")
builder.get_object('lbl_decription').set_label(language['app_info']['description'])
builder.get_object('lbl_license').set_label(str(language['ui_controls']['license']) + ':')
builder.get_object('btn_close').set_label(language['ui_controls']['close'])
def __init__(self, version):
builder = Utility.create_gtk_builder(ABOUT_DIALOG_GLADE)
builder.connect_signals(self)
self.window = builder.get_object('window_about')
builder.get_object('lbl_decription').set_label(_('description'))
builder.get_object('lbl_license').set_label(_('License') + ':')
# Set the version at the runtime
builder.get_object("lbl_app_name").set_label("Safe Eyes " + version)
# Set the version at the runtime
builder.get_object('lbl_app_name').set_label('Safe Eyes ' + version)
def show(self):
"""
Show the About dialog.
"""
self.window.show_all()
def show(self):
"""
Show the About dialog.
"""
self.window.show_all()
def on_window_delete(self, *args):
"""
Window close event handler.
"""
self.window.destroy()
def on_window_delete(self, *args):
"""
Window close event handler.
"""
self.window.destroy()
def on_close_clicked(self, button):
"""
Close button click event handler.
"""
self.window.destroy()
def on_close_clicked(self, *args):
"""
Close button click event handler.
"""
self.window.destroy()

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
@ -16,227 +17,241 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gi, threading, logging, time
from Xlib.display import Display, X
import logging
import os
import threading
import time
import gi
from safeeyes import Utility
from Xlib.display import Display
from Xlib.display import X
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Gtk
BREAK_SCREEN_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/break_screen.glade")
class BreakScreen:
"""
The fullscreen window which prevents users from using the computer.
This class reads the break_screen.glade and build the user interface.
"""
class BreakScreen(object):
"""
The fullscreen window which prevents users from using the computer.
This class reads the break_screen.glade and build the user interface.
"""
def __init__(self, context, on_skip, on_postpone, glade_file, style_sheet_path):
self.context = context
self.on_skip = on_skip
self.on_postpone = on_postpone
self.is_pretified = False
self.windows = []
self.count_labels = []
self.glade_file = glade_file
self.enable_shortcut = False
self.display = Display()
def __init__(self, context, on_skip, on_postpone, style_sheet_path):
self.context = context
self.count_labels = []
self.display = Display()
self.enable_postpone = False
self.enable_shortcut = False
self.is_pretified = False
self.keycode_shortcut_postpone = 65
self.keycode_shortcut_skip = 9
self.on_postpone = on_postpone
self.on_skip = on_skip
self.shortcut_disable_time = 2
self.strict_break = False
self.windows = []
# Initialize the theme
css_provider = Gtk.CssProvider()
css_provider.load_from_path(style_sheet_path)
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
# Initialize the theme
css_provider = Gtk.CssProvider()
css_provider.load_from_path(style_sheet_path)
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def initialize(self, config, language):
"""
Initialize the internal properties from configuration
"""
logging.info("Initialize the break screen")
self.skip_button_text = language['ui_controls']['skip']
self.postpone_button_text = language['ui_controls']['postpone']
self.strict_break = config.get('strict_break', False)
self.enable_postpone = config.get('allow_postpone', False)
self.keycode_shortcut_skip = config.get('shortcut_skip', 9)
self.keycode_shortcut_postpone = config.get('shortcut_postpone', 65)
self.shortcut_disable_time = config.get('shortcut_disable_time', 2)
def initialize(self, config):
"""
Initialize the internal properties from configuration
"""
logging.info("Initialize the break screen")
self.enable_postpone = config.get('allow_postpone', False)
self.keycode_shortcut_postpone = config.get('shortcut_postpone', 65)
self.keycode_shortcut_skip = config.get('shortcut_skip', 9)
self.shortcut_disable_time = config.get('shortcut_disable_time', 2)
self.strict_break = config.get('strict_break', False)
def skip_break(self):
"""
Skip the break from the break screen
"""
logging.info("User skipped the break")
# Must call on_skip before close to lock screen before closing the break screen
self.on_skip()
self.close()
def skip_break(self):
"""
Skip the break from the break screen
"""
logging.info("User skipped the break")
# Must call on_skip before close to lock screen before closing the break screen
self.on_skip()
self.close()
def postpone_break(self):
"""
Postpone the break from the break screen
"""
logging.info("User postponed the break")
self.on_postpone()
self.close()
def postpone_break(self):
"""
Postpone the break from the break screen
"""
logging.info("User postponed the break")
self.on_postpone()
self.close()
def on_window_delete(self, *args):
"""
Window close event handler.
"""
logging.info("Closing the break screen")
self.__release_keyboard()
self.close()
def on_window_delete(self, *args):
"""
Window close event handler.
"""
logging.info("Closing the break screen")
self.__release_keyboard()
self.close()
def on_skip_clicked(self, button):
"""
Skip button press event handler.
"""
self.skip_break()
def on_skip_clicked(self, button):
"""
Skip button press event handler.
"""
self.skip_break()
def on_postpone_clicked(self, button):
"""
Postpone button press event handler.
"""
self.postpone_break()
def on_postpone_clicked(self, button):
"""
Postpone button press event handler.
"""
self.postpone_break()
def show_count_down(self, count_down, seconds):
"""
Show/update the count down on all screens.
"""
self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= count_down
mins, secs = divmod(seconds, 60)
timeformat = '{:02d}:{:02d}'.format(mins, secs)
GLib.idle_add(lambda: self.__update_count_down(timeformat))
def show_count_down(self, countdown, seconds):
"""
Show/update the count down on all screens.
"""
self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= seconds
mins, secs = divmod(countdown, 60)
timeformat = '{:02d}:{:02d}'.format(mins, secs)
GLib.idle_add(lambda: self.__update_count_down(timeformat))
def show_message(self, message, image_path, plugins_data):
"""
Show the break screen with the given message on all displays.
"""
self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= 0
GLib.idle_add(lambda: self.__show_break_screen(message, image_path, plugins_data))
def show_message(self, break_obj, widget):
"""
Show the break screen with the given message on all displays.
"""
message = break_obj.name
image_path = break_obj.image
self.enable_shortcut = not self.strict_break and self.shortcut_disable_time <= 0
GLib.idle_add(lambda: self.__show_break_screen(message, image_path, widget))
def close(self):
"""
Hide the break screen from active window and destroy all other windows
"""
logging.info("Close the break screen(s)")
self.__release_keyboard()
def close(self):
"""
Hide the break screen from active window and destroy all other windows
"""
logging.info("Close the break screen(s)")
self.__release_keyboard()
# Destroy other windows if exists
GLib.idle_add(lambda: self.__destroy_all_screens())
# Destroy other windows if exists
GLib.idle_add(lambda: self.__destroy_all_screens())
def __show_break_screen(self, message, image_path, plugins_data):
"""
Show an empty break screen on all screens.
"""
# Lock the keyboard
thread = threading.Thread(target=self.__lock_keyboard)
thread.start()
def __show_break_screen(self, message, image_path, widget):
"""
Show an empty break screen on all screens.
"""
# Lock the keyboard
thread = threading.Thread(target=self.__lock_keyboard)
thread.start()
logging.info("Show break screens in all displays")
screen = Gtk.Window().get_screen()
no_of_monitors = screen.get_n_monitors()
logging.info("Show break screens in all displays")
screen = Gtk.Window().get_screen()
no_of_monitors = screen.get_n_monitors()
for monitor in range(no_of_monitors):
monitor_gemoetry = screen.get_monitor_geometry(monitor)
x = monitor_gemoetry.x
y = monitor_gemoetry.y
for monitor in range(no_of_monitors):
monitor_gemoetry = screen.get_monitor_geometry(monitor)
x = monitor_gemoetry.x
y = monitor_gemoetry.y
builder = Gtk.Builder()
builder.add_from_file(self.glade_file)
builder.connect_signals(self)
builder = Gtk.Builder()
builder.add_from_file(BREAK_SCREEN_GLADE)
builder.connect_signals(self)
window = builder.get_object("window_main")
lbl_message = builder.get_object("lbl_message")
lbl_count = builder.get_object("lbl_count")
lbl_left = builder.get_object("lbl_left")
lbl_right = builder.get_object("lbl_right")
img_break = builder.get_object("img_break")
box_buttons = builder.get_object("box_buttons")
window = builder.get_object("window_main")
lbl_message = builder.get_object("lbl_message")
lbl_count = builder.get_object("lbl_count")
lbl_widget = builder.get_object("lbl_widget")
img_break = builder.get_object("img_break")
box_buttons = builder.get_object("box_buttons")
# Add the buttons
if not self.strict_break:
# Add postpone button
if self.enable_postpone:
btn_postpone = Gtk.Button(self.postpone_button_text)
btn_postpone.get_style_context().add_class('btn_postpone')
btn_postpone.connect('clicked', self.on_postpone_clicked)
btn_postpone.set_visible(True)
box_buttons.pack_start(btn_postpone, True, True, 0)
# Add the buttons
if not self.strict_break:
# Add postpone button
if self.enable_postpone:
btn_postpone = Gtk.Button(_('Postpone'))
btn_postpone.get_style_context().add_class('btn_postpone')
btn_postpone.connect('clicked', self.on_postpone_clicked)
btn_postpone.set_visible(True)
box_buttons.pack_start(btn_postpone, True, True, 0)
# Add the skip button
btn_skip = Gtk.Button(self.skip_button_text)
btn_skip.get_style_context().add_class('btn_skip')
btn_skip.connect('clicked', self.on_skip_clicked)
btn_skip.set_visible(True)
box_buttons.pack_start(btn_skip, True, True, 0)
# Add the skip button
btn_skip = Gtk.Button(_('Skip'))
btn_skip.get_style_context().add_class('btn_skip')
btn_skip.connect('clicked', self.on_skip_clicked)
btn_skip.set_visible(True)
box_buttons.pack_start(btn_skip, True, True, 0)
# Set values
if image_path:
img_break.set_from_file(image_path)
lbl_message.set_label(message)
lbl_left.set_markup(plugins_data['left'])
lbl_right.set_markup(plugins_data['right'])
# Set values
if image_path:
img_break.set_from_file(image_path)
lbl_message.set_label(message)
lbl_widget.set_markup(widget)
self.windows.append(window)
self.count_labels.append(lbl_count)
self.windows.append(window)
self.count_labels.append(lbl_count)
# Set visual to apply css theme. It should be called before show method.
window.set_visual(window.get_screen().get_rgba_visual())
if self.context['desktop'] == 'kde':
# Fix flickering screen in KDE by setting opacity to 1
window.set_opacity(0.9)
# Set visual to apply css theme. It should be called before show method.
window.set_visual(window.get_screen().get_rgba_visual())
if self.context['desktop'] == 'kde':
# Fix flickering screen in KDE by setting opacity to 1
window.set_opacity(0.9)
window.stick()
window.set_keep_above(True)
window.present()
window.move(x, y)
window.fullscreen()
window.stick()
window.set_keep_above(True)
window.present()
window.move(x, y)
window.fullscreen()
def __update_count_down(self, count):
"""
Update the countdown on all break screens.
"""
for label in self.count_labels:
label.set_text(count)
def __update_count_down(self, count):
"""
Update the countdown on all break screens.
"""
for label in self.count_labels:
label.set_text(count)
def __lock_keyboard(self):
"""
Lock the keyboard to prevent the user from using keyboard shortcuts
"""
logging.info("Lock the keyboard")
self.lock_keyboard = True
def __lock_keyboard(self):
"""
Lock the keyboard to prevent the user from using keyboard shortcuts
"""
logging.info("Lock the keyboard")
self.lock_keyboard = True
# Grab the keyboard
root = self.display.screen().root
root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask)
root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
# Grab the keyboard
root = self.display.screen().root
root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask)
root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
# Consume keyboard events
while self.lock_keyboard:
if self.display.pending_events() > 0:
# Avoid waiting for next event by checking pending events
event = self.display.next_event()
if self.enable_shortcut and event.type == X.KeyPress:
if event.detail == self.keycode_shortcut_skip:
self.skip_break()
break
elif self.enable_postpone and event.detail == self.keycode_shortcut_postpone:
self.postpone_break()
break
else:
# Reduce the CPU usage by sleeping for a second
time.sleep(1)
# Consume keyboard events
while self.lock_keyboard:
if self.display.pending_events() > 0:
# Avoid waiting for next event by checking pending events
event = self.display.next_event()
if self.enable_shortcut and event.type == X.KeyPress:
if event.detail == self.keycode_shortcut_skip:
self.skip_break()
break
elif self.enable_postpone and event.detail == self.keycode_shortcut_postpone:
self.postpone_break()
break
else:
# Reduce the CPU usage by sleeping for a second
time.sleep(1)
def __release_keyboard(self):
"""
Release the locked keyboard.
"""
logging.info("Unlock the keyboard")
self.lock_keyboard = False
self.display.ungrab_keyboard(X.CurrentTime)
self.display.flush()
def __release_keyboard(self):
"""
Release the locked keyboard.
"""
logging.info("Unlock the keyboard")
self.lock_keyboard = False
self.display.ungrab_keyboard(X.CurrentTime)
self.display.flush()
def __destroy_all_screens(self):
"""
Close all the break screens.
"""
for win in self.windows:
win.destroy()
del self.windows[:]
del self.count_labels[:]
def __destroy_all_screens(self):
"""
Close all the break screens.
"""
for win in self.windows:
win.destroy()
del self.windows[:]
del self.count_labels[:]

View File

@ -1,80 +0,0 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2016 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gi, logging
gi.require_version('Notify', '0.7')
from gi.repository import Notify
from safeeyes import Utility
APPINDICATOR_ID = 'safeeyes'
class Notification:
"""
This class is responsible for the notification to the user before the break.
"""
def __init__(self, context, language):
logging.info('Initialize the notification')
Notify.init(APPINDICATOR_ID)
self.context = context
self.language = language
def initialize(self, language):
"""
Initialize the notification object.
"""
self.language = language
def show(self, warning_time):
"""
Show the notification
"""
logging.info('Show pre-break notification')
# Construct the message based on the type of the next break
message = '\n'
if self.context['break_type'] == 'short':
message += self.language['messages']['ready_for_a_short_break'].format(warning_time)
else:
message += self.language['messages']['ready_for_a_long_break'].format(warning_time)
self.notification = Notify.Notification.new('Safe Eyes', message, icon='safeeyes_enabled')
try:
self.notification.show()
except Exception as e:
logging.exception('Error in showing notification', e)
def close(self):
"""
Close the notification if it is not closed by the system already.
"""
logging.info('Close pre-break notification')
try:
self.notification.close()
except:
# Some Linux systems automatically close the notification.
pass
def quite(self):
"""
Uninitialize the notification. Call this method when closing the application.
"""
logging.info('Uninitialize Safe Eyes notification')
Utility.execute_main_thread(Notify.uninit)

304
safeeyes/PluginManager.py Normal file
View File

@ -0,0 +1,304 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
PluginManager loads all enabled plugins and call their lifecycle methods.
A plugin must have the following directory structure:
<plugin-id>
|- config.json
|- plugin.py
|- icon.png (Optional)
The plugin.py can have following methods but all are optional:
- description()
If a custom description has to be displayed, use this function
- on_init(context, safeeyes_config, plugin_config)
Initialize the plugin. Will be called after loading and after every changes in configuration
- on_start()
Executes when Safe Eyes is enabled
- on_stop()
Executes when Safe Eyes is disabled
- enable()
Executes once the plugin.py is loaded as a module
- disable()
Executes if the plugin is disabled at the runtime by the user
- on_exit()
Executes before Safe Eyes exits
"""
import importlib
import inspect
import logging
import os
import sys
from safeeyes import Utility
sys.path.append(os.path.abspath(Utility.SYSTEM_PLUGINS_DIR))
sys.path.append(os.path.abspath(Utility.USER_PLUGINS_DIR))
HORIZONTAL_LINE_LENGTH = 64
class PluginManager(object):
"""
Imports the Safe Eyes plugins and calls the methods defined in those plugins.
"""
def __init__(self, context, config):
logging.info('Load all the plugins')
self.__plugins = {}
self.__plugins_on_init = []
self.__plugins_on_start = []
self.__plugins_on_stop = []
self.__plugins_on_exit = []
self.__plugins_on_pre_break = []
self.__plugins_on_start_break = []
self.__plugins_on_stop_break = []
self.__plugins_on_countdown = []
self.__plugins_update_next_break = []
self.__widget_plugins = []
self.last_break = None
self.horizontal_line = '' * HORIZONTAL_LINE_LENGTH
def init(self, context, config):
"""
Initialize all the plugins with init(context, safeeyes_config, plugin_config) function.
"""
# Load the plugins
for plugin in config.get('plugins'):
try:
self.__load_plugin(plugin, context)
except BaseException:
logging.error('Error in loading the plugin: %s', plugin['id'])
continue
# Initialize the plugins
for plugin in self.__plugins_on_init:
plugin['module'].init(context, config, plugin['config'])
return True
def start(self):
"""
Execute the on_start() function of plugins.
"""
for plugin in self.__plugins_on_start:
plugin['module'].on_start()
return True
def stop(self):
"""
Execute the on_stop() function of plugins.
"""
for plugin in self.__plugins_on_stop:
plugin['module'].on_stop()
return True
def exit(self):
"""
Execute the on_exit() function of plugins.
"""
for plugin in self.__plugins_on_exit:
plugin['module'].on_exit()
return True
def pre_break(self, break_obj):
"""
Execute the on_pre_break(break_obj) function of plugins.
"""
for plugin in self.__plugins_on_pre_break:
if break_obj.plugin_enabled(plugin['id'], plugin['enabled']):
if plugin['module'].on_pre_break(break_obj):
return False
return True
def start_break(self, break_obj):
"""
Execute the start_break(break_obj) function of plugins.
"""
self.last_break = break_obj
for plugin in self.__plugins_on_start_break:
if break_obj.plugin_enabled(plugin['id'], plugin['enabled']):
if plugin['module'].on_start_break(break_obj):
return False
return True
def stop_break(self):
"""
Execute the stop_break() function of plugins.
"""
for plugin in self.__plugins_on_stop_break:
if self.last_break.plugin_enabled(plugin['id'], plugin['enabled']):
plugin['module'].on_stop_break()
def countdown(self, countdown, seconds):
"""
Execute the on_countdown(countdown, seconds) function of plugins.
"""
for plugin in self.__plugins_on_countdown:
if self.last_break.plugin_enabled(plugin['id'], plugin['enabled']):
plugin['module'].on_countdown(countdown, seconds)
def update_next_break(self, break_time):
"""
Execute the update_next_break(break_time) function of plugins.
"""
for plugin in self.__plugins_update_next_break:
plugin['module'].update_next_break(break_time)
return True
def get_break_screen_widgets(self, break_obj):
"""
Return the HTML widget generated by the plugins.
The widget is generated by calling the get_widget_title and get_widget_content functions of plugins.
"""
widget = ''
for plugin in self.__widget_plugins:
if break_obj.plugin_enabled(plugin['id'], plugin['enabled']):
try:
title = plugin['module'].get_widget_title(break_obj).upper().strip()
if title == '':
continue
content = plugin['module'].get_widget_content(break_obj)
if content == '':
continue
widget += '<b>{}</b>\n{}\n{}\n\n\n'.format(title, self.horizontal_line, content)
except BaseException:
continue
return widget.strip()
def __has_method(self, module, method_name, no_of_args=0):
"""
Check whether the given function is defined in the module or not.
"""
if hasattr(module, method_name):
if len(inspect.getargspec(getattr(module, method_name)).args) == no_of_args:
return True
return False
def __remove_if_exists(self, list_of_items, item):
"""
Remove the item from the list_of_items it it exists.
"""
if item in list_of_items:
list_of_items.remove(item)
def __load_plugin(self, plugin, context):
"""
Load the given plugin.
"""
plugin_enabled = plugin['enabled']
if plugin['id'] in self.__plugins and not plugin_enabled:
# A disabled plugin but that was loaded earlier
plugin_obj = self.__plugins[plugin['id']]
if plugin_obj['enabled']:
# Previously enabled but now disabled
plugin_obj['enabled'] = False
self.__remove_if_exists(self.__plugins_on_start, plugin_obj)
self.__remove_if_exists(self.__plugins_on_stop, plugin_obj)
self.__remove_if_exists(self.__plugins_on_exit, plugin_obj)
self.__remove_if_exists(self.__plugins_update_next_break, plugin_obj)
# Call the plugin.disable method if available
if self.__has_method(plugin_obj['module'], 'disable'):
plugin_obj['module'].disable()
logging.info("Successfully unloaded the plugin '%s'", plugin['id'])
if not plugin_obj['break_override_allowed']:
# Remaining methods also should be removed
self.__remove_if_exists(self.__plugins_on_init, plugin_obj)
self.__remove_if_exists(self.__plugins_on_pre_break, plugin_obj)
self.__remove_if_exists(self.__plugins_on_start_break, plugin_obj)
self.__remove_if_exists(self.__plugins_on_stop_break, plugin_obj)
self.__remove_if_exists(self.__plugins_on_countdown, plugin_obj)
self.__remove_if_exists(self.__widget_plugins, plugin_obj)
del self.__plugins[plugin['id']]
return
# Look for plugin.py
plugin_dir = None
if os.path.isfile(os.path.join(Utility.SYSTEM_PLUGINS_DIR, plugin['id'], 'plugin.py')):
plugin_dir = Utility.SYSTEM_PLUGINS_DIR
elif os.path.isfile(os.path.join(Utility.USER_PLUGINS_DIR, plugin['id'], 'plugin.py')):
plugin_dir = Utility.USER_PLUGINS_DIR
else:
logging.error('plugin.py not found for the plugin: %s', plugin['id'])
return
# Look for config.json
plugin_config_path = os.path.join(plugin_dir, plugin['id'], 'config.json')
if not os.path.isfile(plugin_config_path):
logging.error('config.json not found for the plugin: %s', plugin['id'])
return
plugin_config = Utility.load_json(plugin_config_path)
if plugin_config is None:
return
if (plugin_enabled or plugin_config.get('break_override_allowed', False)):
if plugin['id'] in self.__plugins:
# The plugin is already enabled or partially loaded due to break_override_allowed
# Use the existing plugin object
plugin_obj = self.__plugins[plugin['id']]
if plugin_obj['enabled']:
# Already loaded completely
return
# Plugin was partially loaded due to break_override_allowed
if plugin_enabled:
# Load the rest of the methods
plugin_obj['enabled'] = True
module = plugin_obj['module']
if self.__has_method(module, 'on_start'):
self.__plugins_on_start.append(plugin_obj)
if self.__has_method(module, 'on_stop'):
self.__plugins_on_stop.append(plugin_obj)
if self.__has_method(module, 'on_exit'):
self.__plugins_on_exit.append(plugin_obj)
if self.__has_method(module, 'update_next_break', 1):
self.__plugins_update_next_break.append(plugin_obj)
else:
# This is the first time to load the plugin
# Check for dependencies
if Utility.check_plugin_dependencies(plugin_config):
return
# Load the plugin module
module = importlib.import_module((plugin['id'] + '.plugin'))
logging.info("Successfully loaded %s", str(module))
plugin_obj = {'id': plugin['id'], 'module': module, 'config': plugin.get('settings', {}), 'enabled': plugin_enabled, 'break_override_allowed': plugin_config.get('break_override_allowed', False)}
self.__plugins[plugin['id']] = plugin_obj
if self.__has_method(module, 'enable'):
module.enable()
if plugin_enabled:
if self.__has_method(module, 'on_start'):
self.__plugins_on_start.append(plugin_obj)
if self.__has_method(module, 'on_stop'):
self.__plugins_on_stop.append(plugin_obj)
if self.__has_method(module, 'on_exit'):
self.__plugins_on_exit.append(plugin_obj)
if self.__has_method(module, 'update_next_break', 1):
self.__plugins_update_next_break.append(plugin_obj)
if self.__has_method(module, 'init', 3):
self.__plugins_on_init.append(plugin_obj)
if self.__has_method(module, 'on_pre_break', 1):
self.__plugins_on_pre_break.append(plugin_obj)
if self.__has_method(module, 'on_start_break', 1):
self.__plugins_on_start_break.append(plugin_obj)
if self.__has_method(module, 'on_stop_break', 0):
self.__plugins_on_stop_break.append(plugin_obj)
if self.__has_method(module, 'on_countdown', 2):
self.__plugins_on_countdown.append(plugin_obj)
if self.__has_method(module, 'get_widget_title', 1) and self.__has_method(module, 'get_widget_content', 1):
self.__widget_plugins.append(plugin_obj)

View File

@ -1,143 +0,0 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <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:
"""
Imports the Safe Eyes plugins and calls the methods defined in those plugins.
"""
def __init__(self, config):
logging.info('Load all the plugins')
self.__plugins = []
for plugin in config['plugins']:
if plugin['location'].lower() in ['left', 'right']:
if os.path.isfile(os.path.join(plugins_directory, plugin['name'] + '.py')):
module = importlib.import_module(plugin['name'])
if self.__has_method(module, 'start') and self.__has_method(module, 'pre_notification') and self.__has_method(module, 'pre_break') and self.__has_method(module, 'post_break') and self.__has_method(module, 'exit'):
self.__plugins.append({'name': plugin['name'], 'module': module, 'location': plugin['location'].lower()})
else:
logging.warning('Ignoring the plugin ' + str(plugin['name']) + ' due to invalid method signature')
else:
logging.warning('Plugin file ' + str(plugin['name']) + '.py not found')
else:
logging.warning('Ignoring the plugin ' + str(plugin['name']) + ' due to invalid location value: ' + plugin['location'])
if self.__plugins:
self.__thread_pool = ThreadPool(min([4, len(self.__plugins)]))
def start(self, context):
"""
Call the start function of all the plugins in separate thread.
"""
if self.__plugins:
context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes
for plugin in self.__plugins:
try:
self.__thread_pool.apply_async(plugin['module'].start, (context,))
except Exception as e:
pass
def pre_notification(self, context):
"""
Call the pre_notification function of all the plugins in separate thread.
"""
if self.__plugins:
context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes
for plugin in self.__plugins:
try:
self.__thread_pool.apply_async(plugin['module'].pre_notification, (context,))
except Exception as e:
pass
def pre_break(self, context):
"""
Call the pre_break function of all the plugins and provide maximum 1 second to return the result.
If they return the reault within 1 sec, append it to the output.
Returns: {'left': 'Markup of plugins to be aligned on left', 'right': 'Markup of plugins to be aligned on right' }
"""
output = {'left': ' \n', 'right': ' \n'}
if self.__plugins:
context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes
multiple_results = [self.__thread_pool.apply_async(plugin['module'].pre_break, (context,)) for plugin in self.__plugins]
for i in range(len(multiple_results)):
try:
result = multiple_results[i].get(timeout=1)
if result:
# Limit the line length to 50 characters
large_lines = list(filter(lambda x: len(x) > 50, Utility.html_to_text(result).splitlines()))
if large_lines:
logging.warning('Ignoring lengthy result from the plugin ' + self.__plugins[i]['name'])
continue
output[self.__plugins[i]['location']] += (result + '\n\n')
except Exception:
# Something went wrong in the plugin
logging.warning('Error when executing the plugin ' + self.__plugins[i]['name'])
return output
def post_break(self, context):
"""
Call the post_break function of all the plugins in separate thread.
"""
if self.__plugins:
context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes
for plugin in self.__plugins:
try:
self.__thread_pool.apply_async(plugin['module'].post_break, (context,))
except Exception as e:
pass
def exit(self, context):
"""
Call the exit function of all the plugins in separate thread.
"""
if self.__plugins:
context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes
# Give maximum 1 sec for all plugins before terminating the thread pool
multiple_results = [self.__thread_pool.apply_async(plugin['module'].exit, (context,)) for plugin in self.__plugins]
for i in range(len(multiple_results)):
try:
multiple_results[i].get(timeout=1)
except Exception:
# Something went wrong in the plugin
pass
try:
self.__thread_pool.terminate() # Shutdown the pool
except Exception as e:
pass
def __has_method(self, module, method_name, no_of_args=1):
"""
Check whether the given function is defined in the module or not.
"""
if hasattr(module, method_name):
if len(inspect.getargspec(getattr(module, method_name)).args) == no_of_args:
return True
return False

270
safeeyes/SafeEyes.py Normal file
View File

@ -0,0 +1,270 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
SafeEyes connects all the individual components and provide the complete application.
"""
import atexit
import logging
from threading import Timer
import dbus
import gi
from dbus.mainloop.glib import DBusGMainLoop
from safeeyes import Utility
from safeeyes.AboutDialog import AboutDialog
from safeeyes.BreakScreen import BreakScreen
from safeeyes.model import State
from safeeyes.rpc import RPCServer
from safeeyes.PluginManager import PluginManager
from safeeyes.SafeEyesCore import SafeEyesCore
from safeeyes.settings import SettingsDialog
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
SAFE_EYES_VERSION = "2.0.0"
class SafeEyes(object):
"""
This class represents a runnable Safe Eyes instance.
"""
def __init__(self, system_locale, config):
self.active = False
self.break_screen = None
self.safe_eyes_core = None
self.config = config
self.context = {}
self.plugins_manager = None
self.settings_dialog_active = False
self.rpc_server = None
# Initialize the Safe Eyes Context
self.context['version'] = SAFE_EYES_VERSION
self.context['desktop'] = Utility.desktop_environment()
self.context['locale'] = system_locale
self.context['api'] = {}
self.context['api']['show_settings'] = self.show_settings
self.context['api']['show_about'] = self.show_about
self.context['api']['enable_safeeyes'] = self.enable_safeeyes
self.context['api']['disable_safeeyes'] = self.disable_safeeyes
self.context['api']['quit'] = self.quit
if self.config.get('persist_state'):
self.context['session'] = Utility.open_session()
else:
self.context['session'] = {'plugin': {}}
self.break_screen = BreakScreen(self.context, self.on_skipped, self.on_postponed, Utility.STYLE_SHEET_PATH)
self.break_screen.initialize(self.config)
self.plugins_manager = PluginManager(self.context, self.config)
self.safe_eyes_core = SafeEyesCore(self.context)
self.safe_eyes_core.on_pre_break += self.plugins_manager.pre_break
self.safe_eyes_core.on_start_break += self.start_break
self.safe_eyes_core.on_count_down += self.countdown
self.safe_eyes_core.on_stop_break += self.stop_break
self.safe_eyes_core.on_update_next_break += self.update_next_break
self.safe_eyes_core.initialize(self.config)
self.context['api']['take_break'] = self.safe_eyes_core.take_break
self.context['api']['has_breaks'] = self.safe_eyes_core.has_breaks
self.plugins_manager.init(self.context, self.config)
atexit.register(self.persist_session)
self.rpc_server = RPCServer(self.config.get('rpc_port'), self.context)
self.rpc_server.start()
def start(self):
"""
Start Safe Eyes
"""
if self.safe_eyes_core.has_breaks():
self.active = True
self.context['state'] = State.START
self.plugins_manager.start() # Call the start method of all plugins
self.safe_eyes_core.start()
self.handle_system_suspend()
def show_settings(self):
"""
Listen to tray icon Settings action and send the signal to Settings dialog.
"""
if not self.settings_dialog_active:
logging.info("Show Settings dialog")
self.settings_dialog_active = True
settings_dialog = SettingsDialog(self.config, self.save_settings)
settings_dialog.show()
def show_about(self):
"""
Listen to tray icon About action and send the signal to About dialog.
"""
logging.info("Show About dialog")
about_dialog = AboutDialog(SAFE_EYES_VERSION)
about_dialog.show()
def quit(self):
"""
Listen to the tray menu quit action and stop the core, notification and the app itself.
"""
logging.info("Quit Safe Eyes")
self.context['state'] = State.QUIT
self.plugins_manager.stop()
self.safe_eyes_core.stop()
self.plugins_manager.exit()
self.rpc_server.stop()
Gtk.main_quit()
def handle_suspend_callback(self, sleeping):
"""
If the system goes to sleep, Safe Eyes stop the core if it is already active.
If it was active, Safe Eyes will become active after wake up.
"""
if sleeping:
# Sleeping / suspending
if self.active:
logging.info("Stop Safe Eyes due to system suspend")
self.plugins_manager.stop()
self.safe_eyes_core.stop()
else:
# Resume from sleep
if self.active and self.safe_eyes_core.has_breaks():
logging.info("Resume Safe Eyes after system wakeup")
self.plugins_manager.start()
self.safe_eyes_core.start()
def handle_system_suspend(self):
"""
Setup system suspend listener.
"""
DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus.add_signal_receiver(self.handle_suspend_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
def on_skipped(self):
"""
Listen to break screen Skip action and send the signal to core.
"""
logging.info("User skipped the break")
self.safe_eyes_core.skip()
self.plugins_manager.stop_break()
def on_postponed(self):
"""
Listen to break screen Postpone action and send the signal to core.
"""
logging.info("User postponed the break")
self.safe_eyes_core.postpone()
self.plugins_manager.stop_break()
def save_settings(self, config):
"""
Listen to Settings dialog Save action and write to the config file.
"""
self.settings_dialog_active = False
logging.info("Saving settings to safeeyes.json")
# Stop the Safe Eyes core
if self.active:
self.plugins_manager.stop()
self.safe_eyes_core.stop()
# Write the configuration to file
config.save()
self.persist_session()
logging.info("Initialize SafeEyesCore with modified settings")
# Restart the core and intialize the components
self.config = config
self.safe_eyes_core.initialize(config)
self.break_screen.initialize(config)
self.plugins_manager.init(self.context, self.config)
if self.active and self.safe_eyes_core.has_breaks():
# 1 sec delay is required to give enough time for core to be stopped
Timer(1.0, self.safe_eyes_core.start).start()
self.plugins_manager.start()
def enable_safeeyes(self, scheduled_next_break_time=-1):
"""
Listen to tray icon enable action and send the signal to core.
"""
if not self.active and self.safe_eyes_core.has_breaks():
self.active = True
self.safe_eyes_core.start(scheduled_next_break_time)
self.plugins_manager.start()
def disable_safeeyes(self):
"""
Listen to tray icon disable action and send the signal to core.
"""
if self.active:
self.active = False
self.plugins_manager.stop()
self.safe_eyes_core.stop()
def start_break(self, break_obj):
"""
Pass the break information to plugins and break screen.
"""
if not self.plugins_manager.start_break(break_obj):
return False
# Get the HTML widgets content from plugins
widget = self.plugins_manager.get_break_screen_widgets(break_obj)
self.break_screen.show_message(break_obj, widget)
return True
def countdown(self, countdown, seconds):
"""
Pass the countdown to plugins and break screen.
"""
self.break_screen.show_count_down(countdown, seconds)
self.plugins_manager.countdown(countdown, seconds)
return True
def update_next_break(self, break_time):
"""
Update the next break to plugins and save the session.
"""
self.plugins_manager.update_next_break(break_time)
if self.config.get('persist_state'):
Utility.write_json(Utility.SESSION_FILE_PATH, self.context['session'])
def stop_break(self):
"""
Stop the current break.
"""
self.break_screen.close()
self.plugins_manager.stop_break()
return True
def take_break(self):
"""
Take a break now.
"""
self.safe_eyes_core.take_break()
def persist_session(self):
"""
Save the session object to the session file.
"""
if self.config.get('persist_state'):
Utility.write_json(Utility.SESSION_FILE_PATH, self.context['session'])
else:
Utility.delete(Utility.SESSION_FILE_PATH)

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
@ -15,338 +16,329 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
SafeEyesCore provides the core functionalities of Safe Eyes.
"""
import datetime
import logging
import threading
import time
import time, datetime, threading, logging
from safeeyes import Utility
from safeeyes.model import Break
from safeeyes.model import BreakType
from safeeyes.model import EventHook
from safeeyes.model import State
class SafeEyesCore:
"""
Core of Safe Eyes which runs the scheduler and notifies the breaks.
"""
class SafeEyesCore(object):
"""
Core of Safe Eyes runs the scheduler and notifies the breaks.
"""
def __init__(self, context, show_notification, start_break, end_break, on_countdown, update_next_break_info):
# Initialize the variables
self.break_count = -1
self.long_break_message_index = -1
self.short_break_message_index = -1
self.active = False
self.running = False
self.show_notification = show_notification
self.start_break = start_break
self.end_break = end_break
self.on_countdown = on_countdown
self.update_next_break_info = update_next_break_info
self.notification_condition = threading.Condition()
self.idle_condition = threading.Condition()
self.lock = threading.Lock()
self.context = context
self.context['skipped'] = False
self.context['postponed'] = False
def __init__(self, context):
"""
Create an instance of SafeEyesCore and initialize the variables.
"""
self.break_count = 0
self.break_interval = 0
self.breaks = None
self.long_break_duration = 0
self.next_break_index = context['session'].get('next_break_index', 0)
self.postpone_duration = 0
self.pre_break_warning_time = 0
self.running = False
self.short_break_duration = 0
self.scheduled_next_break_time = -1
# This event is fired before <time-to-prepare> for a break
self.on_pre_break = EventHook()
# This event is fired at the start of a break
self.on_start_break = EventHook()
# This event is fired during every count down
self.on_count_down = EventHook()
# This event is fired at the end of a break
self.on_stop_break = EventHook()
# This event is fired when deciding the next break time
self.on_update_next_break = EventHook()
self.waiting_condition = threading.Condition()
self.lock = threading.Lock()
self.context = context
self.context['skipped'] = False
self.context['postponed'] = False
self.context['state'] = State.WAITING
self.context['new_cycle'] = False
def initialize(self, config, language):
"""
Initialize the internal properties from configuration
"""
logging.info("Initialize the core")
self.short_break_exercises = [] # language['exercises']['short_break_exercises']
self.long_break_exercises = [] # language['exercises']['long_break_exercises']
def initialize(self, config):
"""
Initialize the internal properties from configuration
"""
logging.info("Initialize the core")
self.breaks = []
self.pre_break_warning_time = config.get('pre_break_warning_time')
self.long_break_duration = config.get('long_break_duration')
self.short_break_duration = config.get('short_break_duration')
self.break_interval = config.get('break_interval')
self.postpone_duration = config.get('postpone_duration')
self.no_of_short_breaks_per_long_break = config['no_of_short_breaks_per_long_break']
self.pre_break_warning_time = config['pre_break_warning_time']
self.long_break_duration = config['long_break_duration']
self.short_break_duration = config['short_break_duration']
self.break_interval = config['break_interval']
self.idle_time = config['idle_time']
self.postpone_duration = config['postpone_duration']
self.skip_break_window_classes = [x.lower() for x in config['active_window_class']['skip_break']]
self.take_break_window_classes = [x.lower() for x in config['active_window_class']['take_break']]
self.custom_exercises = config['custom_exercises']
# Enable idle time pause only if xprintidle is available
self.context['idle_pause_enabled'] = Utility.command_exist('xprintidle')
self.__init_breaks(BreakType.SHORT_BREAK, config.get('short_breaks'), config.get('no_of_short_breaks_per_long_break'))
self.__init_breaks(BreakType.LONG_BREAK, config.get('long_breaks'), config.get('no_of_short_breaks_per_long_break'))
self.break_count = len(self.breaks)
if self.break_count == 0:
# No breaks found
return
self.next_break_index = (self.next_break_index) % self.break_count
self.context['session']['next_break_index'] = self.next_break_index
exercises = language['exercises']
for short_break_config in config['short_breaks']:
exercise_name = short_break_config['name']
name = None
def start(self, next_break_time=-1):
"""
Start Safe Eyes is it is not running already.
"""
if not self.has_breaks():
return
with self.lock:
if not self.running:
logging.info("Start Safe Eyes core")
self.running = True
self.scheduled_next_break_time = int(next_break_time)
Utility.start_thread(self.__scheduler_job)
if exercise_name in self.custom_exercises:
name = self.custom_exercises[exercise_name]
else:
name = exercises[exercise_name]
def stop(self):
"""
Stop Safe Eyes if it is running.
"""
with self.lock:
if not self.running:
return
break_time = short_break_config.get('time', self.short_break_duration)
audible_alert = short_break_config.get('audible_alert', config['audible_alert'])
image = short_break_config.get('image')
logging.info("Stop Safe Eye core")
# Validate time value
if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in short break: ' + str(short_break_config))
continue
# Prevent resuming from a long break
if self.has_breaks() and self.__is_long_break():
# Next break will be a long break.
self.__select_next_break()
self.short_break_exercises.append([name, break_time, audible_alert, image])
# Stop the break thread
self.waiting_condition.acquire()
self.running = False
if self.context['state'] != State.QUIT:
self.context['state'] = State.STOPPED
self.waiting_condition.notify_all()
self.waiting_condition.release()
for long_break_config in config['long_breaks']:
exercise_name = long_break_config['name']
name = None
def skip(self):
"""
User skipped the break using Skip button
"""
self.context['skipped'] = True
if exercise_name in self.custom_exercises:
name = self.custom_exercises[exercise_name]
else:
name = exercises[exercise_name]
def postpone(self):
"""
User postponed the break using Postpone button
"""
self.context['postponed'] = True
break_time = long_break_config.get('time', self.long_break_duration)
audible_alert = long_break_config.get('audible_alert', config['audible_alert'])
image = long_break_config.get('image')
def take_break(self):
"""
Calling this method stops the scheduler and show the next break screen
"""
if not self.has_breaks():
return
if not self.context['state'] == State.WAITING:
return
Utility.start_thread(self.__take_break)
# Validate time value
if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in long break: ' + str(long_break_config))
continue
def has_breaks(self):
"""
Check whether Safe Eyes has breaks or not.
"""
return bool(self.breaks)
self.long_break_exercises.append([name, break_time, audible_alert, image])
def __take_break(self):
"""
Show the next break screen
"""
logging.info('Take a break due to external request')
def start(self):
"""
Start Safe Eyes is it is not running already.
"""
with self.lock:
if not self.active:
logging.info("Scheduling next break")
self.active = True
self.running = True
Utility.start_thread(self.__scheduler_job)
if self.context['idle_pause_enabled']:
Utility.start_thread(self.__start_idle_monitor)
with self.lock:
if not self.running:
return
def stop(self):
"""
Stop Safe Eyes if it is running.
"""
with self.lock:
if self.active:
logging.info("Stop the core")
logging.info("Stop the scheduler")
# Prevent resuming from a long break
current_break_count = self.break_count = 0
self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break)
if self.__is_long_break():
# Next break will be a long break. Leave the increment so that next break will be short break after the long
pass
else:
# Reset to the current state
self.break_count = current_break_count
# Stop the break thread
self.waiting_condition.acquire()
self.running = False
self.waiting_condition.notify_all()
self.waiting_condition.release()
time.sleep(1) # Wait for 1 sec to ensure the sceduler is dead
self.running = True
# Stop the break thread
self.notification_condition.acquire()
self.active = False
self.running = False
self.notification_condition.notify_all()
self.notification_condition.release()
self.context['new_cycle'] = self.next_break_index == 0
Utility.execute_main_thread(self.__fire_start_break)
# Stop the idle monitor
self.idle_condition.acquire()
self.idle_condition.notify_all()
self.idle_condition.release()
def __scheduler_job(self):
"""
Scheduler task to execute during every interval
"""
if not self.running:
return
def pause(self):
"""
Pause Safe Eyes if it is running.
"""
with self.lock:
if self.active and self.running:
self.notification_condition.acquire()
self.running = False
self.notification_condition.notify_all()
self.notification_condition.release()
self.context['state'] = State.WAITING
time_to_wait = self.break_interval # In minutes
def resume(self):
"""
Resume Safe Eyes if it is not running.
"""
with self.lock:
if self.active and not self.running:
self.running = True
Utility.start_thread(self.__scheduler_job)
if self.context['postponed']:
# Wait until the postpone time
time_to_wait = self.postpone_duration
self.context['postponed'] = False
def skip_break(self):
"""
User skipped the break using Skip button
"""
self.context['skipped'] = True
current_time = datetime.datetime.now()
current_timestamp = int(current_time.timestamp())
if current_timestamp < self.scheduled_next_break_time:
time_to_wait = int((self.scheduled_next_break_time - current_timestamp) / 60)
self.scheduled_next_break_time = -1
def postpone_break(self):
"""
User postponed the break using Postpone button
"""
self.context['postponed'] = True
next_break_time = current_time + datetime.timedelta(minutes=time_to_wait)
self.on_update_next_break.fire(next_break_time)
def __scheduler_job(self):
"""
Scheduler task to execute during every interval
"""
if not self.__is_running():
return
if self.__is_long_break():
self.context['break_type'] = 'long'
else:
self.context['break_type'] = 'short'
time_to_wait = self.break_interval # In minutes
# Wait for the pre break warning period
logging.info("Waiting for %s minutes until next break", time_to_wait)
self.__wait_for(time_to_wait * 60) # Convert to seconds
if self.context['postponed']:
# Reduce the break count by 1 to show the same break again
if self.break_count == 0:
self.break_count = -1
else:
self.break_count = ((self.break_count - 1) % self.no_of_short_breaks_per_long_break)
if self.__is_long_break():
self.long_break_message_index = (self.long_break_message_index - 1) % len(self.long_break_exercises)
else:
self.short_break_message_index = (self.short_break_message_index - 1) % len(self.short_break_exercises)
logging.info("Pre-break waiting is over")
# Wait until the postpone time
time_to_wait = self.postpone_duration
self.context['postponed'] = False
if not self.running:
return
next_break_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
self.update_next_break_info(next_break_time)
self.context['new_cycle'] = self.next_break_index == 0
Utility.execute_main_thread(self.__fire_pre_break)
self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break)
if self.__is_long_break():
self.context['break_type'] = 'long'
else:
self.context['break_type'] = 'short'
def __fire_pre_break(self):
"""
Show the notification and start the break after the notification.
"""
self.context['state'] = State.PRE_BREAK
if not self.on_pre_break.fire(self.breaks[self.next_break_index]):
# Plugins wanted to ignore this break
self.__start_next_break()
return
Utility.start_thread(self.__wait_until_prepare)
# Wait for the pre break warning period
logging.info("Pre-break waiting for {} minutes".format(time_to_wait))
self.notification_condition.acquire()
self.notification_condition.wait(time_to_wait * 60) # Convert to seconds
self.notification_condition.release()
def __wait_until_prepare(self):
logging.info("Wait for %d seconds before the break", self.pre_break_warning_time)
# Wait for the pre break warning period
self.__wait_for(self.pre_break_warning_time)
if not self.running:
return
Utility.execute_main_thread(self.__fire_start_break)
logging.info("Pre-break waiting is over")
def __fire_start_break(self):
# Show the break screen
if not self.on_start_break.fire(self.breaks[self.next_break_index]):
# Plugins wanted to ignore this break
self.__start_next_break()
return
Utility.start_thread(self.__start_break)
if not self.__is_running():
return
def __start_break(self):
"""
Start the break screen.
"""
self.context['state'] = State.BREAK
break_obj = self.breaks[self.next_break_index]
countdown = break_obj.time
total_break_time = countdown
logging.info("Ready to show the break")
while countdown and self.running and not self.context['skipped'] and not self.context['postponed']:
seconds = total_break_time - countdown
self.on_count_down.fire(countdown, seconds)
time.sleep(1) # Sleep for 1 second
countdown -= 1
Utility.execute_main_thread(self.__fire_stop_break)
self.is_before_break = False
Utility.execute_main_thread(self.__check_active_window)
def __fire_stop_break(self):
# Loop terminated because of timeout (not skipped) -> Close the break alert
if not self.context['skipped'] and not self.context['postponed']:
logging.info("Break is terminated automatically")
self.on_stop_break.fire()
def __show_notification(self):
"""
Show the notification and start the break after the notification.
"""
# Show the notification
self.show_notification()
# Reset the skipped flag
self.context['skipped'] = False
self.__start_next_break()
logging.info("Wait for {} seconds which is the time to prepare".format(self.pre_break_warning_time))
# Wait for the pre break warning period
self.notification_condition.acquire()
self.notification_condition.wait(self.pre_break_warning_time)
self.notification_condition.release()
def __wait_for(self, duration):
"""
Wait until someone wake up or the timeout happens.
"""
self.waiting_condition.acquire()
self.waiting_condition.wait(duration)
self.waiting_condition.release()
self.is_before_break = True
Utility.execute_main_thread(self.__check_active_window)
def __select_next_break(self):
"""
Select the next break.
"""
self.next_break_index = (self.next_break_index + 1) % self.break_count
self.context['session']['next_break_index'] = self.next_break_index
def __check_active_window(self):
"""
Check the active window for full-screen and user defined exceptions.
"""
# Check the active window again. (User might changed the window)
if self.__is_running() and Utility.is_active_window_skipped(self.skip_break_window_classes, self.take_break_window_classes, self.is_before_break):
# If full screen app found, do not show break screen
logging.info("Found a skip_break or full-screen window. Skip the break")
if self.__is_running():
# Schedule the break again
Utility.start_thread(self.__scheduler_job)
return
def __is_long_break(self):
"""
Check if the next break is long break.
"""
return self.breaks[self.next_break_index].type is BreakType.LONG_BREAK
# Execute the post-operation
if self.is_before_break:
Utility.start_thread(self.__start_break)
else:
Utility.start_thread(self.__show_notification)
def __start_next_break(self):
if not self.context['postponed']:
self.__select_next_break()
def __start_break(self):
"""
Start the break screen.
"""
# User can disable SafeEyes during notification
if self.__is_running():
message = ""
image = None
seconds = 0
audible_alert = None
if self.__is_long_break():
logging.info("Count is {}; get a long beak message".format(self.break_count))
self.long_break_message_index = (self.long_break_message_index + 1) % len(self.long_break_exercises)
message = self.long_break_exercises[self.long_break_message_index][0]
seconds = self.long_break_exercises[self.long_break_message_index][1]
audible_alert = self.long_break_exercises[self.long_break_message_index][2]
image = self.long_break_exercises[self.long_break_message_index][3]
else:
logging.info("Count is {}; get a short beak message".format(self.break_count))
self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises)
message = self.short_break_exercises[self.short_break_message_index][0]
seconds = self.short_break_exercises[self.short_break_message_index][1]
audible_alert = self.short_break_exercises[self.short_break_message_index][2]
image = self.short_break_exercises[self.short_break_message_index][3]
if self.running:
# Schedule the break again
Utility.start_thread(self.__scheduler_job)
self.context['break_length'] = seconds
self.context['audible_alert'] = audible_alert
total_break_time = seconds
def __init_breaks(self, break_type, break_configs, short_breaks_per_long_break=0):
"""
Fill the self.breaks using short and local breaks.
"""
# Defin the default break time
default_break_time = self.short_break_duration
# Show the break screen
self.start_break(message, image)
# Duplicate short breaks to equally distribute the long breaks
if break_type is BreakType.LONG_BREAK:
if self.breaks:
default_break_time = self.long_break_duration
required_short_breaks = short_breaks_per_long_break * len(break_configs)
no_of_short_breaks = len(self.breaks)
short_break_index = 0
while no_of_short_breaks < required_short_breaks:
self.breaks.append(self.breaks[short_break_index])
short_break_index += 1
no_of_short_breaks += 1
else:
# If there are no short breaks, extend the break interval according to long break interval
self.break_interval = int(self.break_interval * short_breaks_per_long_break)
# Use self.active instead of self.__is_running to avoid idle pause interrupting the break
while seconds and self.active and not self.context['skipped'] and not self.context['postponed']:
count_down = total_break_time - seconds
self.context['count_down'] = count_down
self.on_countdown(count_down, seconds)
time.sleep(1) # Sleep for 1 second
seconds -= 1
iteration = 1
for break_config in break_configs:
name = _(break_config['name'])
break_time = break_config.get('duration', default_break_time)
image = break_config.get('image')
plugins = break_config.get('plugins', None)
# Loop terminated because of timeout (not skipped) -> Close the break alert
if not self.context['skipped'] and not self.context['postponed']:
logging.info("Break is terminated automatically")
self.end_break(audible_alert)
# Validate time value
if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in break: ' + str(break_config))
continue
# Reset the skipped flag
self.context['skipped'] = False
# Resume
if self.__is_running():
# Schedule the break again
Utility.start_thread(self.__scheduler_job)
def __is_running(self):
"""
Tells whether Safe Eyes is running or not.
"""
return self.active and self.running
def __is_long_break(self):
"""
Check if the current break is long break or short current
"""
return self.break_count == self.no_of_short_breaks_per_long_break - 1
def __start_idle_monitor(self):
"""
Continuously check the system idle time and pause/resume Safe Eyes based on it.
"""
while self.active:
# Wait for 2 seconds
self.idle_condition.acquire()
self.idle_condition.wait(2)
self.idle_condition.release()
if self.active:
# Get the system idle time
system_idle_time = Utility.system_idle_time()
if system_idle_time >= self.idle_time and self.running:
logging.info('Pause Safe Eyes due to system idle')
self.pause()
elif system_idle_time < self.idle_time and not self.running:
logging.info('Resume Safe Eyes due to user activity')
self.resume()
break_obj = Break(break_type, name, break_time, image, plugins)
if break_type is BreakType.SHORT_BREAK:
self.breaks.append(break_obj)
else:
# Long break
index = iteration * (short_breaks_per_long_break + 1) - 1
self.breaks.insert(index, break_obj)
iteration += 1

View File

@ -1,226 +0,0 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2016 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
from safeeyes import Utility
class SettingsDialog:
"""
Create and initialize SettingsDialog instance.
"""
def __init__(self, config, language, languages, able_to_lock_screen, on_save_settings, glade_file):
self.config = config
self.on_save_settings = on_save_settings
self.languages = []
self.language = language
builder = Gtk.Builder()
builder.add_from_file(glade_file)
builder.connect_signals(self)
xprintidle_available = Utility.command_exist('xprintidle')
# Get the UI components
self.window = builder.get_object('window_settings')
self.spin_short_break_duration = builder.get_object('spin_short_break_duration')
self.spin_long_break_duration = builder.get_object('spin_long_break_duration')
self.spin_interval_between_two_breaks = builder.get_object('spin_interval_between_two_breaks')
self.spin_short_between_long = builder.get_object('spin_short_between_long')
self.spin_time_to_prepare = builder.get_object('spin_time_to_prepare')
self.spin_idle_time_to_pause = builder.get_object('spin_idle_time_to_pause')
self.spin_postpone_duration = builder.get_object('spin_postpone_duration')
self.spin_disable_keyboard_shortcut = builder.get_object('spin_disable_keyboard_shortcut')
self.switch_show_time_in_tray = builder.get_object('switch_show_time_in_tray')
self.switch_strict_break = builder.get_object('switch_strict_break')
self.switch_postpone = builder.get_object('switch_postpone')
self.switch_audible_alert = builder.get_object('switch_audible_alert')
self.cmb_language = builder.get_object('cmb_language')
self.switch_screen_lock = builder.get_object('switch_screen_lock')
self.spin_time_to_screen_lock = builder.get_object('spin_time_to_screen_lock')
# Translate the UI labels
builder.get_object('lbl_short_break').set_label(language['ui_controls']['short_break_duration'])
builder.get_object('lbl_long_break').set_label(language['ui_controls']['long_break_duration'])
builder.get_object('lbl_interval_bettween_breaks').set_label(language['ui_controls']['interval_between_two_breaks'])
builder.get_object('lbl_short_per_long').set_label(language['ui_controls']['no_of_short_breaks_between_two_long_breaks'])
builder.get_object('lbl_time_to_prepare').set_label(language['ui_controls']['time_to_prepare_for_break'])
builder.get_object('lbl_idle_time_to_pause').set_label(language['ui_controls']['idle_time'])
builder.get_object('lbl_postpone_duration').set_label(language['ui_controls']['postpone_duration'])
builder.get_object('lbl_allow_postpone').set_label(language['ui_controls']['allow_postpone'])
builder.get_object('lbl_disable_keyboard_shortcut').set_label(language['ui_controls']['disable_keyboard_shortcut'])
builder.get_object('lbl_show_time_in_tray').set_label(language['ui_controls']['show_time_in_tray'])
builder.get_object('lbl_strict_break').set_label(language['ui_controls']['strict_break'])
builder.get_object('lbl_audible_alert').set_label(language['ui_controls']['audible_alert'])
builder.get_object('lbl_language').set_label(language['ui_controls']['language'])
builder.get_object('lbl_enable_screen_lock').set_label(language['ui_controls']['enable_screen_lock'])
builder.get_object('lbl_lock_screen_after').set_label(language['ui_controls']['time_to_screen_lock'])
builder.get_object('btn_cancel').set_label(language['ui_controls']['cancel'])
builder.get_object('btn_save').set_label(language['ui_controls']['save'])
# Set the current values of input fields
self.spin_short_break_duration.set_value(config['short_break_duration'])
self.spin_long_break_duration.set_value(config['long_break_duration'])
self.spin_interval_between_two_breaks.set_value(config['break_interval'])
self.spin_short_between_long.set_value(config['no_of_short_breaks_per_long_break'])
self.spin_time_to_prepare.set_value(config['pre_break_warning_time'])
self.spin_idle_time_to_pause.set_value(config['idle_time'] and xprintidle_available)
self.spin_postpone_duration.set_value(config['postpone_duration'])
self.spin_disable_keyboard_shortcut.set_value(config['shortcut_disable_time'])
self.switch_show_time_in_tray.set_active(config['show_time_in_tray'])
self.switch_strict_break.set_active(config['strict_break'])
self.switch_audible_alert.set_active(config['audible_alert'] and Utility.pyaudio is None)
self.spin_time_to_screen_lock.set_value(config['time_to_screen_lock'])
# Enable idle_time_to_pause only if xprintidle is available
self.spin_idle_time_to_pause.set_sensitive(xprintidle_available)
self.switch_screen_lock.set_sensitive(able_to_lock_screen)
self.switch_screen_lock.set_active(able_to_lock_screen and config['enable_screen_lock'])
self.switch_postpone.set_active(config['allow_postpone'] and not config['strict_break'])
# Update relative states
# GtkSwitch state-set signal is available only from 3.14
if Gtk.get_minor_version() >= 14:
self.switch_strict_break.connect('state-set', self.on_switch_strict_break_activate)
self.switch_screen_lock.connect('state-set', self.on_switch_screen_lock_activate)
self.switch_postpone.connect('state-set', self.on_switch_postpone_activate)
self.on_switch_strict_break_activate(self.switch_strict_break, self.switch_strict_break.get_active())
self.on_switch_screen_lock_activate(self.switch_screen_lock, self.switch_screen_lock.get_active())
self.on_switch_postpone_activate(self.switch_postpone, self.switch_postpone.get_active())
if Utility.pyaudio is None:
self.switch_audible_alert.connect('state-set', self.on_switch_audible_alert_activate)
# Initialize the language combobox
language_list_store = Gtk.ListStore(GObject.TYPE_STRING)
language_index = 2
lang_code = config['language']
# Add 'System Language' as the first option
language_list_store.append([language['ui_controls']['system_language']])
language_list_store.append(['-'])
self.languages.append('system')
self.languages.append('system') # Dummy record for row separator
if 'system' == lang_code:
self.cmb_language.set_active(0)
for key in sorted(languages.keys()):
language_list_store.append([languages[key]])
self.languages.append(key)
if key == lang_code:
self.cmb_language.set_active(language_index)
language_index += 1
self.cmb_language.set_model(language_list_store)
self.cmb_language.set_row_separator_func(lambda m, i: m.get_value(i, 0) == '-')
cell = Gtk.CellRendererText()
self.cmb_language.pack_start(cell, True)
self.cmb_language.add_attribute(cell, 'text', 0)
def show(self):
"""
Show the SettingsDialog.
"""
self.window.show_all()
def on_switch_screen_lock_activate(self, switch, state):
"""
Event handler to the state change of the screen_lock switch.
Enable or disable the self.spin_time_to_screen_lock based on the state of the screen_lock switch.
"""
self.spin_time_to_screen_lock.set_sensitive(self.switch_screen_lock.get_active())
def on_switch_strict_break_activate(self, switch, state):
"""
Event handler to the state change of the postpone switch.
Enable or disable the self.spin_postpone_duration based on the state of the postpone switch.
"""
strict_break_enable = state # self.switch_strict_break.get_active()
self.switch_postpone.set_sensitive(not strict_break_enable)
if strict_break_enable:
self.switch_postpone.set_active(False)
def on_switch_postpone_activate(self, switch, state):
"""
Event handler to the state change of the postpone switch.
Enable or disable the self.spin_postpone_duration based on the state of the postpone switch.
"""
self.spin_postpone_duration.set_sensitive(self.switch_postpone.get_active())
def on_switch_audible_alert_activate(self, switch, state):
"""
Event handler to the state change of the audible_alert switch.
Show the information message dialog to install pyaudio if not installed.
"""
if state and Utility.pyaudio is None:
self.__show_message_dialog(self.language['messages']['audible_alert_disabled'], self.language['messages']['software_required'].format('pyaudio'))
switch.emit_stop_by_name('state-set')
self.switch_audible_alert.set_active(False)
def on_window_delete(self, *args):
"""
Event handler for Settings dialog close action.
"""
self.window.destroy()
def on_save_clicked(self, button):
"""
Event handler for Save button click.
"""
self.config['short_break_duration'] = self.spin_short_break_duration.get_value_as_int()
self.config['long_break_duration'] = self.spin_long_break_duration.get_value_as_int()
self.config['break_interval'] = self.spin_interval_between_two_breaks.get_value_as_int()
self.config['no_of_short_breaks_per_long_break'] = self.spin_short_between_long.get_value_as_int()
self.config['pre_break_warning_time'] = self.spin_time_to_prepare.get_value_as_int()
self.config['idle_time'] = self.spin_idle_time_to_pause.get_value_as_int()
self.config['postpone_duration'] = self.spin_postpone_duration.get_value_as_int()
self.config['shortcut_disable_time'] = self.spin_disable_keyboard_shortcut.get_value_as_int()
self.config['show_time_in_tray'] = self.switch_show_time_in_tray.get_active()
self.config['strict_break'] = self.switch_strict_break.get_active()
self.config['language'] = self.languages[self.cmb_language.get_active()]
self.config['time_to_screen_lock'] = self.spin_time_to_screen_lock.get_value_as_int()
self.config['enable_screen_lock'] = self.switch_screen_lock.get_active()
self.config['allow_postpone'] = self.switch_postpone.get_active()
# Check if pyaudio is installed when turning audible notifications on
if self.switch_audible_alert.get_active() and not Utility.pyaudio:
# Notify user that pyaudio is not installed
self.__show_message_dialog(self.language['messages']['audible_alert_disabled'], self.language['messages']['software_required'].format('pyaudio'))
self.config['audible_alert'] = False
else:
self.config['audible_alert'] = self.switch_audible_alert.get_active()
self.on_save_settings(self.config) # Call the provided save method
self.window.destroy() # Close the settings window
def on_cancel_clicked(self, button):
"""
Event handler for Cancel button click.
"""
self.window.destroy()
def __show_message_dialog(self, primary_text, secondary_text):
"""
Show a popup message dialog.
"""
dialog = Gtk.MessageDialog(self.window, 0, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, primary_text)
dialog.format_secondary_text(secondary_text)
dialog.run()
dialog.destroy()

View File

@ -1,299 +0,0 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2016 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <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:
"""
Create and show the tray icon along with the tray menu.
"""
def __init__(self, config, language, on_show_settings, on_show_about, on_enable, on_disable, on_quite):
logging.info("Initialize the tray icon")
self.on_show_settings = on_show_settings
self.on_show_about = on_show_about
self.on_quite = on_quite
self.on_enable = on_enable
self.on_disable = on_disable
self.language = language
self.dateTime = None
self.active = True
self.wakeup_time = None
self.idle_condition = threading.Condition()
self.lock = threading.Lock()
# Construct the tray icon
self.indicator = appindicator.Indicator.new(
APPINDICATOR_ID, "safeeyes_enabled", appindicator.IndicatorCategory.APPLICATION_STATUS)
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
self.initialize(config)
# Construct the context menu
self.menu = Gtk.Menu()
# Next break info menu item
self.item_info = Gtk.ImageMenuItem()
img_timer = Gtk.Image()
img_timer.set_from_icon_name("safeeyes_timer", 16)
self.item_info.set_image(img_timer)
self.item_separator = Gtk.SeparatorMenuItem()
self.item_enable = Gtk.MenuItem()
self.item_enable.connect('activate', self.on_enable_clicked)
self.item_disable = Gtk.MenuItem()
self.item_disable.connect('activate', self.on_disable_clicked)
self.sub_menu_disable = Gtk.Menu()
self.sub_menu_items = []
# Read disable options and build the sub menu
for disable_option in config['disable_options']:
time_in_minutes = disable_option['time']
# Validate time value
if not isinstance(time_in_minutes, int) or time_in_minutes <= 0:
logging.error('Invalid time in disable option: ' + str(time_in_minutes))
continue
time_unit = disable_option['unit'].lower()
if time_unit == 'seconds' or time_unit == 'second':
time_in_minutes = int(time_in_minutes / 60)
elif time_unit == 'minutes' or time_unit == 'minute':
time_in_minutes = int(time_in_minutes * 1)
elif time_unit == 'hours' or time_unit == 'hour':
time_in_minutes = int(time_in_minutes * 60)
else:
# Invalid unit
logging.error('Invalid unit in disable option: ' + str(disable_option))
continue
# Create submenu
sub_menu_item = Gtk.MenuItem()
sub_menu_item.connect('activate', self.on_disable_clicked, time_in_minutes)
self.sub_menu_items.append([sub_menu_item, disable_option['label'], disable_option['time']])
self.sub_menu_disable.append(sub_menu_item)
# Disable until restart submenu
self.sub_menu_item_until_restart = Gtk.MenuItem()
self.sub_menu_item_until_restart.connect('activate', self.on_disable_clicked, -1)
self.sub_menu_disable.append(self.sub_menu_item_until_restart)
# Add the sub menu to the enable/disable menu
self.item_disable.set_submenu(self.sub_menu_disable)
# Settings menu item
self.item_settings = Gtk.MenuItem()
self.item_settings.connect('activate', self.show_settings)
# About menu item
self.item_about = Gtk.MenuItem()
self.item_about.connect('activate', self.show_about)
# Quit menu item
self.item_quit = Gtk.MenuItem()
self.item_quit.connect('activate', self.quit_safe_eyes)
self.set_labels(language)
# At startup, no need for activate menu
self.item_enable.set_sensitive(False)
# Append all menu items and show the menu
self.menu.append(self.item_info)
self.menu.append(self.item_separator)
self.menu.append(self.item_enable)
self.menu.append(self.item_disable)
self.menu.append(self.item_settings)
self.menu.append(self.item_about)
self.menu.append(self.item_quit)
self.menu.show_all()
self.indicator.set_menu(self.menu)
def initialize(self, config):
"""
Initialize the tray icon by setting the config.
"""
self.config = config
def set_labels(self, language):
"""
Update the text of menu items based on the selected language.
"""
self.language = language
for entry in self.sub_menu_items:
entry[0].set_label(self.language['ui_controls'][entry[1]].format(entry[2]))
self.sub_menu_item_until_restart.set_label(self.language['ui_controls']['until_restart'])
self.item_enable.set_label(self.language['ui_controls']['enable'])
self.item_disable.set_label(self.language['ui_controls']['disable'])
if self.active:
if self.dateTime:
self.__set_next_break_info()
else:
if self.wakeup_time:
self.item_info.set_label(self.language['messages']['disabled_until_x'].format(Utility.format_time(self.wakeup_time)))
else:
self.item_info.set_label(self.language['messages']['disabled_until_restart'])
self.item_settings.set_label(self.language['ui_controls']['settings'])
self.item_about.set_label(self.language['ui_controls']['about'])
self.item_quit.set_label(self.language['ui_controls']['quit'])
def show_icon(self):
"""
Show the tray icon.
"""
Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.ACTIVE)
def hide_icon(self):
"""
Hide the tray icon.
"""
Utility.execute_main_thread(self.indicator.set_status, appindicator.IndicatorStatus.PASSIVE)
def quit_safe_eyes(self, *args):
"""
Handle Quit menu action.
This action terminates the application.
"""
self.on_quite()
with self.lock:
self.active = True
# Notify all schedulers
self.idle_condition.acquire()
self.idle_condition.notify_all()
self.idle_condition.release()
def show_settings(self, *args):
"""
Handle Settings menu action.
This action shows the Settings dialog.
"""
self.on_show_settings()
def show_about(self, *args):
"""
Handle About menu action.
This action shows the About dialog.
"""
self.on_show_about()
def next_break_time(self, dateTime):
"""
Update the next break time to be displayed in the menu and optionally in the tray icon.
"""
logging.info("Update next break information")
self.dateTime = dateTime
self.__set_next_break_info()
def __set_next_break_info(self):
"""
A private method to be called within this class to update the next break information using self.dateTime.
"""
formatted_time = Utility.format_time(self.dateTime)
message = self.language['messages']['next_break_at'].format(formatted_time)
# Update the tray icon label
if self.config.get('show_time_in_tray', False):
self.indicator.set_label(formatted_time, '')
else:
self.indicator.set_label('', '')
# Update the menu item label
Utility.execute_main_thread(self.item_info.set_label, message)
def on_enable_clicked(self, *args):
"""
Handle 'Enable Safe Eyes' menu action.
This action enables the application if it is currently disabled.
"""
# active = self.item_enable.get_active()
if not self.active:
with self.lock:
logging.info('Enable Safe Eyes')
self.active = True
self.indicator.set_icon("safeeyes_enabled")
self.item_info.set_sensitive(True)
self.item_enable.set_sensitive(False)
self.item_disable.set_sensitive(True)
self.on_enable()
# Notify all schedulers
self.idle_condition.acquire()
self.idle_condition.notify_all()
self.idle_condition.release()
def on_disable_clicked(self, *args):
"""
Handle the menu actions of all the sub menus of 'Disable Safe Eyes'.
This action disables the application if it is currently active.
"""
# active = self.item_enable.get_active()
if self.active and len(args) > 1:
logging.info('Disable Safe Eyes')
self.active = False
self.indicator.set_icon("safeeyes_disabled")
self.indicator.set_label('', '')
self.item_info.set_sensitive(False)
self.item_enable.set_sensitive(True)
self.item_disable.set_sensitive(False)
self.on_disable()
time_to_wait = args[1]
if time_to_wait <= 0:
self.wakeup_time = None
self.item_info.set_label(self.language['messages']['disabled_until_restart'])
else:
self.wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
Utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait)
self.item_info.set_label(self.language['messages']['disabled_until_x'].format(Utility.format_time(self.wakeup_time)))
def lock_menu(self):
"""
This method is called by the core to prevent user from disabling Safe Eyes after the notification.
"""
if self.active:
self.menu.set_sensitive(False)
def unlock_menu(self):
"""
This method is called by the core to activate the menu after the the break.
"""
if self.active:
self.menu.set_sensitive(True)
def __schedule_resume(self, time_minutes):
"""
Schedule a local timer to enable Safe Eyes after the given timeout.
"""
self.idle_condition.acquire()
self.idle_condition.wait(time_minutes * 60) # Convert to seconds
self.idle_condition.release()
with self.lock:
if not self.active:
Utility.execute_main_thread(self.item_enable.activate)

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
@ -15,485 +16,482 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module contains utility functions for Safe Eyes and its plugins.
"""
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkX11
from html.parser import HTMLParser
import errno
import imp
import json
import locale
import logging
import os
import shutil
import subprocess
import threading
from distutils.version import LooseVersion
from logging.handlers import RotatingFileHandler
import babel.dates, os, errno, re, subprocess, threading, logging, locale, json, shutil, wave
bin_directory = os.path.dirname(os.path.realpath(__file__))
home_directory = os.path.expanduser('~')
system_language_directory = os.path.join(bin_directory, 'config/lang')
config_directory = os.path.join(home_directory, '.config/safeeyes')
config_file_path = os.path.join(config_directory, 'safeeyes.json')
style_sheet_path = os.path.join(config_directory, 'style/safeeyes_style.css')
system_config_file_path = os.path.join(bin_directory, "config/safeeyes.json")
system_style_sheet_path = os.path.join(bin_directory, "config/style/safeeyes_style.css")
log_file_path = os.path.join(config_directory, 'safeeyes.log')
pyaudio = None
import babel.dates
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
gi.require_version('Gdk', '3.0')
def import_dependencies():
"""
Import the optional Python dependencies.
"""
try:
# Import pyaudio if exists
global pyaudio
pyaudio = __import__("pyaudio")
except ImportError:
logging.warning('Install pyaudio for audible notifications.')
def play_notification():
"""
Play the alert.wav
"""
if pyaudio:
logging.info('Playing audible alert')
CHUNK = 1024
try:
# Open the sound file
path = get_resource_path('alert.wav')
if path is None:
return
sound = wave.open(path, 'rb')
# Create a sound stream
wrapper = pyaudio.PyAudio()
stream = wrapper.open(format=wrapper.get_format_from_width(
sound.getsampwidth()),
channels=sound.getnchannels(),
rate=sound.getframerate(),
output=True)
# Write file data into the sound stream
data = sound.readframes(CHUNK)
while data != b'':
stream.write(data)
data = sound.readframes(CHUNK)
# Close steam
stream.stop_stream()
stream.close()
sound.close()
wrapper.terminate()
except Exception as e:
logging.warning('Unable to play audible alert')
logging.exception(e)
BIN_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
HOME_DIRECTORY = os.path.expanduser('~')
CONFIG_DIRECTORY = os.path.join(HOME_DIRECTORY, '.config/safeeyes')
CONFIG_FILE_PATH = os.path.join(CONFIG_DIRECTORY, 'safeeyes.json')
CONFIG_RESOURCE = os.path.join(CONFIG_DIRECTORY, 'resource')
SESSION_FILE_PATH = os.path.join(CONFIG_DIRECTORY, 'session.json')
STYLE_SHEET_PATH = os.path.join(CONFIG_DIRECTORY, 'style/safeeyes_style.css')
SYSTEM_CONFIG_FILE_PATH = os.path.join(BIN_DIRECTORY, "config/safeeyes.json")
SYSTEM_STYLE_SHEET_PATH = os.path.join(BIN_DIRECTORY, "config/style/safeeyes_style.css")
LOG_FILE_PATH = os.path.join(CONFIG_DIRECTORY, 'safeeyes.log')
SYSTEM_PLUGINS_DIR = os.path.join(BIN_DIRECTORY, 'plugins')
USER_PLUGINS_DIR = os.path.join(CONFIG_DIRECTORY, 'plugins')
LOCALE_PATH = os.path.join(BIN_DIRECTORY, 'config/locale')
DESKTOP_ENVIRONMENT = None
def get_resource_path(resource_name):
"""
Return the user-defined resource if a system resource is overridden by the user.
Otherwise, return the system resource. Return None if the specified resource does not exist.
"""
if resource_name is None:
return None
resource_location = os.path.join(config_directory, 'resource', resource_name)
if not os.path.isfile(resource_location):
resource_location = os.path.join(bin_directory, 'resource', resource_name)
if not os.path.isfile(resource_location):
logging.error('Resource not found: ' + resource_name)
resource_location = None
"""
Return the user-defined resource if a system resource is overridden by the user.
Otherwise, return the system resource. Return None if the specified resource does not exist.
"""
if resource_name is None:
return None
resource_location = os.path.join(CONFIG_RESOURCE, resource_name)
if not os.path.isfile(resource_location):
resource_location = os.path.join(BIN_DIRECTORY, 'resource', resource_name)
if not os.path.isfile(resource_location):
# Resource not found
resource_location = None
return resource_location
def system_idle_time():
"""
Get system idle time in minutes.
Return the idle time if xprintidle is available, otherwise return 0.
"""
try:
return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 60000 # Convert to minutes
except:
return 0
return resource_location
def start_thread(target_function, **args):
"""
Execute the function in a separate thread.
"""
thread = threading.Thread(target=target_function, kwargs=args)
thread.start()
"""
Execute the function in a separate thread.
"""
thread = threading.Thread(target=target_function, kwargs=args)
thread.start()
def execute_main_thread(target_function, args=None):
"""
Execute the given function in main thread.
"""
if args:
GLib.idle_add(lambda: target_function(args))
else:
GLib.idle_add(lambda: target_function())
"""
Execute the given function in main thread.
"""
if args:
GLib.idle_add(lambda: target_function(args))
else:
GLib.idle_add(target_function)
def is_active_window_skipped(skip_break_window_classes, take_break_window_classes, unfullscreen_allowed=False):
"""
Check for full-screen applications.
This method must be executed by the main thread. If not, it will cause to random failure.
"""
logging.info('Searching for full-screen application')
screen = Gdk.Screen.get_default()
active_window = screen.get_active_window()
if active_window:
active_xid = str(active_window.get_xid())
cmdlist = ['xprop', '-root', '-notype', '-id', active_xid, 'WM_CLASS', '_NET_WM_STATE']
try:
stdout = subprocess.check_output(cmdlist).decode('utf-8')
except subprocess.CalledProcessError:
logging.warning('Error in finding full-screen application')
pass
else:
if stdout:
is_fullscreen = 'FULLSCREEN' in stdout
# Extract the process name
process_names = re.findall('"(.+?)"', stdout)
if process_names:
process = process_names[1].lower()
if process in skip_break_window_classes:
return True
elif process in take_break_window_classes:
if is_fullscreen and unfullscreen_allowed:
try:
active_window.unfullscreen()
except:
logging.error('Error in unfullscreen the window ' + process)
pass
return False
return is_fullscreen
return False
def __system_locale():
"""
Return the system locale. If not available, return en_US.UTF-8.
"""
locale.setlocale(locale.LC_ALL, '')
system_locale = locale.getlocale(locale.LC_TIME)[0]
if not system_locale:
system_locale = 'en_US.UTF-8'
return system_locale
def system_locale():
"""
Return the system locale. If not available, return en_US.UTF-8.
"""
try:
locale.setlocale(locale.LC_ALL, '')
sys_locale = locale.getlocale(locale.LC_TIME)[0]
if not sys_locale:
sys_locale = 'en_US.UTF-8'
return sys_locale
except BaseException:
# Some systems does not return proper locale
return 'en_US.UTF-8'
def format_time(time):
"""
Format time based on the system time.
"""
system_locale = __system_locale()
return babel.dates.format_time(time, format='short', locale=system_locale)
"""
Format time based on the system time.
"""
sys_locale = system_locale()
return babel.dates.format_time(time, format='short', locale=sys_locale)
def mkdir(path):
"""
Create directory if not exists.
"""
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
logging.error('Error while creating ' + str(path))
raise
"""
Create directory if not exists.
"""
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
logging.error('Error while creating ' + str(path))
raise
def parse_language_code(lang_code):
"""
Convert the user defined language code to a valid one.
This includes converting to lower case and finding system locale language,
if the given lang_code code is 'system'.
"""
# Convert to lower case
lang_code = str(lang_code).lower()
# If it is system, use the system language
if lang_code == 'system':
logging.info('Use system language for Safe Eyes')
system_locale = __system_locale()
lang_code = system_locale[0:2].lower()
# Check whether translation is available for this language.
# If not available, use English by default.
language_file_path = os.path.join(system_language_directory, lang_code + '.json')
if not os.path.exists(language_file_path):
logging.warn('The language {} does not exist. Use English instead'.format(lang_code))
lang_code = 'en'
return lang_code
def load_json(json_path):
"""
Load the JSON file from the given path.
"""
json_obj = None
if os.path.isfile(json_path):
try:
with open(json_path) as config_file:
json_obj = json.load(config_file)
except BaseException:
pass
return json_obj
def load_language(lang_code):
"""
Load the desired language from the available list based on the preference.
"""
# Convert the user defined language code to a valid one
lang_code = parse_language_code(lang_code)
# Construct the translation file path
language_file_path = os.path.join(system_language_directory, lang_code + '.json')
language = None
# Read the language file and construct the json object
with open(language_file_path) as language_file:
language = json.load(language_file)
return language
def write_json(json_path, json_obj):
"""
Write the JSON object at the given path
"""
try:
with open(json_path, 'w') as json_file:
json.dump(json_obj, json_file, indent=4, sort_keys=True)
except BaseException:
pass
def read_lang_files():
"""
Read all the language translations and build a key-value mapping of language names
in English and ISO 639-1 (Filename without extension).
"""
languages = {}
for lang_file_name in os.listdir(system_language_directory):
lang_file_path = os.path.join(system_language_directory, lang_file_name)
if os.path.isfile(lang_file_path):
with open(lang_file_path) as lang_file:
lang = json.load(lang_file)
languages[lang_file_name.lower().replace('.json', '')] = lang['meta_info']['language_name']
def delete(file_path):
"""
Delete the given file or directory
"""
try:
os.remove(file_path)
except OSError:
pass
return languages
def check_plugin_dependencies(plugin_config):
"""
Check the plugin dependencies.
"""
# Check the desktop environment
if plugin_config['dependencies']['desktop_environments']:
# Plugin has restrictions on desktop environments
if DESKTOP_ENVIRONMENT not in plugin_config['dependencies']['desktop_environments']:
return _('Plugin does not support %s desktop environment') % DESKTOP_ENVIRONMENT
# Check the Python modules
for module in plugin_config['dependencies']['python_modules']:
if not module_exist(module):
return _("Please install the Python module '%s'") % module
# Check the shell commands
for command in plugin_config['dependencies']['shell_commands']:
if not command_exist(command):
return _("Please install the command-line tool '%s'") % command
# Check the resources
for resource in plugin_config['dependencies']['resources']:
if get_resource_path(resource) is None:
return _('Please add the resource %(resource)s to %(config_resource)s directory') % {'resource': resource, 'config_resource': CONFIG_RESOURCE}
return None
def load_plugins_config(safeeyes_config):
"""
Load all the plugins from the given directory.
"""
configs = []
for plugin in safeeyes_config.get('plugins'):
plugin_path = os.path.join(SYSTEM_PLUGINS_DIR, plugin['id'])
if not os.path.isdir(plugin_path):
# User plugin
plugin_path = os.path.join(USER_PLUGINS_DIR, plugin['id'])
plugin_config_path = os.path.join(plugin_path, 'config.json')
plugin_icon_path = os.path.join(plugin_path, 'icon.png')
plugin_module_path = os.path.join(plugin_path, 'plugin.py')
if not os.path.isfile(plugin_module_path):
return
icon = None
if os.path.isfile(plugin_icon_path):
icon = plugin_icon_path
else:
icon = get_resource_path('ic_plugin.png')
config = load_json(plugin_config_path)
if config is None:
continue
dependency_description = check_plugin_dependencies(config)
if dependency_description:
plugin['enabled'] = False
config['error'] = True
config['meta']['description'] = dependency_description
icon = get_resource_path('ic_warning.png')
else:
config['error'] = False
config['id'] = plugin['id']
config['icon'] = icon
config['enabled'] = plugin['enabled']
for setting in config['settings']:
setting['safeeyes_config'] = plugin['settings']
configs.append(config)
return configs
def desktop_environment():
"""
Detect the desktop environment.
"""
desktop_session = os.environ.get('DESKTOP_SESSION')
current_desktop = os.environ.get('XDG_CURRENT_DESKTOP')
if desktop_session is not None:
desktop_session = desktop_session.lower()
if desktop_session in ['gnome', 'unity', 'budgie-desktop', 'cinnamon', 'mate', 'xfce4', 'lxde', 'pantheon', 'fluxbox', 'blackbox', 'openbox', 'icewm', 'jwm', 'afterstep', 'trinity', 'kde']:
return desktop_session
elif (desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop)):
return 'xfce'
elif desktop_session.startswith('ubuntu'):
return 'unity'
elif desktop_session.startswith('lubuntu'):
return 'lxde'
elif 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true':
return 'kde'
elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
return 'gnome'
return 'unknown'
"""
Detect the desktop environment.
"""
global DESKTOP_ENVIRONMENT
desktop_session = os.environ.get('DESKTOP_SESSION')
current_desktop = os.environ.get('XDG_CURRENT_DESKTOP')
env = 'unknown'
if desktop_session is not None:
desktop_session = desktop_session.lower()
if desktop_session in ['gnome', 'unity', 'budgie-desktop', 'cinnamon', 'mate', 'xfce4', 'lxde', 'pantheon', 'fluxbox', 'blackbox', 'openbox', 'icewm', 'jwm', 'afterstep', 'trinity', 'kde']:
env = desktop_session
elif desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop):
env = 'xfce'
elif desktop_session.startswith('lubuntu'):
env = 'lxde'
elif 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true':
env = 'kde'
elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
env = 'gnome'
elif desktop_session.startswith('ubuntu'):
env = 'unity'
DESKTOP_ENVIRONMENT = env
return env
def lock_screen_command():
"""
Function tries to detect the screensaver command based on the current envinroment
Possible results:
Gnome, Unity, Budgie: ['gnome-screensaver-command', '--lock']
Cinnamon: ['cinnamon-screensaver-command', '--lock']
Pantheon, LXDE: ['light-locker-command', '--lock']
Mate: ['mate-screensaver-command', '--lock']
KDE: ['qdbus', 'org.freedesktop.ScreenSaver', '/ScreenSaver', 'Lock']
XFCE: ['xflock4']
Otherwise: None
"""
desktop_session = os.environ.get('DESKTOP_SESSION')
current_desktop = os.environ.get('XDG_CURRENT_DESKTOP')
if desktop_session is not None:
desktop_session = desktop_session.lower()
if ('xfce' in desktop_session or desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop)) and command_exist('xflock4'):
return ['xflock4']
elif desktop_session == 'cinnamon' and command_exist('cinnamon-screensaver-command'):
return ['cinnamon-screensaver-command', '--lock']
elif (desktop_session == 'pantheon' or desktop_session.startswith('lubuntu')) and command_exist('light-locker-command'):
return ['light-locker-command', '--lock']
elif desktop_session == 'mate' and command_exist('mate-screensaver-command'):
return ['mate-screensaver-command', '--lock']
elif desktop_session == 'kde' or 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true':
return ['qdbus', 'org.freedesktop.ScreenSaver', '/ScreenSaver', 'Lock']
elif desktop_session in ['gnome', 'unity', 'budgie-desktop'] or desktop_session.startswith('ubuntu'):
if command_exist('gnome-screensaver-command'):
return ['gnome-screensaver-command', '--lock']
else:
# From Gnome 3.8 no gnome-screensaver-command
return ['dbus-send', '--type=method_call', '--dest=org.gnome.ScreenSaver', '/org/gnome/ScreenSaver', 'org.gnome.ScreenSaver.Lock']
elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
if 'deprecated' not in os.environ.get('GNOME_DESKTOP_SESSION_ID') and command_exist('gnome-screensaver-command'):
# Gnome 2
return ['gnome-screensaver-command', '--lock']
elif command_exist('xscreensaver-command'):
# this will fail if the daemon is not running
return os.system('xscreensaver-command -version > /dev/null') == 0
return None
def lock_desktop(command):
"""
Lock the screen using the predefined commands
"""
if command:
try:
subprocess.Popen(command)
except Exception as e:
logging.error('Error in executing the commad' + str(command) + ' to lock screen')
def html_to_text(html):
"""
Convert HTML to plain text
"""
extractor = __HTMLTextExtractor()
extractor.feed(html)
return extractor.get_data()
def execute_command(command, args=[]):
"""
Execute the shell command without waiting for its response.
"""
if command:
command_to_execute = []
if isinstance(command, str):
command_to_execute.append(command)
else:
command_to_execute.extend(command)
if args:
command_to_execute.extend(args)
try:
subprocess.Popen(command_to_execute)
except BaseException:
logging.error('Error in executing the command ' + str(command))
def command_exist(command):
"""
Check whether the given command exist in the system or not.
"""
if shutil.which(command):
return True
else:
return False
"""
Check whether the given command exist in the system or not.
"""
if shutil.which(command):
return True
return False
def module_exist(module):
"""
Check wther the given Python module exists or not.
"""
try:
imp.find_module(module)
return True
except ImportError:
return False
def merge_configs(new_config, old_config):
"""
Merge the values of old_config into the new_config.
"""
new_config = new_config.copy()
new_config.update(old_config)
return new_config
"""
Merge the values of old_config into the new_config.
"""
new_config = new_config.copy()
new_config.update(old_config)
return new_config
def __initialize_safeeyes():
"""
Create the config file and style sheet in ~/.config/safeeyes directory.
"""
logging.info('Copy the config files to ~/.config/safeeyes')
def initialize_safeeyes():
"""
Create the config file and style sheet in ~/.config/safeeyes directory.
"""
logging.info('Copy the config files to ~/.config/safeeyes')
style_dir_path = os.path.join(home_directory, '.config/safeeyes/style')
startup_dir_path = os.path.join(home_directory, '.config/autostart')
style_dir_path = os.path.join(HOME_DIRECTORY, '.config/safeeyes/style')
startup_dir_path = os.path.join(HOME_DIRECTORY, '.config/autostart')
# Remove the ~/.config/safeeyes directory
shutil.rmtree(config_directory, ignore_errors=True)
# Remove the ~/.config/safeeyes directory
delete(os.path.join(CONFIG_DIRECTORY, 'safeeyes.json'))
# Remove the startup file
try:
os.remove(os.path.join(home_directory, os.path.join(startup_dir_path, 'safeeyes.desktop')))
except:
pass
# Remove the startup file
delete(os.path.join(HOME_DIRECTORY, os.path.join(startup_dir_path, 'safeeyes.desktop')))
# Create the ~/.config/safeeyes/style directory
mkdir(style_dir_path)
mkdir(startup_dir_path)
# Create the ~/.config/safeeyes/style directory
mkdir(style_dir_path)
mkdir(startup_dir_path)
# Copy the safeeyes.json
shutil.copy2(system_config_file_path, config_file_path)
# Copy the safeeyes.json
shutil.copy2(SYSTEM_CONFIG_FILE_PATH, CONFIG_FILE_PATH)
# Copy the new startup file
try:
os.symlink("/usr/share/applications/safeeyes.desktop", os.path.join(startup_dir_path, 'safeeyes.desktop'))
except OSError as exc:
pass
# Copy the new startup file
try:
os.symlink("/usr/share/applications/safeeyes.desktop", os.path.join(startup_dir_path, 'safeeyes.desktop'))
except OSError:
pass
# Copy the new style sheet
if not os.path.isfile(style_sheet_path):
shutil.copy2(system_style_sheet_path, style_sheet_path)
# Copy the new style sheet
if not os.path.isfile(STYLE_SHEET_PATH):
shutil.copy2(SYSTEM_STYLE_SHEET_PATH, STYLE_SHEET_PATH)
def intialize_logging():
"""
Initialize the logging framework using the Safe Eyes specific configurations.
"""
# Create the directory to store log file if not exist
if not os.path.exists(config_directory):
try:
os.makedirs(config_directory)
except:
pass
def intialize_logging(debug):
"""
Initialize the logging framework using the Safe Eyes specific configurations.
"""
# Create the directory to store log file if not exist
if not os.path.exists(CONFIG_DIRECTORY):
try:
os.makedirs(CONFIG_DIRECTORY)
except OSError:
pass
# Configure logging.
log_formatter = logging.Formatter('%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s')
# Configure logging.
root_logger = logging.getLogger()
log_formatter = logging.Formatter('%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s')
# Apped the logs and overwrite once reached 5MB
handler = RotatingFileHandler(log_file_path, mode='a', maxBytes=5 * 1024 * 1024, backupCount=2, encoding=None, delay=0)
handler.setFormatter(log_formatter)
handler.setLevel(logging.INFO)
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
root_logger.addHandler(handler)
# Append the logs and overwrite once reached 5MB
file_handler = RotatingFileHandler(LOG_FILE_PATH, mode='a', maxBytes=5 * 1024 * 1024, backupCount=2, encoding=None, delay=0)
file_handler.setFormatter(log_formatter)
if debug:
file_handler.setLevel(logging.DEBUG)
root_logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setFormatter(log_formatter)
root_logger.addHandler(console_handler)
else:
file_handler.setLevel(logging.INFO)
root_logger.setLevel(logging.INFO)
root_logger.addHandler(file_handler)
def read_config():
"""
Read the configuration from the config directory.
If does not exist or outdated by major version, copy the system config and
startup script to user directory.
If the user config is outdated by minor version, update the config by the new values.
"""
logging.info('Reading the configuration file')
if not os.path.isfile(config_file_path):
logging.info('Safe Eyes configuration file not found')
__initialize_safeeyes()
# Read the configurations
with open(config_file_path) as config_file:
user_config = json.load(config_file)
with open(system_config_file_path) as config_file:
system_config = json.load(config_file)
user_config_version = str(user_config['meta']['config_version'])
system_config_version = str(system_config['meta']['config_version'])
if LooseVersion(user_config_version) < LooseVersion(system_config_version):
# Outdated user config
logging.info('Update the old config version {} with new config version {}'.format(user_config_version, system_config_version))
user_config_major_version = user_config_version.split('.')[0]
system_config_major_version = system_config_version.split('.')[0]
if LooseVersion(user_config_major_version) < LooseVersion(system_config_major_version):
# Major version change
__initialize_safeeyes()
# Update the user_config
user_config = system_config
else:
# Minor version change
new_config = system_config.copy()
new_config.update(user_config)
# Update the version
new_config['meta']['config_version'] = system_config_version
# Write the configuration to file
with open(config_file_path, 'w') as config_file:
json.dump(new_config, config_file, indent=4, sort_keys=True)
# Update the user_config
user_config = new_config
return user_config
def __open_plugin_config(plugins_dir, plugin_id):
"""
Open the given plugin's configuration.
"""
plugin_config_path = os.path.join(plugins_dir, plugin_id, 'config.json')
plugin_module_path = os.path.join(plugins_dir, plugin_id, 'plugin.py')
if not os.path.isfile(plugin_config_path) or not os.path.isfile(plugin_module_path):
# Either the config.json or plugin.py is not available
return None
return load_json(plugin_config_path)
class __HTMLTextExtractor(HTMLParser):
"""
Helper class to convert HTML to text
"""
def __init__(self):
self.reset()
self.strict = False
self.convert_charrefs = True
self.fed = []
def __update_plugin_config(plugin, plugin_config, config):
"""
Update the plugin configuration.
"""
if plugin_config is None:
config['plugins'].remove(plugin)
else:
if LooseVersion(plugin['version']) < LooseVersion(plugin_config['meta']['version']):
# Update the configuration
plugin['version'] = plugin_config['meta']['version']
setting_ids = []
# Add the new settings
for setting in plugin_config['settings']:
setting_ids.append(setting['id'])
if plugin['settings'].get(setting['id'], None) is None:
plugin['settings'][setting['id']] = setting['default']
# Remove the removed ids
keys_to_remove = []
for key in plugin['settings']:
if key not in setting_ids:
keys_to_remove.append(key)
for key in keys_to_remove:
del plugin['settings'][key]
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def __add_plugin_config(plugin_id, plugin_config, safe_eyes_config):
"""
"""
if plugin_config is None:
return
config = {}
config['id'] = plugin_id
config['enabled'] = False # By default plugins are disabled
config['version'] = plugin_config['meta']['version']
if plugin_config['settings']:
config['settings'] = {}
for setting in plugin_config['settings']:
config['settings'][setting['id']] = setting['default']
safe_eyes_config['plugins'].append(config)
def merge_plugins(config):
"""
Merge plugin configurations with Safe Eyes configuration.
"""
system_plugins = None
user_plugins = None
# Load system plugins id
if os.path.isdir(SYSTEM_PLUGINS_DIR):
system_plugins = os.listdir(SYSTEM_PLUGINS_DIR)
else:
system_plugins = []
# Load user plugins id
if os.path.isdir(USER_PLUGINS_DIR):
user_plugins = os.listdir(USER_PLUGINS_DIR)
else:
user_plugins = []
# Create a list of existing plugins
for plugin in config['plugins']:
plugin_id = plugin['id']
if plugin_id in system_plugins:
plugin_config = __open_plugin_config(SYSTEM_PLUGINS_DIR, plugin_id)
__update_plugin_config(plugin, plugin_config, config)
system_plugins.remove(plugin_id)
elif plugin_id in user_plugins:
plugin_config = __open_plugin_config(USER_PLUGINS_DIR, plugin_id)
__update_plugin_config(plugin, plugin_config, config)
user_plugins.remove(plugin_id)
else:
config['plugins'].remove(plugin)
# Add all system plugins
for plugin_id in system_plugins:
plugin_config = __open_plugin_config(SYSTEM_PLUGINS_DIR, plugin_id)
__add_plugin_config(plugin_id, plugin_config, config)
# Add all user plugins
for plugin_id in user_plugins:
plugin_config = __open_plugin_config(USER_PLUGINS_DIR, plugin_id)
__add_plugin_config(plugin_id, plugin_config, config)
def open_session():
"""
Open the last session.
"""
logging.info('Reading the session file')
session = load_json(SESSION_FILE_PATH)
if session is None:
session = {'plugin': {}}
return session
def create_gtk_builder(glade_file):
"""
Create a Gtk builder and load the glade file.
"""
builder = Gtk.Builder()
builder.set_translation_domain('safeeyes')
builder.add_from_file(glade_file)
# Tranlslate all sub components
for obj in builder.get_objects():
if (not isinstance(obj, Gtk.SeparatorMenuItem)) and hasattr(obj, "get_label"):
label = obj.get_label()
if label is not None:
obj.set_label(_(label))
elif hasattr(obj, "get_title"):
title = obj.get_title()
if title is not None:
obj.set_title(_(title))
return builder

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python3
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
@ -17,279 +16,124 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, gi, json, dbus, logging, psutil, sys
"""
Safe Eyes is a utility to remind you to take break frequently to protect your eyes from eye strain.
"""
import argparse
import gettext
import locale
import logging
import sys
from threading import Timer
from dbus.mainloop.glib import DBusGMainLoop
import gi
import psutil
from safeeyes import Utility
from safeeyes.model import Config
from safeeyes.SafeEyes import SafeEyes
from safeeyes.SafeEyes import SAFE_EYES_VERSION
from safeeyes.rpc import RPCClient
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from safeeyes.AboutDialog import AboutDialog
from safeeyes.BreakScreen import BreakScreen
from safeeyes.Notification import Notification
from safeeyes.Plugins import Plugins
from safeeyes.SafeEyesCore import SafeEyesCore
from safeeyes.SettingsDialog import SettingsDialog
from safeeyes.TrayIcon import TrayIcon
from safeeyes import Utility
# Define necessary paths
break_screen_glade = os.path.join(Utility.bin_directory, "glade/break_screen.glade")
settings_dialog_glade = os.path.join(Utility.bin_directory, "glade/settings_dialog.glade")
about_dialog_glade = os.path.join(Utility.bin_directory, "glade/about_dialog.glade")
is_active = True
SAFE_EYES_VERSION = "1.2.2"
gettext.install('safeeyes', Utility.LOCALE_PATH)
def show_settings():
"""
Listen to tray icon Settings action and send the signal to Settings dialog.
"""
logging.info("Show Settings dialog")
able_to_lock_screen = False
if system_lock_command:
able_to_lock_screen = True
settings_dialog = SettingsDialog(config, language, Utility.read_lang_files(), able_to_lock_screen, save_settings, settings_dialog_glade)
settings_dialog.show()
def __running():
"""
Check if SafeEyes is already running.
"""
process_count = 0
for proc in psutil.process_iter():
if not proc.cmdline:
continue
try:
# Check if safeeyes is in process arguments
if callable(proc.cmdline):
# Latest psutil has cmdline function
cmd_line = proc.cmdline()
else:
# In older versions cmdline was a list object
cmd_line = proc.cmdline
if ('python3' in cmd_line[0] or 'python' in cmd_line[0]) and ('safeeyes' in cmd_line[1] or 'safeeyes' in cmd_line):
process_count += 1
if process_count > 1:
return True
# Ignore if process does not exist or does not have command line args
except (IndexError, psutil.NoSuchProcess):
pass
return False
def show_about():
"""
Listen to tray icon About action and send the signal to About dialog.
"""
logging.info("Show About dialog")
about_dialog = AboutDialog(about_dialog_glade, SAFE_EYES_VERSION, language)
about_dialog.show()
def show_notification():
"""
Receive the signal from core and pass it to the Notification.
"""
if config['strict_break']:
Utility.execute_main_thread(tray_icon.lock_menu)
plugins.pre_notification(context)
notification.show(config['pre_break_warning_time'])
def show_alert(message, image_name):
"""
Receive the break signal from core and pass it to the break screen.
"""
logging.info("Show the break screen")
notification.close()
plugins_data = plugins.pre_break(context)
break_screen.show_message(message, Utility.get_resource_path(image_name), plugins_data)
if config['strict_break'] and is_active:
Utility.execute_main_thread(tray_icon.unlock_menu)
def close_alert(audible_alert_on):
"""
Receive the stop break signal from core and pass it to the break screen.
"""
logging.info("Close the break screen")
if config['enable_screen_lock'] and context['break_type'] == 'long':
# Lock the screen before closing the break screen
Utility.lock_desktop(system_lock_command)
break_screen.close()
if audible_alert_on:
Utility.play_notification()
plugins.post_break(context)
def on_quit():
"""
Listen to the tray menu quit action and stop the core, notification and the app itself.
"""
logging.info("Quit Safe Eyes")
plugins.exit(context)
core.stop()
notification.quite()
Gtk.main_quit()
def handle_suspend_callback(sleeping):
"""
If the system goes to sleep, Safe Eyes stop the core if it is already active.
If it was active, Safe Eyes will become active after wake up.
"""
if sleeping:
# Sleeping / suspending
if is_active:
core.stop()
logging.info("Stopped Safe Eyes due to system suspend")
else:
# Resume from sleep
if is_active:
core.start()
logging.info("Resumed Safe Eyes after system wakeup")
def handle_system_suspend():
"""
Setup system suspend listener.
"""
DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus.add_signal_receiver(handle_suspend_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
def on_skipped():
"""
Listen to break screen Skip action and send the signal to core.
"""
logging.info("User skipped the break")
if config['enable_screen_lock'] and context['break_type'] == 'long' and context.get('count_down', 0) >= config['time_to_screen_lock']:
# Lock the screen before closing the break screen
Utility.lock_desktop(system_lock_command)
core.skip_break()
plugins.post_break(context)
def on_postponed():
"""
Listen to break screen Postpone action and send the signal to core.
"""
logging.info("User postponed the break")
if config['enable_screen_lock'] and context['break_type'] == 'long' and context.get('count_down', 0) >= config['time_to_screen_lock']:
# Lock the screen before closing the break screen
Utility.lock_desktop(system_lock_command)
core.postpone_break()
def save_settings(config):
"""
Listen to Settings dialog Save action and write to the config file.
"""
global language
logging.info("Saving settings to safeeyes.json")
# Stop the Safe Eyes core
if is_active:
core.stop()
# Write the configuration to file
with open(Utility.config_file_path, 'w') as config_file:
json.dump(config, config_file, indent=4, sort_keys=True)
# Reload the language translation
language = Utility.load_language(config['language'])
tray_icon.initialize(config)
tray_icon.set_labels(language)
logging.info("Initialize SafeEyesCore with modified settings")
# Restart the core and intialize the components
core.initialize(config, language)
break_screen.initialize(config, language)
notification.initialize(language)
if is_active:
# 1 sec delay is required to give enough time for core to be stopped
Timer(1.0, core.start).start()
def enable_safeeyes():
"""
Listen to tray icon enable action and send the signal to core.
"""
global is_active
is_active = True
core.start()
def disable_safeeyes():
"""
Listen to tray icon disable action and send the signal to core.
"""
global is_active
is_active = False
core.stop()
def running():
"""
Check if SafeEyes is already running.
"""
process_count = 0
for proc in psutil.process_iter():
if not proc.cmdline:
continue
try:
# Check if safeeyes is in process arguments
if callable(proc.cmdline):
# Latest psutil has cmdline function
cmd_line = proc.cmdline()
else:
# In older versions cmdline was a list object
cmd_line = proc.cmdline
if ('python3' in cmd_line[0] or 'python' in cmd_line[0]) and ('safeeyes' in cmd_line[1] or 'safeeyes' in cmd_line):
process_count += 1
if process_count > 1:
return True
# Ignore if process does not exist or does not have command line args
except (IndexError, psutil.NoSuchProcess):
pass
return False
def __evaluate_arguments(args, safe_eyes):
"""
Evaluate the arguments and execute the operations.
"""
if args.about:
Utility.execute_main_thread(safe_eyes.show_about)
elif args.disable:
Utility.execute_main_thread(safe_eyes.disable_safeeyes)
elif args.enable:
Utility.execute_main_thread(safe_eyes.enable_safeeyes)
elif args.settings:
Utility.execute_main_thread(safe_eyes.show_settings)
elif args.take_break:
Utility.execute_main_thread(safe_eyes.take_break)
def main():
"""
Start the Safe Eyes.
"""
# Initialize the logging
Utility.intialize_logging()
"""
Start the Safe Eyes.
"""
system_locale = gettext.translation('safeeyes', localedir=Utility.LOCALE_PATH, languages=[Utility.system_locale(), 'en_US'], fallback=True)
system_locale.install()
# locale.bindtextdomain is required for Glade files
# gettext.bindtextdomain(gettext.textdomain(), Utility.LOCALE_PATH)
locale.bindtextdomain('safeeyes', Utility.LOCALE_PATH)
logging.info("Starting Safe Eyes")
parser = argparse.ArgumentParser(prog='safeeyes', description=_('description'))
group = parser.add_mutually_exclusive_group()
group.add_argument('-a', '--about', help=_('show the about dialog'), action='store_true')
group.add_argument('-d', '--disable', help=_('disable the currently running safeeyes instance'), action='store_true')
group.add_argument('-e', '--enable', help=_('enable the currently running safeeyes instance'), action='store_true')
group.add_argument('-q', '--quit', help=_('quit the running safeeyes instance and exit'), action='store_true')
group.add_argument('-s', '--settings', help=_('show the settings dialog'), action='store_true')
group.add_argument('-t', '--take-break', help=_('Take a break now').lower(), action='store_true')
parser.add_argument('--debug', help=_('start safeeyes in debug mode'), action='store_true')
parser.add_argument('--version', action='version', version='%(prog)s ' + SAFE_EYES_VERSION)
args = parser.parse_args()
# Import the dependencies
Utility.import_dependencies()
# Initialize the logging
Utility.intialize_logging(args.debug)
config = Config()
if not running():
global break_screen
global core
global config
global notification
global tray_icon
global language
global context
global plugins
global system_lock_command
config = Utility.read_config()
context = {}
language = Utility.load_language(config['language'])
# Get the lock command only one time
if config['lock_screen_command']:
system_lock_command = config['lock_screen_command']
else:
system_lock_command = Utility.lock_screen_command()
# Initialize the Safe Eyes Context
context['version'] = SAFE_EYES_VERSION
context['desktop'] = Utility.desktop_environment()
tray_icon = TrayIcon(config, language, show_settings, show_about, enable_safeeyes, disable_safeeyes, on_quit)
break_screen = BreakScreen(context, on_skipped, on_postponed, break_screen_glade, Utility.style_sheet_path)
break_screen.initialize(config, language)
notification = Notification(context, language)
plugins = Plugins(config)
core = SafeEyesCore(context, show_notification, show_alert, close_alert, break_screen.show_count_down, tray_icon.next_break_time)
core.initialize(config, language)
plugins.start(context) # Call the start method of all plugins
core.start()
handle_system_suspend()
Gtk.main()
else:
logging.info('Another instance of safeeyes is already running')
sys.exit(0)
if __running():
logging.info("Safe Eyes is already running")
rpc_client = RPCClient(config.get('rpc_port'))
if args.about:
rpc_client.show_about()
elif args.disable:
rpc_client.disable_safeeyes()
elif args.enable:
rpc_client.enable_safeeyes()
elif args.settings:
rpc_client.show_settings()
elif args.take_break:
rpc_client.take_break()
elif args.quit:
rpc_client.quit()
else:
# Default behavior is opening settings
rpc_client.show_settings()
sys.exit(0)
elif not args.quit:
logging.info("Starting Safe Eyes")
safeeyes = SafeEyes(system_locale, config)
safeeyes.start()
Timer(1.0, lambda: __evaluate_arguments(args, safeeyes)).start()
Gtk.main()
if __name__ == '__main__':
main()
main()

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Català",
"language_name_en": "Catalan"
},
"app_info": {
"description": "Safe Eyes protegeix els vostres ulls de la fatiga visual (astenopia) recordant-vos que feu petits descansos mentre esteu treballant amb l'ordinador."
},
"exercises": {
"short_break_close_eyes": "Tanqueu fortament els ulls",
"short_break_roll_eyes": "Moveu els ulls a banda i banda",
"short_break_rotate_clockwise": "Gireu els ulls en sentit horari",
"short_break_rotate_counter_clockwise": "Gireu els ulls en sentit antihorari",
"short_break_blink": "Parpallegeu",
"short_break_focus_far_distance": "Fixeu la mirada en un punt llunyà",
"short_break_drink_water": "Bebeu una mica d'aigua",
"long_break_walk": "Camineu una estona",
"long_break_lean_back": "Reclineu-vos sobre la cadira i relaxeu-vos"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Prepareu-vos per a una pausa en {} segons",
"ready_for_a_long_break": "Prepareu-vos per a una pausa en {} segons",
"disabled_until_restart": "Inhabilita fins que es reinicïi",
"disabled_until_x": "Inhabilitat fins {}",
"next_break_at": "Propera pausa a les {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Quant a",
"allow_postpone": "Permet posposar les pauses",
"audible_alert": "Alerta sonora en finalitzar una pausa",
"cancel": "Canceŀla",
"close": "Tanca",
"disable": "Desactiva Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Activa Safe Eyes",
"enable_screen_lock": "Bloca la pantalla després de cada pausa llarga",
"for_x_hour": "Durant {} hora",
"for_x_hours": "Durant {} hores",
"for_x_minutes": "Durant {} minuts",
"idle_time": "Temps mínim inactiu per a pausar (en minuts)",
"interval_between_two_breaks": "Interval entre dues pauses (en minuts)",
"language": "Llengua",
"license": "Llicència",
"long_break_duration": "Durada d'una pausa llarga (en segons)",
"no_of_short_breaks_between_two_long_breaks": "Nombre de pauses breus entre dues pauses llargues",
"postpone": "Posposa",
"postpone_duration": "Durada d'una posposició (en minuts)",
"quit": "Tanca",
"save": "Desa",
"settings": "Configuració",
"short_break_duration": "Durada d'una pausa breu (en segons)",
"show_time_in_tray": "Mostra l'hora de la propera pausa a l'àrea de notificacions",
"skip": "Omet",
"strict_break": "Pausa estrica (sense botó per ometre-la)",
"system_language": "Idioma del sistema",
"time_to_prepare_for_break": "Temps per preparar-se per a una pausa (en segons)",
"time_to_screen_lock": "Temps màxim per saltar-se una pausa, evitant la pantalla de bloqueig (en segons)",
"until_restart": "Fins que es reinicïi"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Čeština",
"language_name_en": "Czech"
},
"app_info": {
"description": "Safe Eyes chrání vaše oči před následky přetěžování (asthenopie), když dlouho hledíte do obrazovky počítače, připomínáním potřebných přestávek."
},
"exercises": {
"short_break_close_eyes": "Zavřete oči",
"short_break_roll_eyes": "Zakroužete očima (několikrát na každou stranu)",
"short_break_rotate_clockwise": "Zakroužete očima ve směru hodinových ručiček",
"short_break_rotate_counter_clockwise": "Zakroužete očima proti směru hodinových ručiček",
"short_break_blink": "Zamrkejte",
"short_break_focus_far_distance": "Zaostřete pohled na nějaký vzdálený objekt",
"short_break_drink_water": "Napijte se vody",
"long_break_walk": "Na chvíli se projděte",
"long_break_lean_back": "Opřete se zády do židle a uvolněte se"
},
"messages": {
"audible_alert_disabled": "Nedaří se zapnout zvuková oznamování",
"ready_for_a_short_break": "Připravte se na krátkou přestávku za {} sekund",
"ready_for_a_long_break": "Připravte se na dlouhou přestávku za {} sekund",
"disabled_until_restart": "Pozastaveno do restartu",
"disabled_until_x": "Pozastaveno do {}",
"next_break_at": "Příští přestávka v {}",
"software_required": "Tato funkce vyžaduje instalaci {}"
},
"ui_controls": {
"about": "O aplikaci",
"allow_postpone": "Umožnit odkládání přestávek",
"audible_alert": "Zvukové upozornění na konci přestávky",
"cancel": "Storno",
"close": "Zavřít",
"disable": "Pozastavit Safe Eyes",
"disable_keyboard_shortcut": "Délka (s) vypnutí klávesové zkratky (prevence neúmyslného přeskočení)",
"enable": "Spustit Safe Eyes",
"enable_screen_lock": "Po uplynutí každé velké přestávky ponechat obrazovku uzamčenou",
"for_x_hour": "Na {} hodinu",
"for_x_hours": "Na {} hodiny",
"for_x_minutes": "Na {} minut",
"idle_time": "Pozastavit časovač při nečinnosti delší než (minut)",
"interval_between_two_breaks": "Interval mezi dvěma po sobě jdoucími přestávkami",
"language": "Jazyk",
"license": "Licence",
"long_break_duration": "Trvání dlouhé přestávky (v sekundách)",
"no_of_short_breaks_between_two_long_breaks": "Počet krátkých přestávek mezi dvěma dlouhými",
"postpone": "Odložit",
"postpone_duration": "O kolik odložit (v minutách)",
"quit": "Ukončit",
"save": "Uložit",
"settings": "Nastavení",
"short_break_duration": "Trvání krátké přestávky (v sekundách)",
"show_time_in_tray": "Zobrazovat čas příští přestávky v oznamovací oblasti",
"skip": "Přeskočit",
"strict_break": "Povinná přestávka (skrýt tlačítko pro přeskočení)",
"system_language": "Jazyk systému",
"time_to_prepare_for_break": "Jakou dobu dopředu hlásit blížící se přestávku (v sekundách)",
"time_to_screen_lock": "Časový limit pro přeskočení přestávky a tedy obejití zámku obrazovky (v sekundách)",
"until_restart": "Do restartu počítače"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Deutsch",
"language_name_en": "German"
},
"app_info": {
"description": "Safe Eyes schützt Ihre Augen vor Überlastung (Asthenopie), indem es Sie bei längerer Arbeit am Computer an regelmäßige Pausen erinnert."
},
"exercises": {
"short_break_close_eyes": "Schließen Sie die Augen",
"short_break_roll_eyes": "Rollen Sie mit den Augen",
"short_break_rotate_clockwise": "Rotieren Sie mit den Augen im Uhrzeigersinn",
"short_break_rotate_counter_clockwise": "Rotieren Sie mit den Augen gegen den Uhrzeigersinn",
"short_break_blink": "Blinzeln Sie",
"short_break_focus_far_distance": "Fokussieren Sie sich auf einen Punkt in weiter Ferne",
"short_break_drink_water": "Trinken Sie ein wenig Wasser",
"long_break_walk": "Gehen Sie ein wenig",
"long_break_lean_back": "Lehnen Sie sich zurück und entspannen Sie sich"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Nächste Pause in {} Sekunden",
"ready_for_a_long_break": "Nächste Pause in {} Sekunden",
"disabled_until_restart": "Deaktiviert bis zum Neustart",
"disabled_until_x": "Deaktiviert bis {}",
"next_break_at": "Nächste Pause um {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Über",
"allow_postpone": "Erlaube Verschieben von Pausen",
"audible_alert": "Akustisches Signal am Ende der Pause",
"cancel": "Abbrechen",
"close": "Schließen",
"disable": "Safe Eyes deaktivieren",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Safe Eyes aktivieren",
"enable_screen_lock": "Sperrt den Bildschirm nach einer langen Pause",
"for_x_hour": "Für {} Stunde",
"for_x_hours": "Für {} Stunden",
"for_x_minutes": "Für {} Minuten",
"idle_time": "Minimale Leerlaufzeit zum Pausieren (in Minuten)",
"interval_between_two_breaks": "Intervall zwischen zwei Pausen",
"language": "Sprache",
"license": "Lizenz",
"long_break_duration": "Lange-Pause-Intervall (in Sekunden)",
"no_of_short_breaks_between_two_long_breaks": "Anzahl von kleinen Pausen zwischen zwei langen Pausen",
"postpone": "Verzögernn",
"postpone_duration": "Verzögerungsdauer (in Minuten)",
"quit": "Schließen",
"save": "Speichern",
"settings": "Einstellungen",
"short_break_duration": "Kleine-Pause-Intervall (in Sekunden)",
"show_time_in_tray": "Zeige Zeit für nächste Pause im Bereich der Systemnotifikationen",
"skip": "Überspringen",
"strict_break": "Strikte Pause (Überspringen nicht möglich)",
"system_language": "Systemsprache",
"time_to_prepare_for_break": "Zeit zur Vorbereitung für die Pause (in Sekunden)",
"time_to_screen_lock": "Maximale Zeit zum Überspringen der Bildschirmsperre (in Sekunden)",
"until_restart": "Bis zum Neustart"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "English",
"language_name_en": "English"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
},
"exercises": {
"short_break_close_eyes": "Tightly close your eyes",
"short_break_roll_eyes": "Roll your eyes a few times to each side",
"short_break_rotate_clockwise": "Rotate your eyes in clockwise direction",
"short_break_rotate_counter_clockwise": "Rotate your eyes in counterclockwise direction",
"short_break_blink": "Blink your eyes",
"short_break_focus_far_distance": "Focus on a point in the far distance",
"short_break_drink_water": "Have some water",
"long_break_walk": "Walk for a while",
"long_break_lean_back": "Lean back at your seat and relax"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Ready for a short break in {} seconds",
"ready_for_a_long_break": "Ready for a long break in {} seconds",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"next_break_at": "Next break at {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "About",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Audible alert at the end of break",
"cancel": "Cancel",
"close": "Close",
"disable": "Disable Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Enable Safe Eyes",
"enable_screen_lock": "Lock the screen after every long break",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"for_x_minutes": "For {} Minutes",
"idle_time": "Minimum idle time to pause (in minutes)",
"interval_between_two_breaks": "Interval between two breaks (in minutes)",
"language": "Language",
"license": "License",
"long_break_duration": "Long break duration (in seconds)",
"no_of_short_breaks_between_two_long_breaks": "Number of short breaks between two long breaks",
"postpone": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"quit": "Quit",
"save": "Save",
"settings": "Settings",
"short_break_duration": "Short break duration (in seconds)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Skip",
"strict_break": "Strict break (Hide skip button)",
"system_language": "System Language",
"time_to_prepare_for_break": "Time to prepare for break (in seconds)",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"until_restart": "Until restart"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Español",
"language_name_en": "Spanish"
},
"app_info": {
"description": "Safe Eyes protege tus ojos de la fatiga visual (astenopía), recordándote tomar descansos cuando estás trabajando muchas horas en el ordenador."
},
"exercises": {
"short_break_close_eyes": "Cierra fuertemente tus ojos",
"short_break_roll_eyes": "Pon los ojos en blanco hacia cada lado",
"short_break_rotate_clockwise": "Mueve tus ojos en círculos en sentido horario",
"short_break_rotate_counter_clockwise": "Mueve tus ojos en círculos en sentido antihorario",
"short_break_blink": "Parpadea tus ojos",
"short_break_focus_far_distance": "Enfoca un punto lejano",
"short_break_drink_water": "Bebe un poco de agua",
"long_break_walk": "Vete a andar un rato",
"long_break_lean_back": "Reclínate sobre tu silla y relájate"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Listo para una pausa en {} segundos",
"ready_for_a_long_break": "Listo para una pausa en {} segundos",
"disabled_until_restart": "Deshabilitado hasta reinicio",
"disabled_until_x": "Deshabilitado hasta {}",
"next_break_at": "Próxima pausa a las {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Acerca de",
"allow_postpone": "Permitir posponer las pausas",
"audible_alert": "Alerta sonora al final de cada pausa",
"cancel": "Cancelar",
"close": "Close",
"disable": "Desactivar Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Activar Safe Eyes",
"enable_screen_lock": "Bloquear la pantalla despues de cada pausa larga",
"for_x_hour": "Durante {} hora",
"for_x_hours": "Durante {} horas",
"for_x_minutes": "Durante {} minutos",
"idle_time": "Tiempo mínimo (en minutos) sin usar el ordenador para que Safe Eyes se desactive automáticamente",
"interval_between_two_breaks": "Tiempo entre dos pausas",
"language": "Idioma",
"license": "Licencia",
"long_break_duration": "Duración de una pausa larga (en segundos)",
"no_of_short_breaks_between_two_long_breaks": "Número de pausas cortas entre dos pausas largas",
"postpone": "Posponer",
"postpone_duration": "Duración de cada posposición (en minutos)",
"quit": "Salir",
"save": "Guardar",
"settings": "Preferencias",
"short_break_duration": "Duración de una pausa corta (en segundos)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Saltar",
"strict_break": "Pausa estricta (No hay botón Saltar)",
"system_language": "Idioma del sistema",
"time_to_prepare_for_break": "Tiempo para prepararse para una pausa (en segundos)",
"time_to_screen_lock": "Máximo tiempo para saltarse una pausa, evitando la pantalla de bloqueo (en segundos)",
"until_restart": "Hasta reinicio"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Eesti",
"language_name_en": "Estonian"
},
"app_info": {
"description": "Safe Eyes aitab vähendada arvutiga töötamisel silmade väsimust, tuletades meelde puhkepause."
},
"exercises": {
"short_break_close_eyes": "Sulge silmad",
"short_break_roll_eyes": "Vaata vasakule ja paremale",
"short_break_rotate_clockwise": "Liiguta silmi kellaosuti suunas",
"short_break_rotate_counter_clockwise": "Liiguta silmi kellaosutile vastupidiselt",
"short_break_blink": "Pilguta silmi",
"short_break_focus_far_distance": "Vaata kaugusesse",
"short_break_drink_water": "Joo vett",
"long_break_walk": "Jaluta ringi",
"long_break_lean_back": "Toetu seljatoele ja lõõgastu"
},
"messages": {
"audible_alert_disabled": "Helimärguandeid ei tekitata",
"ready_for_a_short_break": "Lühike paus {} sekundi pärast",
"ready_for_a_long_break": "Pikk paus {} sekundi pärast",
"disabled_until_restart": "Peatatud taaskäivituseni",
"disabled_until_x": "Peatatud kuni {}",
"next_break_at": "Järgmine paus {}",
"software_required": "Selle võimaluse jaoks tuleb paigaldada tarkvara {}"
},
"ui_controls": {
"about": "Programmist",
"allow_postpone": "Pauside edasilükkamine lubatud",
"audible_alert": "Pausi lõpus helimärguanne",
"cancel": "Tühista",
"close": "Sulge",
"disable": "Peata Safe Eyes",
"disable_keyboard_shortcut": "Klaviatuurikäskude eiramise aeg, et vältida juhuslikku edasilükkamist (sekundit)",
"enable": "Luba Safe Eyes",
"enable_screen_lock": "Pikka pausi lõpus ekraani lukustamine",
"for_x_hour": "{}-ks tunniks",
"for_x_hours": "{}-ks tunniks",
"for_x_minutes": "{}-ks minutiks",
"idle_time": "Minimaalne pausi aeg (minutites)",
"interval_between_two_breaks": "Kahe pausi vaheline aeg (minutites)",
"language": "Keel",
"license": "Litsents",
"long_break_duration": "Pika pausi kestvus (sekundites)",
"no_of_short_breaks_between_two_long_breaks": "Kui mitu lühikest pausi tehaks pikkade pauside vahel",
"postpone": "Lükka edasi",
"postpone_duration": "Edasilükkamise aeg (minutites)",
"quit": "Välju",
"save": "Salvesta",
"settings": "Seaded",
"short_break_duration": "Lühikese pausi kestvus (sekundites)",
"show_time_in_tray": "Kuvatakse järgmise pausi aega",
"skip": "Jäta vahele",
"strict_break": "Range paus (vahelejätmise nuppu ei näidata)",
"system_language": "Süsteemi keel",
"time_to_prepare_for_break": "Kui kaua enne pausi kuvatakse pausi hoiatust (sekundites)",
"time_to_screen_lock": "Kui kaua enne ekraani lukustamist saab pausi edasi lükata (sekundites)",
"until_restart": "Kuni taaskäivituseni"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "فارسی",
"language_name_en": "Persian"
},
"app_info": {
"description": "Safe Eyes .با یاد آوری شما برای استراحت کردن، وقتی ساعت های طولانی را در پشت کامپیوتر سپری میکنید. از تضعیف چشم های شما جلوگیری میکند "
},
"exercises": {
"short_break_close_eyes": "چشم هایتان را محکم ببندید",
"short_break_roll_eyes": "چشم هایتان را چندین مرتبه به طرفین بچرخانید",
"short_break_rotate_clockwise": "چشم هایتان را در جهت عقربه های ساعت بچرخانید",
"short_break_rotate_counter_clockwise": "چشم هایتان را در خلاف جهت عقربه های ساعت بچرخانید",
"short_break_blink": "پلک بزنید",
"short_break_focus_far_distance": "روی یک نقطه در دور دست تمرکز کنید",
"short_break_drink_water": "مقداری آب بنوشید",
"long_break_walk": "مدتی قدم بزنید",
"long_break_lean_back": "به صندلی تکیه داده و راحت باشید"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "آماده استراحت در {} ثانیه بعد باشید",
"ready_for_a_long_break": "آماده استراحت در {} ثانیه بعد باشید",
"disabled_until_restart": "غیرفعال تا بارگذاری مجدد",
"disabled_until_x": "غیرفعال تا {}",
"next_break_at": "استراحت بعد در {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "درباره",
"allow_postpone": "اجازه به تعویق انداختن استراحت",
"audible_alert": "هشدار همراه با صدا در پایان استراحت",
"cancel": "لغو",
"close": "بستن",
"disable": "غیر فعال کردن Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "فعال کردن Safe Eyes",
"enable_screen_lock": "قفل کردن صفحه بعد از هر استراحت طولانی",
"for_x_hour": "برای {} ساعت",
"for_x_hours": "برای {} ساعت",
"for_x_minutes": "برای {} دقیقه",
"idle_time": "حداقل زمان بیکاری برای توقف کردن برنامه (‌به دقیقه)",
"interval_between_two_breaks": "فاصله بین دو استراحت (به ثانیه‌)",
"language": "زبان",
"license": "لایسنس",
"long_break_duration": "زمان استراحت بلند مدت (به دقیقه)",
"no_of_short_breaks_between_two_long_breaks": "تعداد استراحت های کوتاه بین هر دو استراحت بلند",
"postpone": "به تعویق انداختن",
"postpone_duration": "زمان به تعویق انداختن (به دقیقه)",
"quit": "خروج",
"save": "ذخیره",
"settings": "تنظیمات",
"short_break_duration": "مدت زمان استراحت کوتاه",
"show_time_in_tray": "نشان دادن زمان استراحت بعدی در بخش Tray",
"skip": "رد کردن",
"strict_break": "استراحت سخت گیرانه (پنهان کردن دکمه رد کردن استراحت)",
"system_language": "زبان سیستم",
"time_to_prepare_for_break": "زمان آماده شدن برای استراحت (به ثانیه)",
"time_to_screen_lock": "حداکثر زمان برای رد کردن، جلوگیری از قفل شدن صفحه (به ثانیه‌)",
"until_restart": "تا زمان بارگذاری مجدد"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Français",
"language_name_en": "French"
},
"app_info": {
"description": "Safe Eyes protège vos yeux contre la fatigue de l'œil (asthénopie) en vous rappelant de prendre des pauses lors de vos longues heures de travail sur un ordinateur."
},
"exercises": {
"short_break_close_eyes": "Fermez bien vos yeux",
"short_break_roll_eyes": "Regardez de droite à gauche en alternance",
"short_break_rotate_clockwise": "Faites rouler vos yeux dans le sens horaire",
"short_break_rotate_counter_clockwise": "Faites rouler vos yeux dans le sens antihoraire",
"short_break_blink": "Clignez des yeux",
"short_break_focus_far_distance": "Regardez un point au loin",
"short_break_drink_water": "Buvez de l'eau",
"long_break_walk": "Marchez un peu",
"long_break_lean_back": "Adossez-vous à votre siège et relaxez-vous"
},
"messages": {
"audible_alert_disabled": "Impossible d'activer les notifications sonores",
"ready_for_a_short_break": "Préparez-vous à une pause dans {} secondes",
"ready_for_a_long_break": "Préparez-vous à une pause dans {} secondes",
"disabled_until_restart": "Désactivé jusqu'au redémarrage",
"disabled_until_x": "Désactivé jusqu'à {}",
"next_break_at": "Prochaine pause à {}",
"software_required": "Pour activer cette fonction, veuillez installer {}"
},
"ui_controls": {
"about": "À propos",
"allow_postpone": "Permettre le report des pauses",
"audible_alert": "Alerte sonore en fin d'une pause",
"cancel": "Annuler",
"close": "Fermer",
"disable": "Désactiver Safe Eyes",
"disable_keyboard_shortcut": "Période de désactivation du raccourci pour empêcher d'ignorer involontairement (en secondes)",
"enable": "Activer Safe Eyes",
"enable_screen_lock": "Verrouiller l'écran après chaque pause longue",
"for_x_hour": "Pendant {} heure",
"for_x_hours": "Pendant {} heures",
"for_x_minutes": "Pendant {} minutes",
"idle_time": "Durée minimale d'une pause (en minutes)",
"interval_between_two_breaks": "Intervalle entre deux pauses (en minutes)",
"language": "Langue",
"license": "Licence",
"long_break_duration": "Durée d'une pause longue (en secondes)",
"no_of_short_breaks_between_two_long_breaks": "Nombre de pauses courtes entre deux pauses longues",
"postpone": "Reporter",
"postpone_duration": "Durée de report (en minutes)",
"quit": "Quitter",
"save": "Enregistrer",
"settings": "Paramètres",
"short_break_duration": "Durée d'une pause courte (en secondes)",
"show_time_in_tray": "Afficher la prochaine pause dans la zone de notification",
"skip": "Ignorer",
"strict_break": "Pause stricte (cacher le bouton Ignorer)",
"system_language": "Langue du système",
"time_to_prepare_for_break": "Durée de préparation à une pause (en secondes)",
"time_to_screen_lock": "Durée maximale pour ignorer, en passant outre le verrouillage (en secondes)",
"until_restart": "Jusqu'au redémarrage"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "ქართული",
"language_name_en": "Georgian"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
},
"exercises": {
"short_break_close_eyes": "ძლიერად დახუჭე თვალები",
"short_break_roll_eyes": "ფართოდ გაახილეთ თვალები",
"short_break_rotate_clockwise": "დაატრიალეთ თვალები საათის მოძრაობის მიმართულებით",
"short_break_rotate_counter_clockwise": "დაატრიალეთ თვალები საათის მოძრაობის საწინააღმდეგოდ",
"short_break_blink": "თვალები დაახამხამეთ",
"short_break_focus_far_distance": "ფოკუსირება გააკეთეთ შორ ობიექტზე",
"short_break_drink_water": "დალიეთ წყალი",
"long_break_walk": "ცოტა გაიარეთ",
"long_break_lean_back": "სავარძლის საზურგეზე გადაწექით და ცოტა დაისვენეთ"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "მოემზადეთ შესვენებისთვის {} წამში",
"ready_for_a_long_break": "მოემზადეთ შესვენებისთვის {} წამში",
"disabled_until_restart": "გავაუქმოთ შემდეგ რესტარტამდე",
"disabled_until_x": "გაუქმებულია {} მდე",
"next_break_at": "შემდეგი შესვენება {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "პროგრამის შესახებ",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "ხმოვანი შეტყობინება შესვენების დამთავრებისას",
"cancel": "უარყოფა",
"close": "Close",
"disable": "Safe Eyes გამორთვა",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Safe Eyes ჩართვა",
"enable_screen_lock": "Lock the screen after every long break",
"for_x_hour": "{} საათით",
"for_x_hours": "{} საათით",
"for_x_minutes": "{} საათით",
"idle_time": "მინიმუმ idle დრო პაუზის ასაღებად (წუთებში)",
"interval_between_two_breaks": "ინტერვალი შესვენებებს შორის",
"language": "ენა",
"license": "License",
"long_break_duration": "დიდი შესვენების ხანგრძლივობა (წამებში)",
"no_of_short_breaks_between_two_long_breaks": "მცირე შესვენებების რაოდენობა ორ დიდ შესვენებას შორის",
"postpone": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"quit": "გასვლა",
"save": "დამახსოვრება",
"settings": "პარამეტრები",
"short_break_duration": "მცირე შესვენების ხანგრძლივობა (წამებში)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "გამოტოვება",
"strict_break": "აუცილებელი შესვენება (დავმალოთ ღილაკი 'გამოტოვება')",
"system_language": "System Language",
"time_to_prepare_for_break": "შესვენებისთვის მოსამზადებელი დრო (წამებში)",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"until_restart": "შემდეგ რესტარტამდე"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "हिंदी",
"language_name_en": "Hindi"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
},
"exercises": {
"short_break_close_eyes": "आँखों को ज़ोर से बंद करें",
"short_break_roll_eyes": "आँखों को घुमाएं",
"short_break_rotate_clockwise": "आँखों को घड़ी की दिशा में घुमाएं",
"short_break_rotate_counter_clockwise": "आँखों को घड़ी से उल्टी दिशा में घुमाएं",
"short_break_blink": "ऑंखें झपकायें",
"short_break_focus_far_distance": "आँखों को दूर किसी वस्तु पे टिकाएं",
"short_break_drink_water": "पानी पियें",
"long_break_walk": "थोड़ा टहलें",
"long_break_lean_back": "पीछे हट कर आराम करें"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "{} पलों में आराम",
"ready_for_a_long_break": "{} पलों में आराम",
"disabled_until_restart": "अगले आरंभ तक बंद",
"disabled_until_x": "{} तक बंद",
"next_break_at": "अगला आराम {} में",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "हमारे बारे में",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "आराम के ख़तम के ख़तम होने पर आवाज़",
"cancel": "रखना नहीं",
"close": "Close",
"disable": "सेफ आईज बंद",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "सेफ आईज शुरू",
"enable_screen_lock": "Lock the screen after every long break",
"for_x_hour": "{} घंटे के लिए",
"for_x_hours": "{} घंटों के लिए",
"for_x_minutes": "{} मिन्टों के लिए",
"idle_time": "बीच रोकने पर कितने मिन्टों का अंतर",
"interval_between_two_breaks": "दो आराम अवधियों में अंतर (मिन्टों में)",
"language": "भाषा",
"license": "License",
"long_break_duration": "लंबे आराम की अवधि (पलों में)",
"no_of_short_breaks_between_two_long_breaks": "दो बड़े आराम के बीच कितने छोटे आराम",
"postpone": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"quit": "बंद",
"save": "रखें",
"settings": "सेटिंग्स",
"short_break_duration": "छोटे आराम की अवधि (पलों में)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "अभी नहीं",
"strict_break": "जरूरी आराम (रोक नहीं सकते)",
"system_language": "System Language",
"time_to_prepare_for_break": "कितने पलों पहले बताएं",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"until_restart": "अगले आरम्भ तक"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Magyar",
"language_name_en": "Hungarian"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
},
"exercises": {
"short_break_close_eyes": "Csukja be hosszabb időre a szemét!",
"short_break_roll_eyes": "Roll your eyes",
"short_break_rotate_clockwise": "Rotate your eyes in clockwise direction",
"short_break_rotate_counter_clockwise": "Rotate your eyes in counterclockwise direction",
"short_break_blink": "Pislogjon és forgassa meg a szemeit!",
"short_break_focus_far_distance": "Fókuszban egy pontot a távolban",
"short_break_drink_water": "Igyon egy kis vizet!",
"long_break_walk": "Sétáljon egyet!",
"long_break_lean_back": "Dőljön hátra és pihenjen!"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Tervezett szünet {} másodperc múlva!",
"ready_for_a_long_break": "Tervezett szünet {} másodperc múlva!",
"disabled_until_restart": "Disabled until restart",
"disabled_until_x": "Disabled until {}",
"next_break_at": "A következő szünet {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Ról ről",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Audible alert at the end of break",
"cancel": "Mégse",
"close": "Close",
"disable": "Disable Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Safe Eyes Bekapcsolása",
"enable_screen_lock": "Lock the screen after every long break",
"for_x_hour": "For {} Hour",
"for_x_hours": "For {} Hours",
"for_x_minutes": "For {} Minutes",
"idle_time": "Minimum idle time to pause (in minutes)",
"interval_between_two_breaks": "Két szünet közötti idő",
"language": "Nyelv",
"license": "License",
"long_break_duration": "Hosszú szünet hossza (másodpercekben)",
"no_of_short_breaks_between_two_long_breaks": "Hány rövid szünet legyen a hosszabbak között?",
"postpone": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"quit": "Kilépés",
"save": "Mentés",
"settings": "Beállítások",
"short_break_duration": "Rövid szünet hossza (másodpercekben)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Átugrás",
"strict_break": "Kötelezők a szünetek? (nincs átugrás gomb)",
"system_language": "System Language",
"time_to_prepare_for_break": "Szünet előtti figyelmeztetés (másodperc)",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"until_restart": "Until restart"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Bahasa Indonesia",
"language_name_en": "Indonesian"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
},
"exercises": {
"short_break_close_eyes": "Tutup rapat mata Anda",
"short_break_roll_eyes": "Putar mata Anda",
"short_break_rotate_clockwise": "Putar mata Anda searah jarum jam",
"short_break_rotate_counter_clockwise": "Putar mata Anda melawan arah jarum jam",
"short_break_blink": "Kedipkan mata Anda",
"short_break_focus_far_distance": "Fokus ke titik di kejauhan",
"short_break_drink_water": "Silakan meminum air",
"long_break_walk": "Silakan berjalan sebentar",
"long_break_lean_back": "Silakan bersandar ke kursi dan bersantai"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Bersiap beristirahat dalam {} detik",
"ready_for_a_long_break": "Bersiap beristirahat dalam {} detik",
"disabled_until_restart": "Dimatikan hingga dijalankan ulang",
"disabled_until_x": "Dimatikan hingga {}",
"next_break_at": "Istirahat selanjutnya pada {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Tentang",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Peringatan bersuara saat istirahat berakhir",
"cancel": "Batal",
"close": "Close",
"disable": "Matikan Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Hidupkan Safe Eyes",
"enable_screen_lock": "Lock the screen after every long break",
"for_x_hour": "Selama {} Jam",
"for_x_hours": "Selama {} Jam",
"for_x_minutes": "Selama {} Menit",
"idle_time": "Waktu diam jeda minimal (dalam menit)",
"interval_between_two_breaks": "Jarak antara dua istirahat (dalam menit)",
"language": "Bahasa",
"license": "License",
"long_break_duration": "Durasi istirahat panjang (dalam detik)",
"no_of_short_breaks_between_two_long_breaks": "Jumlah istirahat singkat di antara dua istirahat panjang",
"postpone": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"quit": "Keluar",
"save": "Simpan",
"settings": "Pengaturan",
"short_break_duration": "Durasi istirahat singkat (dalam detik)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Lewati",
"strict_break": "Paksa istirahat (sembunyikan tombol Lewati)",
"system_language": "System Language",
"time_to_prepare_for_break": "Waktu persiapan istirahat (dalam detik)",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"until_restart": "Hingga dijalankan ulang"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Italiano",
"language_name_en": "Italiano"
},
"app_info": {
"description": "Safe Eyes protegge i tuoi occhi dall'affaticamento (Astenopia) ricordandoti di fare pause quando lavori per lunghi periodi al computer."
},
"exercises": {
"short_break_close_eyes": "Chiudi gli occhi stringendoli",
"short_break_roll_eyes": "Ruota gli occhi un po' di volte ad ogni lato",
"short_break_rotate_clockwise": "Ruota gli occhi in senso orario",
"short_break_rotate_counter_clockwise": "Ruota gli occhi in senso antiorario",
"short_break_blink": "Batti gli occhi",
"short_break_focus_far_distance": "Focalizza un punto in lontananza",
"short_break_drink_water": "Bevi un po' d'acqua",
"long_break_walk": "Cammina un po'",
"long_break_lean_back": "Appoggiati allo schienale e rilassati"
},
"messages": {
"audible_alert_disabled": "Impossibile abilitare le notifiche audio",
"ready_for_a_short_break": "Preparati per una breve pausa tra {} secondi",
"ready_for_a_long_break": "preparati per una lunga pausa tra {} secondi",
"disabled_until_restart": "Disabilitato fino al riavvio",
"disabled_until_x": "Disabilitato fino alle {}",
"next_break_at": "Prossima pausa alle {}",
"software_required": "Per abilitare quest'impostazione, installa {}"
},
"ui_controls": {
"about": "Informazioni",
"allow_postpone": "Consenti di posticipare pause",
"audible_alert": "Notifica audio alla fine della pausa",
"cancel": "Annulla",
"close": "Chiudi",
"disable": "Disabilita Safe Eyes",
"disable_keyboard_shortcut": "Disabilita tasti di scelta rapida per evitare di saltare le pause (in secondi)",
"enable": "Abilita Safe Eyes",
"enable_screen_lock": "Blocca lo schermo dopo ogni pausa lunga",
"for_x_hour": "Per {} ora",
"for_x_hours": "Per {} ore",
"for_x_minutes": "Per {} minuti",
"idle_time": "Tempo minimo di inattività per la pausa (in minuti)",
"interval_between_two_breaks": "Intervallo tra due pause (in minuti)",
"language": "Lingua",
"license": "Licenza",
"long_break_duration": "Durata pausa lunga (in secondi)",
"no_of_short_breaks_between_two_long_breaks": "Numero di pause brevi tra due pause lunghe",
"postpone": "Posponi",
"postpone_duration": "Durata posticipo (in minuti)",
"quit": "Chiudi",
"save": "Salva",
"settings": "Impostazioni",
"short_break_duration": "Durata pausa breve (in secondi)",
"show_time_in_tray": "Mostra l'ora della prossima pausa nella barra di sistema",
"skip": "Salta",
"strict_break": "Pausa severa (Nasconde il bottone salta)",
"system_language": "Lingua di sistema",
"time_to_prepare_for_break": "Tempo per prepararsi alla pausa (in secondi)",
"time_to_screen_lock": "Tempo massimo da saltare, evitando la schermata di blocco (in secondi)",
"until_restart": "Fino al riavvio"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Македонски",
"language_name_en": "Macedonian"
},
"app_info": {
"description": "Safe Eyes ве штити од замор на очите (asthenophobia) така што ве потсетува да правите паузи додека работите долги часови на компјутер."
},
"exercises": {
"short_break_close_eyes": "Цврсто затворете ги очите",
"short_break_roll_eyes": "Превртете ги очите неколку пати на сите страни",
"short_break_rotate_clockwise": "Превртете ги очите во насока на стрелките на часовникот",
"short_break_rotate_counter_clockwise": "Превртете ги очите обратно од стрелките на часовникот",
"short_break_blink": "Трепнете неколкупати",
"short_break_focus_far_distance": "Фокусирајте се на точка далеку од вас",
"short_break_drink_water": "Напијте се малку вода",
"long_break_walk": "Пешачете малку",
"long_break_lean_back": "Наслонете се на столот и одморете"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Пауза за {} секунди",
"ready_for_a_long_break": "Пауза за {} секунди",
"disabled_until_restart": "Оневозможено до рестарт",
"disabled_until_x": "Оневозможено до {}",
"next_break_at": "Следна пауза во {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "За",
"allow_postpone": "Овозможи одложување на паузи",
"audible_alert": "Звучен оглас на крај на пауза",
"cancel": "Откажи",
"close": "Затвори",
"disable": "Оневозможете го Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Овозможете го Safe Eyes",
"enable_screen_lock": "Заклучување на екранот по секоја долга пауза",
"for_x_hour": "За {} час",
"for_x_hours": "За {} часа",
"for_x_minutes": "За {} минути",
"idle_time": "Минимум време на мирување до пауза (Во минути)",
"interval_between_two_breaks": "Време помеѓу две паузи (Во минути)",
"language": "Јазик",
"license": "Лиценца",
"long_break_duration": "Траење на долгите паузи (Во секунди)",
"no_of_short_breaks_between_two_long_breaks": "Број на кратки паузи помеѓу две долги",
"postpone": "Одложи",
"postpone_duration": "Траење на одложувањето (Во секунди)",
"quit": "Исклучи",
"save": "Зачувај",
"settings": "Подесувања",
"short_break_duration": "Траење на кратките паузи (Во секунди)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Skip",
"strict_break": "Строга пауза (Сокриј го „Прескокни“ копчето)",
"system_language": "Системски Јазик",
"time_to_prepare_for_break": "Време за подготовка за пауза (Во секунди)",
"time_to_screen_lock": "Максимално време од прескокнување на паузата без да се заклучи екранот (Во секунди)",
"until_restart": "До рестарт"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Nederlands",
"language_name_en": "Dutch"
},
"app_info": {
"description": "Safe Eyes beschermt uw ogen tegen vermoeidheid (asthenopie) door je eraan te herinneren tijdig een pauze in te lassen wanneer je langere tijd aan de computer werkt."
},
"exercises": {
"short_break_close_eyes": "Sluit even heel hard de ogen",
"short_break_roll_eyes": "Rol een paar keer met de ogen naar links en naar rechts",
"short_break_rotate_clockwise": "Draai met de ogen in wijzerzin",
"short_break_rotate_counter_clockwise": "Draai met de ogen in tegenwijzerzin",
"short_break_blink": "Knipper met de ogen",
"short_break_focus_far_distance": "Richt je focus op een punt in de verte",
"short_break_drink_water": "Drink een glas water",
"long_break_walk": "Ga een eindje wandelen",
"long_break_lean_back": "Leun achterover op je stoel en relax even"
},
"messages": {
"audible_alert_disabled": "Hoorbare meldingen uitschakelen",
"ready_for_a_short_break": "Klaar voor een korte pauze over {} seconden",
"ready_for_a_long_break": "Klaar voor een lange pauze over {} seconden",
"disabled_until_restart": "Uitschakelen tot heropstart",
"disabled_until_x": "Uitgeschakeld tot {}",
"next_break_at": "Volgende pauze om {}",
"software_required": "Om deze functie te activeren, gelieve {} te installeren"
},
"ui_controls": {
"about": "Over",
"allow_postpone": "Pauzes uitstellen toestaan",
"audible_alert": "Hoorbaar alarm op het einde van de pauze",
"cancel": "Annuleren",
"close": "Sluiten",
"disable": "Safe Eyes uitschakelen",
"disable_keyboard_shortcut": "Periode uitgeschakelde snelkoppeling om ongewild overslaan te voorkomen (in seconden)",
"enable": "Safe Eyes inschakelen",
"enable_screen_lock": "Vergrendel het scherm na elke lange pauze",
"for_x_hour": "Voor {} uur",
"for_x_hours": "Voor {} uren",
"for_x_minutes": "Voor {} minuten",
"idle_time": "Minimale periode van inactiviteit alvorens te pauzeren (in minuten)",
"interval_between_two_breaks": "Interval tussen twee pauzes (in minuten)",
"language": "Taal",
"license": "Licentie",
"long_break_duration": "Tijdsduur lange pauze (in seconden)",
"no_of_short_breaks_between_two_long_breaks": "Aantal korte pauzes tussen twee lange pauzes",
"postpone": "Uitstellen",
"postpone_duration": "Tijdsduur uitstellen (in minuten)",
"quit": "Afsluiten",
"save": "Opslaan",
"settings": "Instellingen",
"short_break_duration": "Tijdsduur korte pauze (in seconden)",
"show_time_in_tray": "Toon de volgende pauzetijd in het systeemvak",
"skip": "Overslaan",
"strict_break": "Strikte pauze (overslaan knop verbergen)",
"system_language": "Systeemtaal",
"time_to_prepare_for_break": "Voorbereidingstijd voor pauze (in seconden)",
"time_to_screen_lock": "Maximale tijd tot overslaan, het afsluitscherm omzeilend (in seconden)",
"until_restart": "Tot heropstart"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Polski",
"language_name_en": "Polish"
},
"app_info": {
"description": "Safe Eyes chroni Twoje oczy przed przemęczeniem i astenopią przypominając Ci o robieniu przerw podczas długotrwałej pracy przy komputerze."
},
"exercises": {
"short_break_close_eyes": "Mocno zaciśnij powieki",
"short_break_roll_eyes": "Ruszaj oczami z boku na bok",
"short_break_rotate_clockwise": "Wykonuj krążenia oczami zgodnie z ruchem wskazówek zegara",
"short_break_rotate_counter_clockwise": "Wykonuj krążenia oczami przeciwnie do ruchu wskazówek zegara",
"short_break_blink": "Pomrugaj oczami",
"short_break_focus_far_distance": "Skup wzrok na odległym punkcie",
"short_break_drink_water": "Napij się wody",
"long_break_walk": "Przejdź się chwilę",
"long_break_lean_back": "Oprzyj się wygodnie na krześle i zrelaksuj"
},
"messages": {
"audible_alert_disabled": "Nie można aktywować powiadomień dźwiękowych",
"ready_for_a_short_break": "Przygotuj się! Przerwa za {} sekund(y).",
"ready_for_a_long_break": "Przygotuj się! Przerwa za {} sekund(y).",
"disabled_until_restart": "Wyłączony do ponownego uruchomienia",
"disabled_until_x": "Wyłączony do {}",
"next_break_at": "Następna przerwa o {}",
"software_required": "Aby aktywować tę funkcję, zainstaluj {}"
},
"ui_controls": {
"about": "O programie",
"allow_postpone": "Pozwól na odroczenie przerwy",
"audible_alert": "Powiadomienie dźwiękowe na koniec przerwy",
"cancel": "Anuluj",
"close": "Zamknij",
"disable": "Zatrzymaj Safe Eyes",
"disable_keyboard_shortcut": "Czas ignorowania skrótu klawiszowego zapobiegający niezamierzonemu pominięciu przerwy (w sekundach)",
"enable": "Uruchom Safe Eyes",
"enable_screen_lock": "Zablokuj ekran po każdej długiej przerwie",
"for_x_hour": "Na {} godzinę",
"for_x_hours": "Na {} godzin(y)",
"for_x_minutes": "Na {} minut(y)",
"idle_time": "Najkrótszy czas bez aktywności do wstrzymania odliczania (w minutach)",
"interval_between_two_breaks": "Czas między kolejnymi przerwami (w minutach)",
"language": "Język",
"license": "Licencja użytkownika",
"long_break_duration": "Czas trwania długiej przerwy (w sekundach)",
"no_of_short_breaks_between_two_long_breaks": "Liczba krótkich przerw między długimi przerwami",
"postpone": "Odrocz",
"postpone_duration": "Odroczenie przerwy (w minutach)",
"quit": "Wyjdź",
"save": "Zapisz",
"settings": "Ustawienia",
"short_break_duration": "Czas trwania krótkiej przerwy (w sekundach)",
"show_time_in_tray": "Pokaż czas następnej przerwy w zasobniku systemowym",
"skip": "Pomiń",
"strict_break": "Bezwzględna przerwa (ukryj przycisk pominięcia)",
"system_language": "Język systemu",
"time_to_prepare_for_break": "Czas na przygotowanie się do przerwy (w sekundach)",
"time_to_screen_lock": "Czas na pominięcie przerwy bez blokowania ekranu (w sekundach)",
"until_restart": "Do ponownego uruchomienia"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Português",
"language_name_en": "Portuguese"
},
"app_info": {
"description": "Safe Eyes protege seus olhos da fadiga ocular (astenopia) ao lembrá-lo de dar uma pausa enquando você trabalha por horas no computador."
},
"exercises": {
"short_break_close_eyes": "Feche bem os olhos",
"short_break_roll_eyes": "Mexa os olhos",
"short_break_rotate_clockwise": "Gire os olhos no sentido horário",
"short_break_rotate_counter_clockwise": "Gire os olhos no sentido anti-horário",
"short_break_blink": "Pisque seus olhos",
"short_break_focus_far_distance": "Focalize seu olhar num ponto distante",
"short_break_drink_water": "Beba água",
"long_break_walk": "Caminhe um pouco",
"long_break_lean_back": "Encoste na cadeira e relaxe"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Pronto para uma pausa em {} segundos",
"ready_for_a_long_break": "Pronto para uma pausa em {} segundos",
"disabled_until_restart": "Desativado até reiniciar",
"disabled_until_x": "Desativado até {}",
"next_break_at": "Próxima pausa em {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Sobre",
"allow_postpone": "Permite o adiamento da pausa",
"audible_alert": "Alerta sonoro no fim da pausa",
"cancel": "Cancelar",
"close": "Fechar",
"disable": "Desativar Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Habilitar Safe Eyes",
"enable_screen_lock": "Bloqueie a tela após cada pausa longa",
"for_x_hour": "Por {} Hora",
"for_x_hours": "Por {} Horas",
"for_x_minutes": "Por {} Minutos",
"idle_time": "Tempo mínimo de inatividade para pausar (em minutos)",
"interval_between_two_breaks": "Intervalo entre duas pausas",
"language": "Idioma",
"license": "Licença",
"long_break_duration": "Duração de uma pausa longa (em segundos)",
"no_of_short_breaks_between_two_long_breaks": "Número de pausas curtas entre duas pausas longas",
"postpone": "Adiar",
"postpone_duration": "Duração do adiamento (em minutos)",
"quit": "Sair",
"save": "Salvar",
"settings": "Configuração",
"short_break_duration": "Duração de uma pausa curta (em segundos)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Pular",
"strict_break": "Pausa rigorosa (Esconder botão pular)",
"system_language": "Linguagem do Sistema",
"time_to_prepare_for_break": "Tempo para se preparar para a pausa (em segundos)",
"time_to_screen_lock": "Tempo máximo para saltar, ignorando a tela de bloqueio (em segundos)",
"until_restart": "Até reiniciar"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Русский",
"language_name_en": "Russian"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
},
"exercises": {
"short_break_close_eyes": "Сильно зажмурьтесь",
"short_break_roll_eyes": "Закатите глаза",
"short_break_rotate_clockwise": "Поводите глазами по кругу по часовой стрелке",
"short_break_rotate_counter_clockwise": "Поводите глазами по кругу против часовой стрелки",
"short_break_blink": "Поморгайте",
"short_break_focus_far_distance": "Сфокусируйте взгляд на далёком объекте",
"short_break_drink_water": "Выпейте воды",
"long_break_walk": "Пройдитесь немного",
"long_break_lean_back": "Откиньтесь на спинку стула и расслабьтесь"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Приготовьтесь к перерыву через {} секунд",
"ready_for_a_long_break": "Приготовьтесь к перерыву через {} секунд",
"disabled_until_restart": "Отключено до перезагрузки",
"disabled_until_x": "Отключено до {}",
"next_break_at": "Следующий перерыв в {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "О программе",
"allow_postpone": "Allow postponing the breaks",
"audible_alert": "Звуковой сигнал в конце перерыва",
"cancel": "Отменить",
"close": "Close",
"disable": "Отключить Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Активировать Safe Eyes",
"enable_screen_lock": "Включить блокировку экрана",
"for_x_hour": "На {} час",
"for_x_hours": "На {} часов",
"for_x_minutes": "На {} минут",
"idle_time": "Минимальное время простоя для паузы (в минутах)",
"interval_between_two_breaks": "Интервал между двумя перерывами",
"language": "Язык",
"license": "License",
"long_break_duration": "Продолжительность длинного перерыва (в секундах)",
"no_of_short_breaks_between_two_long_breaks": "Количество коротких перерывов между двумя длинными",
"postpone": "Отложить",
"postpone_duration": "Отложить на время (в минутах)",
"quit": "Выйти",
"save": "Сохранить",
"settings": "Настройки",
"short_break_duration": "Продолжительность короткого перерыва (в секундах)",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Пропустить",
"strict_break": "Обязательный перерыв (Скрыть кнопку 'Пропустить')",
"system_language": "System Language",
"time_to_prepare_for_break": "Время подготовки в перерыву (в секундах)",
"time_to_screen_lock": "Заблокировать экран если перерыв дольше(в секундах)",
"until_restart": "До перезагрузки"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Slovenský",
"language_name_en": "Slovak"
},
"app_info": {
"description": "Safe Eyes vám pripomína robiť si prestávky, keď trávite dlhé hodiny u počítača a tak chráni vaše oči pred přílišným namáhaním (asthenopií)."
},
"exercises": {
"short_break_close_eyes": "Pevne zavri oči",
"short_break_roll_eyes": "Gúľaj očami",
"short_break_rotate_clockwise": "Otáčaj oči v smere hodinových ručičiek",
"short_break_rotate_counter_clockwise": "Otáčaj oči v protismere hodinových ručičiek",
"short_break_blink": "Žmurkaj očami",
"short_break_focus_far_distance": "Zameraj sa na bod v diaľke",
"short_break_drink_water": "Daj si trochu vody",
"long_break_walk": "Pobehaj si chvíľku",
"long_break_lean_back": "Opri sa o kreslo a relaxuj"
},
"messages": {
"audible_alert_disabled": "Nieje možné zapnúť zvukové upozornenie",
"ready_for_a_short_break": "Priprav sa na prestávku o {} sekúnd",
"ready_for_a_long_break": "Priprav sa na prestávku o {} sekúnd",
"disabled_until_restart": "Zakázať do reštartu",
"disabled_until_x": "Zakázať do {}",
"next_break_at": "Ďalšia prestávka o {}",
"software_required": "Pro ích povolenie nainštalujte {}"
},
"ui_controls": {
"about": "Ohľadom",
"allow_postpone": "Povoliť odkladanie prestávok",
"audible_alert": "Zvukový signál na konci prestávky",
"cancel": "Zrušiť",
"close": "Close",
"disable": "Zakázať Safe Eyes",
"disable_keyboard_shortcut": "Dlžka po kterú zakázať klávesovú skratku v sekundách.",
"enable": "Povoliť Safe Eyes",
"enable_screen_lock": "Zablokovať obrazovku po každej dlhej prestávke",
"for_x_hour": "Počas {} hodiny",
"for_x_hours": "Počas {} hodín",
"for_x_minutes": "Počas {} minút",
"idle_time": "Pozastaviť pri nečinnosti dlhšej ako (v minútach)",
"interval_between_two_breaks": "Interval medzi dvomi prestávkami",
"language": "Jazyk",
"license": "Licencie",
"long_break_duration": "Trvanie dlhej prestávky (v sekundách)",
"no_of_short_breaks_between_two_long_breaks": "Počet krátkych prestávok medzi dvomi dlhými prestávkami",
"postpone": "Odložiť",
"postpone_duration": "O koĺko odložiť (minút)",
"quit": "Koniec",
"save": "Uložiť",
"settings": "Nastavenia",
"short_break_duration": "Trvanie krátkej prestávky (v sekundách)",
"show_time_in_tray": "Zobrazit čas následujúcej prestávky v system tray",
"skip": "Preskočiť",
"strict_break": "Povinná prestávka (Skryje tlačitko Preskočiť)",
"system_language": "Jazyk systému",
"time_to_prepare_for_break": "Čas na prípravu na prestávku (v sekundách)",
"time_to_screen_lock": "O koľko sekúnd najviac preskočiť (obchádza zámok obrazovky)",
"until_restart": "Do reštartu"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "தமிழ்",
"language_name_en": "Tamil"
},
"app_info": {
"description": "அடிக்கடி ஓய்வெடுக்க அறிவுறுத்துவதன் மூலம் Safe Eyes கணனியில் நீண்ட நேரம் பணியாற்றுவதால் ஏற்படும் கண் சோர்வை தடுக்கிறது."
},
"exercises": {
"short_break_close_eyes": "உங்கள் கண்களை இறுக்கமாக மூடுங்கள்",
"short_break_roll_eyes": "உங்கள் கண்களை இடம் வலமாக உருட்டுங்கள்",
"short_break_rotate_clockwise": "உங்கள் கண்களை வலஞ்சுழியாக சுழற்றுங்கள்",
"short_break_rotate_counter_clockwise": "உங்கள் கண்களை இடஞ்சுழியாக சுழற்றுங்கள்",
"short_break_blink": "உங்கள் கண்களை சிமிட்டுங்கள்",
"short_break_focus_far_distance": "தூரத்தில் உள்ள ஒரு பொருளை பாருங்கள்",
"short_break_drink_water": "கொஞ்சம் தண்ணீர் குடியுங்கள்",
"long_break_walk": "சிறிது தூரம் நடவுங்கள்",
"long_break_lean_back": "கதிைரயில் பின்பக்கமாக சாய்ந்து ஓய்வெடுங்கள்"
},
"messages": {
"audible_alert_disabled": "ஒலிச் சமிக்ஞையை செயற்படுத்த முடியாதுள்ளது",
"ready_for_a_short_break": "{} விநாடிகளில் குறுகிய இடைவேளைக்கு தயாராகுங்கள்",
"ready_for_a_long_break": "{} விநாடிகளில் நீண்ட இடைவேளைக்கு தயாராகுங்கள்",
"disabled_until_restart": "மறுதுவக்கம் வரை நிறுத்தி வைக்கப்பட்டுள்ளது",
"disabled_until_x": "{} வரை நிறுத்தி வைக்கப்பட்டுள்ளது",
"next_break_at": "அடுத்த இடைவேளை {}",
"software_required": "இந்த வசதியை செயற்படுத்த {} ஐ நிறுவவும்"
},
"ui_controls": {
"about": "Safe Eyes குறித்த தகவல்கள்",
"allow_postpone": "ஒத்திவைக்க அனுமதிக்கவும்",
"audible_alert": "இடைவேளையின் இறுதியில் ஒலி சமிக்ஞை",
"cancel": "ரத்து",
"close": "மூடு",
"disable": "Safe Eyes ஐ நிறுத்துக",
"disable_keyboard_shortcut": "தற்செயல் பயன்பாட்டை தவிர்ப்பதற்காக விசைப்பலகை குறுக்குவழியை நிறுத்தி வைக்கும் நேரம் (விநாடிகளில்)",
"enable": "Safe Eyes ஐ செயல்படுத்துக",
"enable_screen_lock": "நீண்ட கால இடைவேளைகளின் பின்னர் திரையை பூட்டுக",
"for_x_hour": "{} மணித்தியாலத்திற்கு",
"for_x_hours": "{} மணித்தியாலங்களுக்கு",
"for_x_minutes": "{} நிமிடங்களுக்கு",
"idle_time": "இடைநிறுத்துவதற்கான குறைந்தபட்ச செயலற்ற நேரம் (நிமிடங்களில்)",
"interval_between_two_breaks": "இரண்டு இடைவேளைகளுக்கிடையிலான இடைவெளி (நிமிடங்களில்)",
"language": "மொழி",
"license": "மென்பொருள் உரிமம்",
"long_break_duration": "நீண்ட கால இடைவேளை (விநாடிகளில்)",
"no_of_short_breaks_between_two_long_breaks": "இரண்டு நீண்ட இடைவேளைகளுக்கிடையிலான குறுகிய இடைவேளைகள்",
"postpone": "ஒத்தி வை",
"postpone_duration": "இடைவேளையை ஒத்தி வைக்கும் காலம் (நிமிடங்களில்)",
"quit": "நிறுத்து",
"save": "சேமி",
"settings": "அமைப்பு",
"short_break_duration": "குறுகிய கால இடைவேளை (விநாடிகளில்)",
"show_time_in_tray": "அடுத்த இடைவேளை நேரத்தை Safe Eyes சின்னத்திற்கு அருகில் காண்பிக்கவும்",
"skip": "தவிர்",
"strict_break": "கட்டாய இடைவேளை (தவிர்க்கும் பொத்தான் காண்பிக்கப்பட மாட்டாது)",
"system_language": "இயங்குதள மொழி",
"time_to_prepare_for_break": "இடைவேளைக்கு தயாராக தேவைப்படும் நேரம் (விநாடிகளில்)",
"time_to_screen_lock": "திரையினை பூட்டாமல் இடைவேளையை தவிர்ப்பதற்கான அதிகபட்ச நேரம் (விநாடிகளில்)",
"until_restart": "மீள ஆரம்பிக்கும் வரைை"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Türkçe",
"language_name_en": "Turkish"
},
"app_info": {
"description": "Safe Eyes uzun bir süre boyunca bilgisayar başında çalışmanız gerektiği zamanlarda size mola vermeyi hatırlatmak suretiyle gözlerinizi göz yorgunluğuna (asthenopia) karşı korur."
},
"exercises": {
"short_break_close_eyes": "Gözlerinizi sıkıca kapatın",
"short_break_roll_eyes": "Gözlerinizi çevirin",
"short_break_rotate_clockwise": "Gözlerinizi saat yönünde döndürün",
"short_break_rotate_counter_clockwise": "Gözlerinizi saat yönünün tersine döndürün",
"short_break_blink": "Göz kapaklarınızı kapatıp açın",
"short_break_focus_far_distance": "Uzak mesafede bir noktaya odaklanın",
"short_break_drink_water": "Gidip biraz su için",
"long_break_walk": "Biraz yürüyün",
"long_break_lean_back": "Arkanıza yaslanın ve biraz gevşeyin"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "{} Saniye içerisinde bir molaya hazır olun",
"ready_for_a_long_break": "{} Saniye içerisinde bir molaya hazır olun",
"disabled_until_restart": "Tekrar başlatılana kadar devre dışı",
"disabled_until_x": "{}'e kadar devre dışı",
"next_break_at": "Bir sonraki mola zamanı: {}",
"software_required": "Bu özelliği etkinleştirmek için lütfen {} yükleyiniz."
},
"ui_controls": {
"about": "Hakkında",
"allow_postpone": "Mola ertelemeye izin ver",
"audible_alert": "Mola sonunda sesli uyarı",
"cancel": "İptal",
"close": "Kapat",
"disable": "Safe Eyes'ı devre dışı bırak",
"disable_keyboard_shortcut": "İstem dışı geçişi engellemek için kısayol tuşunun etkisiz kalma süresi (saniye)",
"enable": "Safe Eyes'ı etkinleştir",
"enable_screen_lock": "Her uzun mola sonunda ekranı kilitle",
"for_x_hour": "{} Saat",
"for_x_hours": "{} Saat",
"for_x_minutes": "{} Dakika",
"idle_time": "Minimum boş zaman (dakika)",
"interval_between_two_breaks": "İki mola arasındaki süre",
"language": "Dil",
"license": "Lisans",
"long_break_duration": "Uzun mola süresi (saniye)",
"no_of_short_breaks_between_two_long_breaks": "İki uzun mola arasındaki kısa mola sayısı",
"postpone": "Ertele",
"postpone_duration": "Ertelme süresi (dakika)",
"quit": ıkış",
"save": "Kaydet",
"settings": "Ayarlar",
"short_break_duration": "Kısa mola süresi (saniye)",
"show_time_in_tray": "Bir sonraki mola zamanını sistem bildirim alanında göster",
"skip": "Geç",
"strict_break": "Kesin mola (Geç düğmesini gizler)",
"system_language": "Sistem Dili",
"time_to_prepare_for_break": "Mola için hazırlanma zamanı (saniye)",
"time_to_screen_lock": "Ekran kilitlemeyi ertelemede geçilecek maksimum süre (saniye)",
"until_restart": "Tekrar başlatılana kadar"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "Українська",
"language_name_en": "Ukrainian"
},
"app_info": {
"description": "Safe Eyes нагадує вам робити перерви, при довгій роботі за комп'ютером, захищаючи ваші очі від втоми (asthenopia)."
},
"exercises": {
"short_break_close_eyes": "Сильно заплющіть очі",
"short_break_roll_eyes": "Закотіть очі в різні боки",
"short_break_rotate_clockwise": "Покрутіть очима за годинниковою стрілкою",
"short_break_rotate_counter_clockwise": "Покрутіть очима проти годинникової стрілки",
"short_break_blink": "Поморгайте",
"short_break_focus_far_distance": "Подивіться на далекий об'єкт",
"short_break_drink_water": "Випийте води",
"long_break_walk": "Трохи пройдіться",
"long_break_lean_back": "Відкиньтеся на спинку і розслабтеся"
},
"messages": {
"audible_alert_disabled": "Cannot enable audible notifications",
"ready_for_a_short_break": "Приготуйтеся до перерви через {} секунд",
"ready_for_a_long_break": "Приготуйтеся до перерви через {} секунд",
"disabled_until_restart": "Відключено до перезапуску",
"disabled_until_x": "Відключено до {}",
"next_break_at": "Наступна перерва о {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Про SafeEyes",
"allow_postpone": "Дозволити відкладати перерви",
"audible_alert": "Звуковий сигнал вкінці перерви",
"cancel": "Відмінити",
"close": "Закрити",
"disable": "Відключити Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Включити Safe Eyes",
"enable_screen_lock": "Блокувати екран після довгих перерв",
"for_x_hour": "На {} годину",
"for_x_hours": "На {} години",
"for_x_minutes": "На {} хвилин",
"idle_time": "Час бездіяльності для паузи (в хвилинах)",
"interval_between_two_breaks": "Інтервал між двома перервами (в хвилинах)",
"language": "Мова",
"license": "Ліцензія",
"long_break_duration": "Тривалість довгої перерви (в секундах)",
"no_of_short_breaks_between_two_long_breaks": "Кількість коротких перерв між двома довгими",
"postpone": "Відкласти",
"postpone_duration": "На скільки відкласти (в хвилинах)",
"quit": "Вийти",
"save": "Зберегти",
"settings": "Налаштування",
"short_break_duration": "Тривалість короткої перерви (в секундах)",
"show_time_in_tray": "Показувати час наступної перерви в системному треї",
"skip": "Пропустити",
"strict_break": "Обов'язкова перерва (прибрати кнопку для пропускання)",
"system_language": "Мова системи",
"time_to_prepare_for_break": "Час підготовки до перерви (в секундах)",
"time_to_screen_lock": "Найбільший час для пропуску, без блокування екрану (в секундах)",
"until_restart": "До перезапуску"
}
}

View File

@ -1,67 +0,0 @@
{
"meta_info":
{
"language_name": "Tiếng Việt",
"language_name_en": "Vietnamese"
},
"app_info":
{
"description": "Safe Eyes bảo vệ đôi mắt của bạn khỏi sự mệt mỏi (asthenopia) bằng cách nhắc nhở bạn nghỉ ngơi khi bạn đang làm việc trong một thời gian dài bên máy tính."
},
"exercises":
{
"short_break_close_eyes": "Nhắm chặt mắt lại",
"short_break_roll_eyes": "Đảo đi đảo lại đôi mắt của bạn cho mỗi bên",
"short_break_rotate_clockwise": "Xoay mắt theo chiều kim đồng hồ",
"short_break_rotate_counter_clockwise": "Xoay mắt theo chiều ngược chiều kim đồng hồ",
"short_break_blink": "Chớp chớp mắt",
"short_break_focus_far_distance": "Nhìn ra xa",
"short_break_drink_water": "Uống một ít nước",
"long_break_walk": "Đứng dậy và đi lại một lúc",
"long_break_lean_back": "Tựa lưng vào ghế và thư giãn"
},
"messages":
{
"audible_alert_disabled": "Không thể bật thông báo âm thanh",
"ready_for_a_short_break": "Sẵn sàng để nghỉ ngơi {} giây",
"ready_for_a_long_break": "Sẵn sàng để nghỉ ngơi {} giây",
"disabled_until_restart": "Vô hiệu hóa cho đến khi khởi động lại",
"disabled_until_x": "Vô hiệu hóa cho đến khi {}",
"next_break_at": "Giờ nghỉ ngơi tiếp theo là {}",
"software_required": "Để bật tính năng này, vui lòng cài đặt {}"
},
"ui_controls":
{
"about": "Giới thiệu",
"allow_postpone": "Cho phép trì hoãn sự nghỉ ngơi",
"audible_alert": "Âm báo sau mỗi lần nghỉ",
"cancel": "Huỷ",
"close": "Đóng",
"disable": "Tắt Safe Eyes",
"disable_keyboard_shortcut": "Th.gian vô hiệu hóa phím tắt để tránh tắt nhầm Nhắc nhở khi nó hiện lên (giây)",
"enable": "Bật Safe Eyes",
"enable_screen_lock": "Khoá màn hình ở mỗi thời điểm nghỉ ngơi dài",
"for_x_hour": "Trong {} giờ",
"for_x_hours": "Trong {} giờ",
"for_x_minutes": "Trong {} phút",
"idle_time": "Thơi gian rỗi nhỏ nhất để tạm dừng (tính bằng phút)",
"interval_between_two_breaks": "Khoảng thời gian giữa hai lần nghỉ ngơi (tính bằng phút)",
"language": "Language (Ngôn ngữ)",
"license": "Giấy phép",
"long_break_duration": "Thời gian nghỉ ngơi dài (tính bằng giây)",
"no_of_short_breaks_between_two_long_breaks": "Số lần nghỉ ngơi ngắn giữa hai lần nghỉ ngơi dài",
"postpone": "Tạm hoãn",
"postpone_duration": "Thời gian tạm hoãn (tính bằng phút)",
"quit": "Thoát",
"save": "Lưu",
"settings": "Cài đặt",
"short_break_duration": "Thời gian nghỉ ngắn (tính bằng giây)",
"show_time_in_tray": "Hiển thị thời gian nghỉ ngắn trên khay hệ thống",
"skip": "Bỏ qua",
"strict_break": "Nghỉ ngơi nghiêm túc (Không cho phép bỏ qua)",
"system_language": "System Language (Ngôn ngữ hệ thống)",
"time_to_prepare_for_break": "Thời gian để chuẩn bị nghỉ ngơi (tính bằng giây)",
"time_to_screen_lock": "Thời gian bỏ qua tối đa, không tính thời gian khóa màn hình (tính bằng giây)",
"until_restart": "Cho đến khi khởi động lại"
}
}

View File

@ -1,62 +0,0 @@
{
"meta_info": {
"language_name": "简体中文",
"language_name_en": "Chinese"
},
"app_info": {
"description": "当您持续使用电脑办公太长时间时Safe Eyes 会提醒您休息一下,通过这种方式来保护您的眼睛,使您远离视疲劳。"
},
"exercises": {
"short_break_close_eyes": "闭上您的眼睛休息一会",
"short_break_roll_eyes": "左右滚动一下您的眼珠",
"short_break_rotate_clockwise": "顺时针方向转动您的眼珠",
"short_break_rotate_counter_clockwise": "逆时针方向转动您的眼珠",
"short_break_blink": "眨眨眼",
"short_break_focus_far_distance": "望远:将视线聚焦到远处物体上",
"short_break_drink_water": "喝一点水",
"long_break_walk": "站起来走走",
"long_break_lean_back": "靠在椅背上休息一会"
},
"messages": {
"audible_alert_disabled": "无法开启声音提示",
"ready_for_a_short_break": "{} 秒后将开始一个短休息",
"ready_for_a_long_break": "{} 秒后将开始一个长休息",
"disabled_until_restart": "暂停直到重启",
"disabled_until_x": "暂停直到 {}",
"next_break_at": "下一次休息开始时间:{}",
"software_required": "激活本功能需要安装 {}"
},
"ui_controls": {
"about": "关于",
"allow_postpone": "允许推迟休息",
"audible_alert": "休息结束时播放声音提示",
"cancel": "取消",
"close": "关闭",
"disable": "禁用Safe Eyes",
"disable_keyboard_shortcut": "休息界面按钮不可用时间(防止操作失误而跳过休息,单位秒)",
"enable": "启用Safe Eyes",
"enable_screen_lock": "每次长休息结束时锁定屏幕",
"for_x_hour": "{} 小时",
"for_x_hours": "{} 小时",
"for_x_minutes": "{} 分钟",
"idle_time": "电脑无操作多长时间时暂停计时(单位:分钟)",
"interval_between_two_breaks": "两次休息之间的间隔时间(单位:分钟)",
"language": "语言",
"license": "许可证",
"long_break_duration": "长休息时长(单位:秒)",
"no_of_short_breaks_between_two_long_breaks": "在两次长休息之间的短休息个数",
"postpone": "推迟休息",
"postpone_duration": "推迟时间间隔(单位:分钟)",
"quit": "退出",
"save": "保存",
"settings": "设置",
"short_break_duration": "短休息时长(单位:秒)",
"show_time_in_tray": "在系统托盘显示下一次休息时间",
"skip": "跳过",
"strict_break": "严格休息(隐藏跳过按钮)",
"system_language": "系统语言",
"time_to_prepare_for_break": "休息前的准备时长(单位:秒)",
"time_to_screen_lock": "长休息开始几秒后自动锁屏",
"until_restart": "直到重启"
}
}

View File

@ -0,0 +1,310 @@
# SAFE EYES ENGLISH TRANSLATION.
# Copyright (C) 2017 Gobinath
# Gobinath slgobinath@gmail.com, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2017-09-17 07:59-0400\n"
"PO-Revision-Date: 2017-09-17 07:59-0400\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.8.7.1\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: en_US\n"
# Short Breaks
msgid "Tightly close your eyes"
msgstr "Tightly close your eyes"
msgid "Roll your eyes a few times to each side"
msgstr "Roll your eyes a few times to each side"
msgid "Rotate your eyes in clockwise direction"
msgstr "Rotate your eyes in clockwise direction"
msgid "Rotate your eyes in counterclockwise direction"
msgstr "Rotate your eyes in counterclockwise direction"
msgid "Blink your eyes"
msgstr "Blink your eyes"
msgid "Focus on a point in the far distance"
msgstr "Focus on a point in the far distance"
msgid "Have some water"
msgstr "Have some water"
# Long Breaks
msgid "Walk for a while"
msgstr "Walk for a while"
msgid "Lean back at your seat and relax"
msgstr "Lean back at your seat and relax"
# Commandline Arguments
msgid "show the about dialog"
msgstr "show the about dialog"
msgid "disable the currently running safeeyes instance"
msgstr "disable the currently running safeeyes instance"
msgid "enable the currently running safeeyes instance"
msgstr "enable the currently running safeeyes instance"
msgid "quit the running safeeyes instance and exit"
msgstr "quit the running safeeyes instance and exit"
msgid "show the settings dialog"
msgstr "show the settings dialog"
msgid "start safeeyes in debug mode"
msgstr "start safeeyes in debug mode"
# AboutDialog
msgid "Close"
msgstr "Close"
msgid "description"
msgstr "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
msgid "License"
msgstr "License"
# BreakScreen
msgid "Skip"
msgstr "Skip"
msgid "Postpone"
msgstr "Postpone"
# Settings dialog
msgid "Break duration (in seconds)"
msgstr "Break duration (in seconds)"
msgid "Interval between two breaks (in minutes)"
msgstr "Interval between two breaks (in minutes)"
msgid "Time to prepare for a break (in seconds)"
msgstr "Time to prepare for a break (in seconds)"
msgid "Keyboard shortcuts disabled period (in seconds)"
msgstr "Keyboard shortcuts disabled period (in seconds)"
msgid "Postpone duration (in minutes)"
msgstr "Postpone duration (in minutes)"
msgid "Strict break (No way to skip breaks)"
msgstr "Strict break (No way to skip breaks)"
msgid "Allow postponing breaks"
msgstr "Allow postponing breaks"
msgid "Persist the internal state"
msgstr "Persist the internal state"
msgid "Long break interval must be a multiple of short break interval"
msgstr "Long break interval must be a multiple of short break interval"
msgid "Options"
msgstr "Options"
msgid "Short Breaks"
msgstr "Short Breaks"
msgid "Long Breaks"
msgstr "Long Breaks"
msgid "Break"
msgstr "Break"
msgid "Breaks"
msgstr "Breaks"
msgid "Plugins"
msgstr "Plugins"
msgid "Type"
msgstr "Type"
msgid "Short"
msgstr "Short"
msgid "Long"
msgstr "Long"
msgid "Image"
msgstr "Image"
msgid "Select"
msgstr "Select"
msgid "Please select an image"
msgstr "Please select an image"
msgid "Duration"
msgstr "Duration"
msgid "Override"
msgstr "Override"
msgid "Time (in seconds)"
msgstr "Time (in seconds)"
msgid "Break Settings"
msgstr "Break Settings"
msgid "Plugin Settings"
msgstr "Plugin Settings"
msgid "Plugin does not support %s desktop environment"
msgstr "Plugin does not support %s desktop environment"
msgid "Please install the Python module '%s'"
msgstr "Please install the Python module '%s'"
msgid "Please install the command-line tool '%s'"
msgstr "Please install the command-line tool '%s'"
msgid "Please add the resource %(resource)s to %(config_resource)s directory"
msgstr "Please add the resource %(resource)s to %(config_resource)s directory"
msgid "New Break"
msgstr "New Break"
msgid "Remove"
msgstr "Remove"
msgid "Discard"
msgstr "Discard"
msgid "Save"
msgstr "Save"
# plugin/audiblealert
msgid "Audible Alert"
msgstr "Audible Alert"
msgid "Play an audible alert at the end of breaks"
msgstr "Play an audible alert at the end of breaks"
# plugin/donotdisturb
msgid "Do Not Disturb"
msgstr "Do Not Disturb"
msgid "Skip the break if the active window is in fullscreen mode"
msgstr "Skip the break if the active window is in fullscreen mode"
msgid "Do not interrupt these windows anytime"
msgstr "Do not interrupt these windows anytime"
msgid "Interrupt these windows regardless of their state"
msgstr "Interrupt these windows regardless of their state"
msgid "Switch the interruptible windows to normal mode"
msgstr "Switch the interruptible windows to normal mode"
# plugin/healthstats
msgid "Health Statistics"
msgstr "Health Statistics"
msgid "Show statistics based on how you use Safe Eyes"
msgstr "Show statistics based on how you use Safe Eyes"
# plugin/notification
msgid "Notification"
msgstr "Notification"
msgid "Show a system notification before breaks"
msgstr "Show a system notification before breaks"
msgid "Ready for a short break in %s seconds"
msgstr "Ready for a short break in %s seconds"
msgid "Ready for a long break in %s seconds"
msgstr "Ready for a long break in %s seconds"
# plugin/screensaver
msgid "Screensaver"
msgstr "Screensaver"
msgid "Lock the screen after long breaks by starting screensaver"
msgstr "Lock the screen after long breaks by starting screensaver"
msgid "Custom screensaver command"
msgstr "Custom screensaver command"
msgid "Minimum seconds to skip without screensaver"
msgstr "Minimum seconds to skip without screensaver"
# plugin/smartpause
msgid "Smart Pause"
msgstr "Smart Pause"
msgid "Pause Safe Eyes if the system is idle"
msgstr "Pause Safe Eyes if the system is idle"
msgid "Minimum idle time to pause Safe Eyes"
msgstr "Minimum idle time to pause Safe Eyes"
#: plugins/trayicon
msgid "Tray Icon"
msgstr "Tray Icon"
msgid "Show a tray icon in the notification area"
msgstr "Show a tray icon in the notification area"
msgid "Show next break time in tray icon"
msgstr "Show next break time in tray icon"
msgid "About"
msgstr "About"
msgid "Disable Safe Eyes"
msgstr "Disable Safe Eyes"
msgid "Disabled until %s"
msgstr "Disabled until %s"
msgid "Disabled until restart"
msgstr "Disabled until restart"
msgid "Enable Safe Eyes"
msgstr "Enable Safe Eyes"
msgid "For %d Hour"
msgid_plural "For %d Hours"
msgstr[0] "For %d Hour"
msgstr[1] "For %d Hours"
msgid "For %d Minute"
msgid_plural "For %d Minutes"
msgstr[0] "For %d Minute"
msgstr[1] "For %d Minutes"
msgid "For %d Second"
msgid_plural "For %d Seconds"
msgstr[0] "For %d Second"
msgstr[1] "For %d Seconds"
msgid "Next break at %s"
msgstr "Next break at %s"
msgid "No Breaks Available"
msgstr "No Breaks Available"
msgid "Settings"
msgstr "Settings"
msgid "Take a break now"
msgstr "Take a break now"
msgid "Until restart"
msgstr "Until restart"
msgid "Quit"
msgstr "Quit"

View File

@ -0,0 +1,310 @@
# SAFE EYES TAMIL TRANSLATION.
# Copyright (C) 2017 Gobinath
# Gobinath slgobinath@gmail.com, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2017-09-17 07:59-0400\n"
"PO-Revision-Date: 2017-09-17 07:59-0400\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.8.7.1\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: ta\n"
# Short Breaks
msgid "Tightly close your eyes"
msgstr "உங்கள் கண்களை இறுக்கமாக மூடுங்கள்"
msgid "Roll your eyes a few times to each side"
msgstr "உங்கள் கண்களை இடம் வலமாக உருட்டுங்கள்"
msgid "Rotate your eyes in clockwise direction"
msgstr "உங்கள் கண்களை வலஞ்சுழியாக சுழற்றுங்கள்"
msgid "Rotate your eyes in counterclockwise direction"
msgstr "உங்கள் கண்களை இடஞ்சுழியாக சுழற்றுங்கள்"
msgid "Blink your eyes"
msgstr "உங்கள் கண்களை சிமிட்டுங்கள்"
msgid "Focus on a point in the far distance"
msgstr "தூரத்தில் உள்ள ஒரு பொருளை பாருங்கள்"
msgid "Have some water"
msgstr "கொஞ்சம் தண்ணீர் குடியுங்கள்"
# Long Breaks
msgid "Walk for a while"
msgstr "சிறிது தூரம் நடவுங்கள்"
msgid "Lean back at your seat and relax"
msgstr "கதிைரயில் பின்பக்கமாக சாய்ந்து ஓய்வெடுங்கள்"
# Commandline Arguments
msgid "show the about dialog"
msgstr "தகவல் சாளரத்தை காண்பி"
msgid "disable the currently running safeeyes instance"
msgstr "செயற்பட்டு கொண்டிருக்கும் Safe Eyes ஐ முடக்கு"
msgid "enable the currently running safeeyes instance"
msgstr "முடக்க பட்டிருக்கும் Safe Eyes ஐ செயல்படுத்துக"
msgid "quit the running safeeyes instance and exit"
msgstr "செயற்பட்டு கொண்டிருக்கும் Safe Eyes ஐ நிறுத்துக"
msgid "show the settings dialog"
msgstr "அமைப்புகள் சாளரத்தை காண்பி"
msgid "start safeeyes in debug mode"
msgstr "வழு நீக்கும் முறையில் Safe Eyes ஐ ஆரம்பிக்குக"
# AboutDialog
msgid "Close"
msgstr "மூடு"
msgid "description"
msgstr "அடிக்கடி ஓய்வெடுக்க அறிவுறுத்துவதன் மூலம் Safe Eyes கணனியில் நீண்ட நேரம் பணியாற்றுவதால் ஏற்படும் கண் சோர்வை தடுக்கிறது."
msgid "License"
msgstr "மென்பொருள் உரிமம்"
# BreakScreen
msgid "Skip"
msgstr "தவிர்"
msgid "Postpone"
msgstr "ஒத்தி வை"
# Settings dialog
msgid "Break duration (in seconds)"
msgstr "இடைவேளையின் கால அளவு (விநாடிகளில்)"
msgid "Interval between two breaks (in minutes)"
msgstr "இரண்டு இடைவேளைகளுக்கிடையிலான இடைவெளி (நிமிடங்களில்)"
msgid "Time to prepare for a break (in seconds)"
msgstr "இடைவேளைக்கு தயாராக தேவைப்படும் நேரம் (விநாடிகளில்)"
msgid "Keyboard shortcuts disabled period (in seconds)"
msgstr "விசைப்பலகை குறுக்குவழிகள் முடக்கப்பட்டிருக்கும் காலம் (விநாடிகளில்)"
msgid "Postpone duration (in minutes)"
msgstr "இடைவேளையை ஒத்தி வைக்கும் காலம் (நிமிடங்களில்)"
msgid "Strict break (No way to skip breaks)"
msgstr "கட்டாய இடைவேளை (இடைவேளையை தவிர்க்க முடியாது)"
msgid "Allow postponing breaks"
msgstr "இடைவேளையை ஒத்திவைக்க அனுமதிக்கவும்"
msgid "Persist the internal state"
msgstr "உள்ளக நிலையை தக்கவைத்து கொள்ளவும்"
msgid "Long break interval must be a multiple of short break interval"
msgstr "நீண்ட இடைவேளைகளுக்கிடையிலான நேரம் குறுகிய இடைவேளைகளுக்கிடையிலான நேரத்தின் மடங்காக இருக்க வேண்டும்"
msgid "Options"
msgstr "தேர்வுகள்"
msgid "Short Breaks"
msgstr "குறுகிய இடைவேளைகள்"
msgid "Long Breaks"
msgstr "நீண்ட இடைவேளைகள்"
msgid "Break"
msgstr "இடைவேளை"
msgid "Breaks"
msgstr "இடைவேளைகள்"
msgid "Plugins"
msgstr "நீட்சிகள்"
msgid "Type"
msgstr "வகை"
msgid "Short"
msgstr "குறுகியது"
msgid "Long"
msgstr "நீண்டது"
msgid "Image"
msgstr "படம்"
msgid "Select"
msgstr "தேர்ந்தெடு"
msgid "Please select an image"
msgstr "ஒரு படத்தைத் தேர்ந்தெடுக்கவும்"
msgid "Duration"
msgstr "கால அளவு"
msgid "Override"
msgstr "இயல்புநிலை அமைப்புகளை மீறவும்"
msgid "Time (in seconds)"
msgstr "நேரம் (விநாடிகளில்)"
msgid "Break Settings"
msgstr "இடைவேளை அமைப்புகள்"
msgid "Plugin Settings"
msgstr "நீட்சி அமைப்புகள்"
msgid "Plugin does not support %s desktop environment"
msgstr "இந்த நீட்சி %s முகத்திரை சூழலை ஆதரிக்கவில்லை"
msgid "Please install the Python module '%s'"
msgstr "தயவுசெய்து '%s' Python தொகுப்பை நிறுவவும்"
msgid "Please install the command-line tool '%s'"
msgstr "தயவுசெய்து '%s' முனைய கட்டளையை நிறுவுக"
msgid "Please add the resource %(resource)s to %(config_resource)s directory"
msgstr "தயவுசெய்து %(resource)s வளத்தை %(config_resource)s கோப்புறையில் சேர்க்கவும்"
msgid "New Break"
msgstr "புதிய இடைவேளை"
msgid "Remove"
msgstr "அகற்று"
msgid "Discard"
msgstr "நிராகரி"
msgid "Save"
msgstr "சேமி"
# plugin/audiblealert
msgid "Audible Alert"
msgstr "ஒலிச் சமிக்ஞய்"
msgid "Play an audible alert at the end of breaks"
msgstr "இடைவேளைகளின் முடிவில் ஒலிச் சமிக்ஞய்"
# plugin/donotdisturb
msgid "Do Not Disturb"
msgstr "தொந்தரவு செய்ய வேண்டாம்"
msgid "Skip the break if the active window is in fullscreen mode"
msgstr "பாவனையிலுள்ள சாளரம் முழுத்திரையில் இருப்பின் இடைவேளையை தவிர்"
msgid "Do not interrupt these windows anytime"
msgstr "இந்த சாளரங்கள் எந்த நிலையிலிருந்தாலும் குறுக்கிட வேண்டாம்"
msgid "Interrupt these windows regardless of their state"
msgstr "இந்த சாளரங்களின் நிலையை பொருட்படுத்தாது குறுக்கிடுக"
msgid "Switch the interruptible windows to normal mode"
msgstr "குறுக்கீடு செய்யும் போது சாளரங்களை சாதாரண நிலைக்கு மாற்றுக"
# plugin/healthstats
msgid "Health Statistics"
msgstr "ஆரோக்கிய புள்ளிவிவரங்கள்"
msgid "Show statistics based on how you use Safe Eyes"
msgstr "Safe Eyes பயன்பாடு அடிப்படையிலான ஆரோக்கிய புள்ளிவிவரங்கள்"
# plugin/notification
msgid "Notification"
msgstr "முன்னறிவிப்பு"
msgid "Show a system notification before breaks"
msgstr "இடைவேளைகளின் முன் தயார்படுத்தலுக்கான அறிவிப்பு"
msgid "Ready for a short break in %s seconds"
msgstr "%s விநாடிகளில் குறுகிய இடைவேளைக்கு தயாராகுங்கள்"
msgid "Ready for a long break in %s seconds"
msgstr "{} விநாடிகளில் நீண்ட இடைவேளைக்கு தயாராகுங்கள்"
# plugin/screensaver
msgid "Screensaver"
msgstr "திரைக் காப்பு"
msgid "Lock the screen after long breaks by starting screensaver"
msgstr "நீண்ட இடைவேளைகளின் பின் திரைக்காப்பு மென்பொருள் இயக்கம்"
msgid "Custom screensaver command"
msgstr "மாறுபட்ட திரைக்காப்பு கட்டளை"
msgid "Minimum seconds to skip without screensaver"
msgstr "திரைக்காப்பு மென்பொருளை செயற்படுத்தாமல் தவிர்ப்பதற்கான வினாடிகள்"
# plugin/smartpause
msgid "Smart Pause"
msgstr "நுண்ணறிவு இடைநிறுத்தம்"
msgid "Pause Safe Eyes if the system is idle"
msgstr "கணினி செயலற்று இருந்தால் Safe Eyesஐ இடைநிறுத்துக"
msgid "Minimum idle time to pause Safe Eyes"
msgstr "Safe Eyesஐ இடைநிறுத்துவதற்கான குறைந்தபட்ச செயலற்ற வினாடிகள்"
#: plugins/trayicon
msgid "Tray Icon"
msgstr "Safe Eyes சின்னம்"
msgid "Show a tray icon in the notification area"
msgstr "அறிவிப்பு பகுதியில் Safe Eyes சின்னம்"
msgid "Show next break time in tray icon"
msgstr "அடுத்த இடைவேளை நேரத்தை Safe Eyes சின்னத்திற்கு அருகில் காண்பிக்கவும்"
msgid "About"
msgstr "Safe Eyes குறித்த தகவல்கள்"
msgid "Disable Safe Eyes"
msgstr "Safe Eyes ஐ நிறுத்துக"
msgid "Disabled until %s"
msgstr "%s வரை நிறுத்தி வைக்கப்பட்டுள்ளது"
msgid "Disabled until restart"
msgstr "மறுதுவக்கம் வரை நிறுத்தி வைக்கப்பட்டுள்ளது"
msgid "Enable Safe Eyes"
msgstr "Safe Eyes ஐ செயல்படுத்துக"
msgid "For %d Hour"
msgid_plural "For %d Hours"
msgstr[0] "%d மணித்தியாலத்திற்கு"
msgstr[1] "%d மணித்தியாலங்களுக்கு"
msgid "For %d Minute"
msgid_plural "For %d Minutes"
msgstr[0] "%d நிமிடத்திற்கு"
msgstr[1] "%d நிமிடங்களுக்கு"
msgid "For %d Second"
msgid_plural "For %d Seconds"
msgstr[0] "%d வினாடிக்கு"
msgstr[1] "%d வினாடிகளுக்கு"
msgid "Next break at %s"
msgstr "அடுத்த இடைவேளை %s"
msgid "No Breaks Available"
msgstr "இடைவேளைகள் வரையறுக்கப்படவில்லை"
msgid "Settings"
msgstr "அமைப்பு"
msgid "Take a break now"
msgstr "சுயவிருப்பிலான இடைவேளை"
msgid "Until restart"
msgstr "மீள ஆரம்பிக்கும் வரை"
msgid "Quit"
msgstr "நிறுத்து"

View File

@ -1,85 +1,119 @@
{
"meta": {
"config_version": "5.0.1"
"config_version": "6.0.0"
},
"allow_postpone": false,
"break_interval": 15,
"enable_screen_lock": false,
"long_break_duration": 60,
"no_of_short_breaks_per_long_break": 5,
"pre_break_warning_time": 10,
"short_break_duration": 15,
"idle_time": 5,
"persist_state": true,
"persist_state": false,
"postpone_duration": 5,
"rpc_port": 7200,
"shortcut_disable_time": 2,
"shortcut_skip": 9,
"shortcut_postpone": 65,
"show_time_in_tray": false,
"strict_break": false,
"audible_alert": false,
"language": "system",
"time_to_screen_lock": 5,
"lock_screen_command": [],
"active_window_class": {
"skip_break": [],
"take_break": []
},
"disable_options": [
{
"label": "for_x_minutes",
"time": 30,
"unit": "minute"
},
{
"label": "for_x_hour",
"time": 1,
"unit": "hour"
},
{
"label": "for_x_hours",
"time": 2,
"unit": "hour"
},
{
"label": "for_x_hours",
"time": 3,
"unit": "hour"
}
],
"short_breaks": [
{
"name": "short_break_close_eyes"
"name": "Tightly close your eyes"
},
{
"name": "short_break_roll_eyes"
"name": "Roll your eyes a few times to each side"
},
{
"name": "short_break_rotate_clockwise"
"name": "Rotate your eyes in clockwise direction"
},
{
"name": "short_break_rotate_counter_clockwise"
"name": "Rotate your eyes in counterclockwise direction"
},
{
"name": "short_break_blink"
"name": "Blink your eyes"
},
{
"name": "short_break_focus_far_distance"
"name": "Focus on a point in the far distance"
},
{
"name": "short_break_drink_water"
"name": "Have some water"
}
],
"long_breaks": [
{
"name": "long_break_walk"
"name": "Walk for a while"
},
{
"name": "long_break_lean_back"
"name": "Lean back at your seat and relax"
}
],
"custom_exercises": {
},
"plugins": []
"plugins": [
{
"id": "donotdisturb",
"enabled": true,
"version": "0.0.1",
"settings": {
"skip_break_windows": "",
"take_break_windows": "",
"unfullscreen": true
}
},
{
"id": "notification",
"enabled": true,
"version": "0.0.1"
},
{
"id": "audiblealert",
"enabled": true,
"version": "0.0.1"
},
{
"id": "trayicon",
"enabled": true,
"version": "0.0.1",
"settings": {
"show_time_in_tray": false,
"disable_options": [
{
"time": 30,
"unit": "minute"
},
{
"time": 1,
"unit": "hour"
},
{
"time": 2,
"unit": "hour"
},
{
"time": 3,
"unit": "hour"
}
]
}
},
{
"id": "smartpause",
"enabled": true,
"version": "0.0.1",
"settings": {
"idle_time": 3
}
},
{
"id": "screensaver",
"enabled": true,
"version": "0.0.1",
"settings": {
"command": "",
"min_seconds": 3
}
},
{
"id": "healthstats",
"enabled": false,
"version": "0.0.1"
}
]
}

View File

@ -72,8 +72,24 @@
color: white;
}
.panel_left {
.lbl_widget {
font-size: 9pt;
color: white;
}
.panel_right {
.btn_properties {
border-radius: 15px;
}
.lbl_plugin_name {
font-weight: bold;
}
.lbl_plugin_description {
color: #9B9B9B;
}
.info_bar_long_break {
opacity: 0.9;
border-radius: 5px;
}

View File

@ -36,31 +36,32 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</property>
</object>
<object class="GtkWindow" id="window_about">
<property name="height_request">400</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Safe Eyes</property>
<property name="resizable">False</property>
<property name="window_position">center</property>
<property name="default_width">350</property>
<property name="default_height">200</property>
<property name="window_position">center-always</property>
<property name="icon_name">safeeyes</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkBox" id="layout_box">
<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">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<property name="baseline_position">top</property>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="lbl_app_name">
@ -70,7 +71,7 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="valign">center</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="label" translatable="yes">Safe Eyes 1.1.1</property>
<property name="label" translatable="yes">Safe Eyes 2.0.0</property>
<property name="justify">center</property>
<attributes>
<attribute name="style" value="normal"/>
@ -91,6 +92,7 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<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="width_chars">60</property>
<property name="max_width_chars">60</property>
</object>
<packing>
@ -118,14 +120,15 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<object class="GtkTextView" id="txt_license">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</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>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
@ -135,10 +138,10 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="label" translatable="yes">http://slgobinath.github.io/SafeEyes</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</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">http://slgobinath.github.io/SafeEyes</property>
</object>
<packing>
@ -171,9 +174,8 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<object class="GtkButtonBox" id="buttonbox">
<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">5</property>
<property name="spacing">10</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btn_close">

View File

@ -34,162 +34,166 @@
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkBox" id="box1">
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_homogeneous">True</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="lbl_left">
<object class="GtkLabel" id="lbl_top">
<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>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid1">
<object class="GtkBox" id="box_center_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">10</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkGrid" id="grid_parent">
<object class="GtkGrid" id="grid_central">
<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>
<property name="row_spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_message">
<object class="GtkImage" id="img_break">
<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">
<object class="GtkGrid" id="grid_parent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="yscale">0.20000000298023224</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="row_spacing">15</property>
<child>
<object class="GtkLabel" id="lbl_count">
<object class="GtkLabel" id="lbl_message">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">00</property>
<property name="label" translatable="yes">Hello World</property>
<property name="justify">center</property>
<style>
<class name="lbl_count"/>
<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">1</property>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="height">3</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>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkImage" id="img_break">
<object class="GtkLabel" id="lbl_widget">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Widget</property>
<property name="yalign">0.25</property>
<style>
<class name="lbl_widget"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</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>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="height">2</property>
</packing>
</child>
</object>

View File

@ -0,0 +1,61 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">label</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_value">
<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="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,70 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkLabel" id="lbl_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">label</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_properties">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">img_properties</property>
<property name="always_show_image">True</property>
<style>
<class name="btn_properties"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<object class="GtkImage" id="img_properties">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
</object>
</interface>

View File

@ -0,0 +1,66 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkAdjustment" id="adjustment_value">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">label</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_value">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="adjustment">adjustment_value</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,146 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkImage" id="img_properties">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
</object>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkImage" id="img_plugin_icon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="stock">gtk-about</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="lbl_plugin_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">end</property>
<property name="label" translatable="yes">Plugin Name</property>
<property name="xalign">0.05000000074505806</property>
<property name="yalign">1</property>
<style>
<class name="lbl_plugin_name"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_plugin_description">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Plugin Description</property>
<property name="xalign">0.05000000074505806</property>
<property name="yalign">0</property>
<style>
<class name="lbl_plugin_description"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch" id="switch_enable">
<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="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_properties">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">img_properties</property>
<property name="always_show_image">True</property>
<style>
<class name="btn_properties"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,60 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">label</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="txt_value">
<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="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,214 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkAdjustment" id="adjustment_duration">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="lst_break_types">
<columns>
<!-- column-name Text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Short</col>
</row>
<row>
<col id="0" translatable="yes">Long</col>
</row>
</data>
</object>
<object class="GtkWindow" id="dialog_new_break">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Properties</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">500</property>
<property name="default_height">50</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">safeeyes</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkBox" id="box_settings">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkBox" id="box6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkEntry" id="txt_break">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="width_chars">64</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_duration3">
<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">Type</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="cmb_type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">lst_break_types</property>
<property name="active">0</property>
<property name="id_column">0</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Break</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<property name="homogeneous">True</property>
<property name="baseline_position">top</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btn_discard">
<property name="label">Discard</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="discard" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_save">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="save" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,480 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkAdjustment" id="adjustment_duration">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkImage" id="img_break">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
<object class="GtkListStore" id="lst_break_types">
<columns>
<!-- column-name Text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Short</col>
</row>
<row>
<col id="0" translatable="yes">Long</col>
</row>
</data>
</object>
<object class="GtkWindow" id="dialog_settings_break">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Properties</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">500</property>
<property name="default_height">50</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">safeeyes</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkBox" id="box_settings">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkBox" id="box6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkEntry" id="txt_break">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_duration3">
<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">Type</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="cmb_type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">lst_break_types</property>
<property name="active">0</property>
<property name="id_column">0</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</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="GtkBox" id="box8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_duration4">
<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">Image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_image">
<property name="label" translatable="yes">Select</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">img_break</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="select_image" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Break</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_duration1">
<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">Override</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_override_duration">
<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="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">100</property>
<child>
<object class="GtkLabel" id="lbl_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">Time (in seconds)</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_duration">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="adjustment">adjustment_duration</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Duration</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkBox" id="box5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_duration2">
<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">Override</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_override_plugins">
<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="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid_plugins">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="margin_bottom">10</property>
<property name="row_homogeneous">True</property>
<property name="column_homogeneous">True</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</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="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Plugins</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="baseline_position">top</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btn_remove">
<property name="label">Remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="remove_break" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
<?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) 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/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkWindow" id="dialog_settings_plugin">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Properties</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">400</property>
<property name="default_height">10</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">safeeyes</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkBox" id="box_settings">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">15</property>
<property name="homogeneous">True</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>

178
safeeyes/model.py Normal file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module contains the entity classes used by Safe Eyes and its plugins.
"""
from distutils.version import LooseVersion
from enum import Enum
from safeeyes import Utility
class Break(object):
"""
An entity class which represents a break.
"""
def __init__(self, break_type, name, time, image, plugins):
self.type = break_type
self.name = name
self.time = time
self.image = image
self.plugins = plugins
def __str__(self):
return 'Break: {{name: "{}", type: {}, time: {}}}\n'.format(self.name, self.type, self.time)
def __repr__(self):
return str(self)
def is_long_break(self):
"""
Check whether this break is a long break.
"""
return self.type == BreakType.LONG_BREAK
def is_short_break(self):
"""
Check whether this break is a short break.
"""
return self.type == BreakType.SHORT_BREAK
def plugin_enabled(self, plugin_id, is_plugin_enabled):
"""
Check whether this break supports the given plugin.
"""
if self.plugins:
return plugin_id in self.plugins
else:
return is_plugin_enabled
class BreakType(Enum):
"""
Type of Safe Eyes breaks.
"""
SHORT_BREAK = 1
LONG_BREAK = 2
class State(Enum):
"""
Possible states of Safe Eyes.
"""
START = 0,
WAITING = 1,
PRE_BREAK = 2,
BREAK = 3,
STOPPED = 4,
QUIT = 5
class EventHook(object):
"""
Hook to attach and detach listeners to system events.
"""
def __init__(self):
self.__handlers = []
def __iadd__(self, handler):
self.__handlers.append(handler)
return self
def __isub__(self, handler):
self.__handlers.remove(handler)
return self
def fire(self, *args, **keywargs):
"""
Fire all listeners attached with.
"""
for handler in self.__handlers:
if not handler(*args, **keywargs):
return False
return True
class Config(object):
"""
The configuration of Safe Eyes.
"""
def __init__(self):
# Read the config files
self.__user_config = Utility.load_json(Utility.CONFIG_FILE_PATH)
self.__system_config = Utility.load_json(Utility.SYSTEM_CONFIG_FILE_PATH)
if self.__user_config is None:
Utility.initialize_safeeyes()
self.__user_config = self.__system_config
self.save()
else:
system_config_version = self.__system_config['meta']['config_version']
meta_obj = self.__user_config.get('meta', None)
if meta_obj is None:
# Corrupted user config
self.__user_config = self.__system_config
else:
user_config_version = str(meta_obj.get('config_version', '0.0.0'))
if LooseVersion(user_config_version) != LooseVersion(system_config_version):
# Update the user config
self.__merge_dictionary(self.__user_config, self.__system_config)
self.__user_config = self.__system_config
Utility.merge_plugins(self.__user_config)
self.save()
def __merge_dictionary(self, old_dict, new_dict):
"""
Merge the dictionaries.
"""
for key in new_dict:
if key == "meta":
continue
if key in old_dict:
new_value = new_dict[key]
old_value = old_dict[key]
if type(new_value) is type(old_value):
# Both properties have same type
if isinstance(new_value, dict):
self.__merge_dictionary(old_value, new_value)
else:
new_dict[key] = old_value
def save(self):
"""
Save the configuration to file.
"""
Utility.write_json(Utility.CONFIG_FILE_PATH, self.__user_config)
def get(self, key, default_value=None):
"""
Get the value.
"""
value = self.__user_config.get(key, default_value)
if value is None:
value = self.__system_config.get(key, None)
return value
def set(self, key, value):
"""
Set the value.
"""
self.__user_config[key] = value

View File

@ -0,0 +1,16 @@
{
"meta": {
"name": "Audible Alert",
"description": "Play an audible alert at the end of breaks",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": ["aplay"],
"operating_systems": [],
"desktop_environments": [],
"resources": ["alert.wav"]
},
"settings": [],
"break_override_allowed": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Audible Alert plugin plays a sound after each breaks to notify the user that the break has end.
"""
import logging
from safeeyes import Utility
context = None
def init(ctx, safeeyes_config, plugin_config):
"""
Initialize the plugin.
"""
global context
logging.debug('Initialize Audible Alert plugin')
context = ctx
def on_stop_break():
"""
After the break, play the alert sound
"""
# Do not play if the break is skipped or postponed
if context['skipped'] or context['postponed']:
return
logging.info('Playing audible alert')
try:
# Open the sound file
path = Utility.get_resource_path('alert.wav')
if path is None:
return
Utility.execute_command('aplay', ['-q', path])
except BaseException:
logging.error('Failed to play audible alert')

View File

@ -0,0 +1,35 @@
{
"meta": {
"name": "Do Not Disturb",
"description": "Skip the break if the active window is in fullscreen mode",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": ["xprop"],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": [
{
"id": "skip_break_windows",
"label": "Do not interrupt these windows anytime",
"type": "TEXT",
"default": ""
},
{
"id": "take_break_windows",
"label": "Interrupt these windows regardless of their state",
"type": "TEXT",
"default": ""
},
{
"id": "unfullscreen",
"label": "Switch the interruptible windows to normal mode",
"type": "BOOL",
"default": true
}
],
"break_override_allowed": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Skip Fullscreen plugin skips the break if the active window is fullscreen.
NOTE: Do not remove the unused import 'GdkX11' becuase it is required in Ubuntu 14.04
"""
import logging
import re
import subprocess
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
from gi.repository import GdkX11
context = None
skip_break_window_classes = []
take_break_window_classes = []
unfullscreen_allowed = True
def is_active_window_skipped():
"""
Check for full-screen applications.
This method must be executed by the main thread. If not, it will cause to random failure.
"""
logging.info('Searching for full-screen application')
screen = Gdk.Screen.get_default()
active_window = screen.get_active_window()
if active_window:
active_xid = str(active_window.get_xid())
cmdlist = ['xprop', '-root', '-notype', '-id', active_xid, 'WM_CLASS', '_NET_WM_STATE']
try:
stdout = subprocess.check_output(cmdlist).decode('utf-8')
except subprocess.CalledProcessError:
logging.warning('Error in finding full-screen application')
else:
if stdout:
is_fullscreen = 'FULLSCREEN' in stdout
# Extract the process name
process_names = re.findall('"(.+?)"', stdout)
if process_names:
process = process_names[1].lower()
if process in skip_break_window_classes:
return True
elif process in take_break_window_classes:
if is_fullscreen and unfullscreen_allowed:
try:
active_window.unfullscreen()
except BaseException:
logging.error('Error in unfullscreen the window ' + process)
return False
return is_fullscreen
return False
def init(ctx, safeeyes_config, plugin_config):
global context
global skip_break_window_classes
global take_break_window_classes
global unfullscreen_allowed
logging.debug('Initialize Skip Fullscreen plugin')
context = ctx
skip_break_window_classes = plugin_config['skip_break_windows'].split()
take_break_window_classes = plugin_config['take_break_windows'].split()
unfullscreen_allowed = plugin_config['unfullscreen']
def on_start_break(break_obj):
"""
"""
return is_active_window_skipped()

View File

@ -0,0 +1,16 @@
{
"meta": {
"name": "Health Statistics",
"description": "Show statistics based on how you use Safe Eyes",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": [],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": [],
"break_override_allowed": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Show health statistics on the break screen.
"""
import logging
context = None
no_of_skipped_breaks = 0
no_of_breaks = 0
no_of_cycles = -1
session = None
def init(ctx, safeeyes_config, plugin_config):
"""
Initialize the plugin.
"""
global context
global session
global no_of_skipped_breaks
global no_of_breaks
global no_of_cycles
logging.debug('Initialize Health Stats plugin')
context = ctx
if session is None:
session = context['session']['plugin'].get('healthstats', None)
if session is None:
session = {'no_of_skipped_breaks': 0, 'no_of_breaks': 0, 'no_of_cycles': -1}
context['session']['plugin']['healthstats'] = session
no_of_skipped_breaks = session.get('no_of_skipped_breaks', 0)
no_of_breaks = session.get('no_of_breaks', 0)
no_of_cycles = session.get('no_of_cycles', -1)
def on_stop_break():
"""
After the break, play the alert sound
"""
global no_of_skipped_breaks
if context['skipped']:
no_of_skipped_breaks += 1
session['no_of_skipped_breaks'] = no_of_skipped_breaks
def get_widget_title(break_obj):
"""
Return the widget title.
"""
global no_of_breaks
global no_of_cycles
no_of_breaks += 1
if context['new_cycle']:
no_of_cycles += 1
session['no_of_breaks'] = no_of_breaks
session['no_of_cycles'] = no_of_cycles
return _('Health Statistics')
def get_widget_content(break_obj):
"""
Return the statistics.
"""
return 'BREAKS: {}\tSKIPPED: {}\tCYCLES: {}'.format(no_of_breaks, no_of_skipped_breaks, no_of_cycles)

View File

@ -0,0 +1,15 @@
{
"meta": {
"name": "Notification",
"description": "Show a system notification before breaks",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": [],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": []
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

View File

@ -0,0 +1,88 @@
# 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
import gi
from safeeyes.model import BreakType
gi.require_version('Notify', '0.7')
from gi.repository import Notify
"""
Safe Eyes Notification plugin
"""
APPINDICATOR_ID = 'safeeyes'
notification = None
context = None
Notify.init(APPINDICATOR_ID)
def init(ctx, safeeyes_config, plugin_config):
"""
Initialize the plugin.
"""
global context
logging.debug('Initialize Notification plugin')
context = ctx
def on_pre_break(break_obj):
"""
Show the notification
"""
# Construct the message based on the type of the next break
global notification
logging.info('Show the notification')
message = '\n'
warning_time = 10
if break_obj.type == BreakType.SHORT_BREAK:
message += (_('Ready for a short break in %s seconds') % warning_time)
else:
message += (_('Ready for a long break in %s seconds') % warning_time)
notification = Notify.Notification.new('Safe Eyes', message, icon='safeeyes_enabled')
try:
notification.show()
except BaseException:
logging.error('Failed to show the notification')
def on_start_break(break_obj):
"""
Close the notification.
"""
global notification
logging.info('Close pre-break notification')
if notification:
try:
notification.close()
notification = None
except BaseException:
# Some operating systems automatically close the notification.
pass
def on_exit():
"""
Uninitialize the registered notificaion.
"""
logging.debug('Stop Notification plugin')
Notify.uninit()

View File

@ -0,0 +1,29 @@
{
"meta": {
"name": "Screensaver",
"description": "Lock the screen after long breaks by starting screensaver",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": [],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": [
{
"id": "command",
"label": "Custom screensaver command",
"type": "TEXT",
"default": ""
},
{
"id": "min_seconds",
"label": "Minimum seconds to skip without screensaver",
"type": "INT",
"default": 3
}
],
"break_override_allowed": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

View File

@ -0,0 +1,113 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Screensaver plugin locks the desktop using native screensaver application, after long breaks.
"""
import logging
import os
from safeeyes import Utility
context = None
lock_screen = False
lock_screen_command = None
min_seconds = 0
seconds_passed = 0
def __lock_screen_command():
"""
Function tries to detect the screensaver command based on the current envinroment
Possible results:
Gnome, Unity, Budgie: ['gnome-screensaver-command', '--lock']
Cinnamon: ['cinnamon-screensaver-command', '--lock']
Pantheon, LXDE: ['light-locker-command', '--lock']
Mate: ['mate-screensaver-command', '--lock']
KDE: ['qdbus', 'org.freedesktop.ScreenSaver', '/ScreenSaver', 'Lock']
XFCE: ['xflock4']
Otherwise: None
"""
desktop_session = os.environ.get('DESKTOP_SESSION')
current_desktop = os.environ.get('XDG_CURRENT_DESKTOP')
if desktop_session is not None:
desktop_session = desktop_session.lower()
if ('xfce' in desktop_session or desktop_session.startswith('xubuntu') or (current_desktop is not None and 'xfce' in current_desktop)) and Utility.command_exist('xflock4'):
return ['xflock4']
elif desktop_session == 'cinnamon' and Utility.command_exist('cinnamon-screensaver-command'):
return ['cinnamon-screensaver-command', '--lock']
elif (desktop_session == 'pantheon' or desktop_session.startswith('lubuntu')) and Utility.command_exist('light-locker-command'):
return ['light-locker-command', '--lock']
elif desktop_session == 'mate' and Utility.command_exist('mate-screensaver-command'):
return ['mate-screensaver-command', '--lock']
elif desktop_session == 'kde' or 'plasma' in desktop_session or desktop_session.startswith('kubuntu') or os.environ.get('KDE_FULL_SESSION') == 'true':
return ['qdbus', 'org.freedesktop.ScreenSaver', '/ScreenSaver', 'Lock']
elif desktop_session in ['gnome', 'unity', 'budgie-desktop'] or desktop_session.startswith('ubuntu'):
if Utility.command_exist('gnome-screensaver-command'):
return ['gnome-screensaver-command', '--lock']
# From Gnome 3.8 no gnome-screensaver-command
return ['dbus-send', '--type=method_call', '--dest=org.gnome.ScreenSaver', '/org/gnome/ScreenSaver', 'org.gnome.ScreenSaver.Lock']
elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
if 'deprecated' not in os.environ.get('GNOME_DESKTOP_SESSION_ID') and Utility.command_exist('gnome-screensaver-command'):
# Gnome 2
return ['gnome-screensaver-command', '--lock']
return None
def init(ctx, safeeyes_config, plugin_config):
"""
Initialize the screensaver plugin.
"""
global context
global lock_screen_command
global min_seconds
logging.debug('Initialize Screensaver plugin')
context = ctx
min_seconds = plugin_config['min_seconds']
if plugin_config['command']:
lock_screen_command = plugin_config['command'].split()
else:
lock_screen_command = __lock_screen_command()
def on_start_break(break_obj):
"""
Determine the break type and only if it is a long break, enable the lock_screen flag.
"""
global lock_screen
global seconds_passed
seconds_passed = 0
if lock_screen_command:
lock_screen = break_obj.is_long_break()
def on_countdown(countdown, seconds):
"""
Keep track of seconds passed from the beginning of long break.
"""
global seconds_passed
seconds_passed = seconds
def on_stop_break():
"""
Lock the screen after a long break if the user has not skipped within min_seconds.
"""
if lock_screen and seconds_passed >= min_seconds:
Utility.execute_command(lock_screen_command)

View File

@ -0,0 +1,22 @@
{
"meta": {
"name": "Smart Pause",
"description": "Pause Safe Eyes if the system is idle",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": ["xprintidle"],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": [
{
"id": "idle_time",
"label": "Minimum idle time to pause Safe Eyes",
"type": "INT",
"default": 3
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

View File

@ -0,0 +1,154 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import logging
import subprocess
import threading
from safeeyes import Utility
from safeeyes.model import State
"""
Safe Eyes smart pause plugin
"""
context = None
idle_condition = threading.Condition()
lock = threading.Lock()
active = False
idle_time = 0
enable_safe_eyes = None
disable_safe_eyes = None
smart_pause_activated = False
idle_start_time = None
next_break_time = None
break_interval = 0
def __system_idle_time():
"""
Get system idle time in minutes.
Return the idle time if xprintidle is available, otherwise return 0.
"""
try:
return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 60000 # Convert to minutes
except BaseException:
return 0
def __is_active():
"""
Thread safe function to see if this plugin is active or not.
"""
is_active = False
with lock:
is_active = active
return is_active
def __set_active(is_active):
"""
Thread safe function to change the state of the plugin.
"""
global active
with lock:
active = is_active
def init(ctx, safeeyes_config, plugin_config):
"""
Initialize the plugin.
"""
global context
global enable_safe_eyes
global disable_safe_eyes
global idle_time
global break_interval
logging.debug('Initialize Smart Pause plugin')
context = ctx
enable_safe_eyes = context['api']['enable_safeeyes']
disable_safe_eyes = context['api']['disable_safeeyes']
idle_time = plugin_config['idle_time']
break_interval = safeeyes_config.get('break_interval') * 60
def __start_idle_monitor():
"""
Continuously check the system idle time and pause/resume Safe Eyes based on it.
"""
global smart_pause_activated
global idle_start_time
while __is_active():
# Wait for 2 seconds
idle_condition.acquire()
idle_condition.wait(2)
idle_condition.release()
if __is_active():
# Get the system idle time
system_idle_time = __system_idle_time()
if system_idle_time >= idle_time and context['state'] == State.WAITING:
smart_pause_activated = True
idle_start_time = datetime.datetime.now()
logging.info('Pause Safe Eyes due to system idle')
disable_safe_eyes()
elif system_idle_time < idle_time and context['state'] == State.STOPPED:
logging.info('Resume Safe Eyes due to user activity')
smart_pause_activated = False
idle_period = (datetime.datetime.now() - idle_start_time)
if idle_period.total_seconds() < break_interval:
next_break = next_break_time + idle_period
enable_safe_eyes(next_break.timestamp())
else:
enable_safe_eyes()
def on_start():
"""
Start a thread to continuously call xprintidle.
"""
global active
if not __is_active():
# If SmartPause is already started, do not start it again
logging.debug('Start Smart Pause plugin')
__set_active(True)
Utility.start_thread(__start_idle_monitor)
def on_stop():
"""
Stop the thread from continuously calling xprintidle.
"""
global active
global smart_pause_activated
if smart_pause_activated:
# Safe Eyes is stopped due to system idle
smart_pause_activated = False
return
logging.debug('Stop Smart Pause plugin')
__set_active(False)
idle_condition.acquire()
idle_condition.notify_all()
idle_condition.release()
def update_next_break(dateTime):
"""
Update the next break time.
"""
global next_break_time
next_break_time = dateTime

View File

@ -0,0 +1,45 @@
{
"meta": {
"name": "Tray Icon",
"description": "Show a tray icon in the notification area",
"version": "0.0.1"
},
"dependencies": {
"python_modules": [],
"shell_commands": [],
"operating_systems": [],
"desktop_environments": [],
"resources": []
},
"settings": [
{
"id": "show_time_in_tray",
"label": "Show next break time in tray icon",
"type": "BOOL",
"default": false
},
{
"id": "disable_options",
"label": "Disable options",
"type": "HIDDEN",
"default": [
{
"time": 30,
"unit": "minute"
},
{
"time": 1,
"unit": "hour"
},
{
"time": 2,
"unit": "hour"
},
{
"time": 3,
"unit": "hour"
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

View File

@ -0,0 +1,406 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Gtk
import logging
from safeeyes import Utility
import threading
"""
Safe Eyes tray icon plugin
"""
APPINDICATOR_ID = 'safeeyes_2'
context = None
tray_icon = None
safeeyes_config = None
class TrayIcon(object):
"""
Create and show the tray icon along with the tray menu.
"""
def __init__(self, context, plugin_config):
self.context = context
self.on_show_settings = context['api']['show_settings']
self.on_show_about = context['api']['show_about']
self.quit = context['api']['quit']
self.on_enable = context['api']['enable_safeeyes']
self.on_disable = context['api']['disable_safeeyes']
self.take_break = context['api']['take_break']
self.has_breaks = context['api']['has_breaks']
self.plugin_config = plugin_config
self.date_time = None
self.active = True
self.wakeup_time = None
self.idle_condition = threading.Condition()
self.lock = threading.Lock()
# Construct the tray icon
self.indicator = appindicator.Indicator.new(
APPINDICATOR_ID, "safeeyes_enabled", appindicator.IndicatorCategory.APPLICATION_STATUS)
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
# Construct the context menu
self.menu = Gtk.Menu()
# Next break info menu item
self.item_info = Gtk.ImageMenuItem()
img_timer = Gtk.Image()
img_timer.set_from_icon_name("safeeyes_timer", 16)
self.item_info.set_image(img_timer)
self.item_separator = Gtk.SeparatorMenuItem()
self.item_enable = Gtk.MenuItem()
self.item_enable.connect('activate', self.on_enable_clicked)
self.item_disable = Gtk.MenuItem()
self.item_disable.connect('activate', self.on_disable_clicked)
self.sub_menu_disable = Gtk.Menu()
self.sub_menu_items = []
# Read disable options and build the sub menu
for disable_option in plugin_config['disable_options']:
time_in_minutes = disable_option['time']
label = []
# Validate time value
if not isinstance(time_in_minutes, int) or time_in_minutes <= 0:
logging.error('Invalid time in disable option: ' + str(time_in_minutes))
continue
time_unit = disable_option['unit'].lower()
if time_unit == 'seconds' or time_unit == 'second':
time_in_minutes = int(time_in_minutes / 60)
label = ['For %d Second', 'For %d Seconds']
elif time_unit == 'minutes' or time_unit == 'minute':
time_in_minutes = int(time_in_minutes * 1)
label = ['For %d Minute', 'For %d Minutes']
elif time_unit == 'hours' or time_unit == 'hour':
time_in_minutes = int(time_in_minutes * 60)
label = ['For %d Hour', 'For %d Hours']
else:
# Invalid unit
logging.error('Invalid unit in disable option: ' + str(disable_option))
continue
# Create submenu
sub_menu_item = Gtk.MenuItem()
sub_menu_item.connect('activate', self.on_disable_clicked, time_in_minutes)
self.sub_menu_items.append([sub_menu_item, label, disable_option['time']])
self.sub_menu_disable.append(sub_menu_item)
# Disable until restart submenu
self.sub_menu_item_until_restart = Gtk.MenuItem()
self.sub_menu_item_until_restart.connect('activate', self.on_disable_clicked, -1)
self.sub_menu_disable.append(self.sub_menu_item_until_restart)
# Add the sub menu to the enable/disable menu
self.item_disable.set_submenu(self.sub_menu_disable)
# Settings menu item
self.item_manual_break = Gtk.MenuItem()
self.item_manual_break.connect('activate', self.on_manual_break_clicked)
# Settings menu item
self.item_settings = Gtk.MenuItem()
self.item_settings.connect('activate', self.show_settings)
# About menu item
self.item_about = Gtk.MenuItem()
self.item_about.connect('activate', self.show_about)
# Quit menu item
self.item_quit = Gtk.MenuItem()
self.item_quit.connect('activate', self.quit_safe_eyes)
self.set_labels()
# At startup, no need for activate menu
self.item_enable.set_sensitive(False)
# Append all menu items and show the menu
self.menu.append(self.item_info)
self.menu.append(self.item_separator)
self.menu.append(self.item_enable)
self.menu.append(self.item_disable)
self.menu.append(self.item_manual_break)
self.menu.append(self.item_settings)
self.menu.append(self.item_about)
self.menu.append(self.item_quit)
self.menu.show_all()
self.indicator.set_menu(self.menu)
def initialize(self, plugin_config):
"""
Initialize the tray icon by setting the config.
"""
self.plugin_config = plugin_config
self.set_labels()
def set_labels(self):
"""
Update the text of menu items based on the selected language.
"""
for entry in self.sub_menu_items:
# print(self.context['locale'].ngettext('For %d Hour', 'For %d Hours', 1) % 1)
entry[0].set_label(self.context['locale'].ngettext(entry[1][0], entry[1][1], entry[2]) % entry[2])
self.sub_menu_item_until_restart.set_label(_('Until restart'))
self.item_enable.set_label(_('Enable Safe Eyes'))
self.item_disable.set_label(_('Disable Safe Eyes'))
breaks_found = self.has_breaks()
if breaks_found:
if self.active:
if self.date_time:
self.__set_next_break_info()
self.indicator.set_icon("safeeyes_enabled")
else:
if self.wakeup_time:
self.item_info.set_label(_('Disabled until %s') % Utility.format_time(self.wakeup_time))
else:
self.item_info.set_label(_('Disabled until restart'))
self.indicator.set_label('', '')
self.indicator.set_icon("safeeyes_disabled")
else:
self.item_info.set_label(_('No Breaks Available'))
self.indicator.set_label('', '')
self.indicator.set_icon("safeeyes_disabled")
self.item_info.set_sensitive(breaks_found and self.active)
self.item_enable.set_sensitive(breaks_found and not self.active)
self.item_disable.set_sensitive(breaks_found and self.active)
self.item_manual_break.set_sensitive(breaks_found and self.active)
self.item_manual_break.set_label(_('Take a break now'))
self.item_settings.set_label(_('Settings'))
self.item_about.set_label(_('About'))
self.item_quit.set_label(_('Quit'))
def show_icon(self):
"""
Show the tray icon.
"""
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
def hide_icon(self):
"""
Hide the tray icon.
"""
self.indicator.set_status(appindicator.IndicatorStatus.PASSIVE)
def quit_safe_eyes(self, *args):
"""
Handle Quit menu action.
This action terminates the application.
"""
self.quit()
with self.lock:
self.active = True
# Notify all schedulers
self.idle_condition.acquire()
self.idle_condition.notify_all()
self.idle_condition.release()
def show_settings(self, *args):
"""
Handle Settings menu action.
This action shows the Settings dialog.
"""
self.on_show_settings()
def show_about(self, *args):
"""
Handle About menu action.
This action shows the About dialog.
"""
self.on_show_about()
def next_break_time(self, dateTime):
"""
Update the next break time to be displayed in the menu and optionally in the tray icon.
"""
logging.info("Update next break information")
self.date_time = dateTime
self.__set_next_break_info()
def __set_next_break_info(self):
"""
A private method to be called within this class to update the next break information using self.dateTime.
"""
formatted_time = Utility.format_time(self.date_time)
message = _('Next break at %s') % (formatted_time)
# Update the menu item label
Utility.execute_main_thread(self.item_info.set_label, message)
# Update the tray icon label
if self.plugin_config.get('show_time_in_tray', False):
self.indicator.set_label(formatted_time, '')
else:
self.indicator.set_label('', '')
def on_manual_break_clicked(self, *args):
"""
Trigger a break manually.
"""
self.take_break()
def on_enable_clicked(self, *args):
"""
Handle 'Enable Safe Eyes' menu action.
This action enables the application if it is currently disabled.
"""
# active = self.item_enable.get_active()
if not self.active:
with self.lock:
self.enable_ui()
self.on_enable()
# Notify all schedulers
self.idle_condition.acquire()
self.idle_condition.notify_all()
self.idle_condition.release()
def on_disable_clicked(self, *args):
"""
Handle the menu actions of all the sub menus of 'Disable Safe Eyes'.
This action disables the application if it is currently active.
"""
# active = self.item_enable.get_active()
if self.active and len(args) > 1:
self.disable_ui()
self.on_disable()
time_to_wait = args[1]
if time_to_wait <= 0:
self.wakeup_time = None
self.item_info.set_label(_('Disabled until restart'))
else:
self.wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
Utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait)
self.item_info.set_label(_('Disabled until %s') % Utility.format_time(self.wakeup_time))
def lock_menu(self):
"""
This method is called by the core to prevent user from disabling Safe Eyes after the notification.
"""
if self.active:
self.menu.set_sensitive(False)
def unlock_menu(self):
"""
This method is called by the core to activate the menu after the the break.
"""
if self.active:
self.menu.set_sensitive(True)
def disable_ui(self):
"""
Change the UI to disabled state.
"""
if self.active:
logging.info('Disable Safe Eyes')
self.active = False
self.indicator.set_icon("safeeyes_disabled")
self.item_info.set_label(_('Disabled until restart'))
self.indicator.set_label('', '')
self.item_info.set_sensitive(False)
self.item_enable.set_sensitive(True)
self.item_disable.set_sensitive(False)
self.item_manual_break.set_sensitive(False)
def enable_ui(self):
"""
Change the UI to enabled state.
"""
if not self.active:
logging.info('Enable Safe Eyes')
self.active = True
self.indicator.set_icon("safeeyes_enabled")
self.item_info.set_sensitive(True)
self.item_enable.set_sensitive(False)
self.item_disable.set_sensitive(True)
self.item_manual_break.set_sensitive(True)
def __schedule_resume(self, time_minutes):
"""
Schedule a local timer to enable Safe Eyes after the given timeout.
"""
self.idle_condition.acquire()
self.idle_condition.wait(time_minutes * 60) # Convert to seconds
self.idle_condition.release()
with self.lock:
if not self.active:
Utility.execute_main_thread(self.item_enable.activate)
def init(ctx, safeeyes_cfg, plugin_config):
"""
Initialize the tray icon.
"""
global context
global tray_icon
global safeeyes_config
logging.debug('Initialize Tray Icon plugin')
context = ctx
safeeyes_config = safeeyes_cfg
if not tray_icon:
tray_icon = TrayIcon(context, plugin_config)
else:
tray_icon.initialize(plugin_config)
def update_next_break(dateTime):
"""
Update the next break time.
"""
tray_icon.next_break_time(dateTime)
def on_pre_break(break_obj):
"""
Disable the menu if strict_break is enabled
"""
if safeeyes_config.get('strict_break'):
tray_icon.lock_menu()
threading.Timer(safeeyes_config.get('pre_break_warning_time'), __unlock_menu).start()
def __unlock_menu():
"""
Unlock the menu
"""
Utility.execute_main_thread(tray_icon.unlock_menu)
def on_start():
"""
Enable the tray icon.
"""
tray_icon.enable_ui()
def on_stop():
"""
Disable the tray icon.
"""
tray_icon.disable_ui()

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

111
safeeyes/rpc.py Normal file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env python
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2017 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
RPC server and client implementation.
"""
import logging
from threading import Thread
from safeeyes import Utility
from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.client import ServerProxy
class RPCServer(object):
"""
An aynchronous RPC server.
"""
def __init__(self, port, context):
self.__running = False
logging.info('Setting up an RPC server on port %d', port)
self.__server = SimpleXMLRPCServer(("localhost", port), logRequests=False, allow_none=True)
self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['show_settings']), 'show_settings')
self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['show_about']), 'show_about')
self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['enable_safeeyes']), 'enable_safeeyes')
self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['disable_safeeyes']), 'disable_safeeyes')
self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['take_break']), 'take_break')
self.__server.register_function(lambda: Utility.execute_main_thread(context['api']['quit']), 'quit')
def start(self):
"""
Start the RPC server.
"""
if not self.__running:
self.__running = True
logging.info('Start the RPC server')
server_thread = Thread(target=self.__server.serve_forever)
server_thread.start()
def stop(self):
"""
Stop the server.
"""
if self.__running:
logging.info('Stop the RPC server')
self.__running = False
self.__server.shutdown()
class RPCClient(object):
"""
An RPC client to communicate with the RPC server.
"""
def __init__(self, port):
self.port = port
def show_settings(self):
"""
Show the settings dialog.
"""
with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy:
proxy.show_settings()
def show_about(self):
"""
Show the about dialog.
"""
with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy:
return proxy.show_about()
def enable_safeeyes(self):
"""
Enable Safe Eyes.
"""
with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy:
return proxy.enable_safeeyes()
def disable_safeeyes(self):
"""
Disable Safe Eyes.
"""
with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy:
return proxy.disable_safeeyes()
def take_break(self):
"""
Take a break now.
"""
with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy:
return proxy.take_break()
def quit(self):
"""
Quit Safe Eyes.
"""
with ServerProxy('http://localhost:%d/' % self.port, allow_none=True) as proxy:
return proxy.quit()

542
safeeyes/settings.py Normal file
View File

@ -0,0 +1,542 @@
# Safe Eyes is a utility to remind you to take break frequently
# to protect your eyes from eye strain.
# Copyright (C) 2016 Gobinath
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import math
import os
import gi
from safeeyes import Utility
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GdkPixbuf
SETTINGS_DIALOG_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/settings_dialog.glade")
SETTINGS_DIALOG_PLUGIN_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/settings_plugin.glade")
SETTINGS_DIALOG_BREAK_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/settings_break.glade")
SETTINGS_DIALOG_NEW_BREAK_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/new_break.glade")
SETTINGS_BREAK_ITEM_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/item_break.glade")
SETTINGS_PLUGIN_ITEM_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/item_plugin.glade")
SETTINGS_ITEM_INT_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/item_int.glade")
SETTINGS_ITEM_TEXT_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/item_text.glade")
SETTINGS_ITEM_BOOL_GLADE = os.path.join(Utility.BIN_DIRECTORY, "glade/item_bool.glade")
class SettingsDialog(object):
"""
Create and initialize SettingsDialog instance.
"""
def __init__(self, config, on_save_settings):
self.config = config
self.on_save_settings = on_save_settings
self.plugin_switches = {}
self.plugin_map = {}
self.last_short_break_interval = config.get('break_interval')
self.initializing = True
self.infobar_long_break_shown = False
builder = Utility.create_gtk_builder(SETTINGS_DIALOG_GLADE)
builder.connect_signals(self)
self.window = builder.get_object('window_settings')
self.box_short_breaks = builder.get_object('box_short_breaks')
self.box_long_breaks = builder.get_object('box_long_breaks')
box_plugins = builder.get_object('box_plugins')
for short_break in config.get('short_breaks'):
self.__create_break_item(short_break, True)
for long_break in config.get('long_breaks'):
self.__create_break_item(long_break, False)
for plugin_config in Utility.load_plugins_config(config):
box_plugins.pack_start(self.__create_plugin_item(plugin_config), False, False, 0)
self.spin_short_break_duration = builder.get_object('spin_short_break_duration')
self.spin_long_break_duration = builder.get_object('spin_long_break_duration')
self.spin_short_break_interval = builder.get_object('spin_short_break_interval')
self.spin_long_break_interval = builder.get_object('spin_long_break_interval')
self.spin_time_to_prepare = builder.get_object('spin_time_to_prepare')
self.spin_postpone_duration = builder.get_object('spin_postpone_duration')
self.spin_disable_keyboard_shortcut = builder.get_object('spin_disable_keyboard_shortcut')
self.switch_strict_break = builder.get_object('switch_strict_break')
self.switch_postpone = builder.get_object('switch_postpone')
self.switch_persist = builder.get_object('switch_persist')
self.info_bar_long_break = builder.get_object("info_bar_long_break")
self.info_bar_long_break.hide()
# Set the current values of input fields
self.spin_short_break_duration.set_value(config.get('short_break_duration'))
self.spin_long_break_duration.set_value(config.get('long_break_duration'))
self.spin_short_break_interval.set_value(config.get('break_interval'))
self.spin_long_break_interval.set_value(config.get('no_of_short_breaks_per_long_break') * config.get('break_interval'))
self.spin_time_to_prepare.set_value(config.get('pre_break_warning_time'))
self.spin_postpone_duration.set_value(config.get('postpone_duration'))
self.spin_disable_keyboard_shortcut.set_value(config.get('shortcut_disable_time'))
self.switch_strict_break.set_active(config.get('strict_break'))
self.switch_postpone.set_active(config.get('allow_postpone') and not config.get('strict_break'))
self.switch_persist.set_active(config.get('persist_state'))
# Update relative states
# GtkSwitch state-set signal is available only from 3.14
if Gtk.get_minor_version() >= 14:
self.switch_strict_break.connect('state-set', self.on_switch_strict_break_activate)
self.switch_postpone.connect('state-set', self.on_switch_postpone_activate)
self.on_switch_strict_break_activate(self.switch_strict_break, self.switch_strict_break.get_active())
self.on_switch_postpone_activate(self.switch_postpone, self.switch_postpone.get_active())
self.initializing = False
def __create_break_item(self, break_config, is_short):
"""
Create an entry for break to be listed in the break tab.
"""
parent_box = self.box_long_breaks
if is_short:
parent_box = self.box_short_breaks
builder = Utility.create_gtk_builder(SETTINGS_BREAK_ITEM_GLADE)
box = builder.get_object('box')
lbl_name = builder.get_object('lbl_name')
lbl_name.set_label(_(break_config['name']))
btn_properties = builder.get_object('btn_properties')
btn_properties.connect(
'clicked',
lambda button: self.__show_break_properties_dialog(
break_config,
is_short,
self.config,
lambda: parent_box.remove(box),
lambda cfg: lbl_name.set_label(_(cfg['name'])),
lambda is_short,
break_config: self.__create_break_item(break_config, is_short)
)
)
box.set_visible(True)
parent_box.pack_start(box, False, False, 0)
return box
def __create_plugin_item(self, plugin_config):
"""
Create an entry for plugin to be listed in the plugin tab.
"""
builder = Utility.create_gtk_builder(SETTINGS_PLUGIN_ITEM_GLADE)
lbl_plugin_name = builder.get_object('lbl_plugin_name')
lbl_plugin_description = builder.get_object('lbl_plugin_description')
switch_enable = builder.get_object('switch_enable')
btn_properties = builder.get_object('btn_properties')
lbl_plugin_name.set_label(_(plugin_config['meta']['name']))
switch_enable.set_active(plugin_config['enabled'])
if plugin_config['error']:
lbl_plugin_description.set_label(_(plugin_config['meta']['description']))
lbl_plugin_name.set_sensitive(False)
lbl_plugin_description.set_sensitive(False)
switch_enable.set_sensitive(False)
else:
lbl_plugin_description.set_label(_(plugin_config['meta']['description']))
self.plugin_switches[plugin_config['id']] = switch_enable
if plugin_config.get('break_override_allowed', False):
self.plugin_map[plugin_config['id']] = plugin_config['meta']['name']
if plugin_config['icon']:
builder.get_object('img_plugin_icon').set_from_file(plugin_config['icon'])
if plugin_config['settings']:
btn_properties.set_sensitive(True)
btn_properties.connect('clicked', lambda button: self.__show_plugins_properties_dialog(plugin_config))
else:
btn_properties.set_sensitive(False)
box = builder.get_object('box')
box.set_visible(True)
return box
def __show_plugins_properties_dialog(self, plugin_config):
"""
Show the PluginProperties dialog
"""
dialog = PluginSettingsDialog(plugin_config)
dialog.show()
def __show_break_properties_dialog(self, break_config, is_short, parent, on_remove, on_close, on_add):
"""
Show the BreakProperties dialog
"""
dialog = BreakSettingsDialog(break_config, is_short, parent, self.plugin_map, on_remove, on_close, on_add)
dialog.show()
def show(self):
"""
Show the SettingsDialog.
"""
self.window.show_all()
def on_switch_strict_break_activate(self, switch, state):
"""
Event handler to the state change of the postpone switch.
Enable or disable the self.spin_postpone_duration based on the state of the postpone switch.
"""
strict_break_enable = state
self.switch_postpone.set_sensitive(not strict_break_enable)
self.spin_disable_keyboard_shortcut.set_sensitive(not strict_break_enable)
def on_switch_postpone_activate(self, switch, state):
"""
Event handler to the state change of the postpone switch.
Enable or disable the self.spin_postpone_duration based on the state of the postpone switch.
"""
self.spin_postpone_duration.set_sensitive(self.switch_postpone.get_active())
def on_spin_short_break_interval_change(self, spin_button, *value):
"""
Event handler for value change of short break interval.
"""
short_break_interval = self.spin_short_break_interval.get_value_as_int()
long_break_interval = self.spin_long_break_interval.get_value_as_int()
self.spin_long_break_interval.set_range(short_break_interval, 120)
self.spin_long_break_interval.set_increments(short_break_interval, short_break_interval * 2)
self.spin_long_break_interval.set_value(short_break_interval * math.ceil(long_break_interval / self.last_short_break_interval))
self.last_short_break_interval = short_break_interval
if not self.initializing and not self.infobar_long_break_shown:
self.infobar_long_break_shown = True
self.info_bar_long_break.show()
def on_spin_long_break_interval_change(self, spin_button, *value):
"""
Event handler for value change of long break interval.
"""
if not self.initializing and not self.infobar_long_break_shown:
self.infobar_long_break_shown = True
self.info_bar_long_break.show()
def on_info_bar_long_break_close(self, infobar, *user_data):
"""
Event handler for info bar close action.
"""
self.info_bar_long_break.hide()
def add_break(self, button):
"""
Event handler for add break button.
"""
dialog = NewBreakDialog(self.config, lambda is_short, break_config: self.__create_break_item(break_config, is_short))
dialog.show()
def on_window_delete(self, *args):
"""
Event handler for Settings dialog close action.
"""
self.config.set('short_break_duration', self.spin_short_break_duration.get_value_as_int())
self.config.set('long_break_duration', self.spin_long_break_duration.get_value_as_int())
self.config.set('break_interval', self.spin_short_break_interval.get_value_as_int())
self.config.set('no_of_short_breaks_per_long_break', math.floor(self.spin_long_break_interval.get_value_as_int() / self.spin_short_break_interval.get_value_as_int()))
self.config.set('pre_break_warning_time', self.spin_time_to_prepare.get_value_as_int())
self.config.set('postpone_duration', self.spin_postpone_duration.get_value_as_int())
self.config.set('shortcut_disable_time', self.spin_disable_keyboard_shortcut.get_value_as_int())
self.config.set('strict_break', self.switch_strict_break.get_active())
self.config.set('allow_postpone', self.switch_postpone.get_active())
self.config.set('persist_state', self.switch_persist.get_active())
for plugin in self.config.get('plugins'):
if plugin['id'] in self.plugin_switches:
plugin['enabled'] = self.plugin_switches[plugin['id']].get_active()
self.on_save_settings(self.config) # Call the provided save method
self.window.destroy()
class PluginSettingsDialog(object):
"""
Builds a settings dialog based on the configuration of a plugin.
"""
def __init__(self, config):
self.config = config
self.property_controls = []
builder = Utility.create_gtk_builder(SETTINGS_DIALOG_PLUGIN_GLADE)
builder.connect_signals(self)
self.window = builder.get_object('dialog_settings_plugin')
box_settings = builder.get_object('box_settings')
self.window.set_title(_('Plugin Settings'))
for setting in config.get('settings'):
if setting['type'].upper() == 'INT':
box_settings.pack_start(self.__load_int_item(setting['label'], setting['id'], setting['safeeyes_config']), False, False, 0)
elif setting['type'].upper() == 'TEXT':
box_settings.pack_start(self.__load_text_item(setting['label'], setting['id'], setting['safeeyes_config']), False, False, 0)
elif setting['type'].upper() == 'BOOL':
box_settings.pack_start(self.__load_bool_item(setting['label'], setting['id'], setting['safeeyes_config']), False, False, 0)
def __load_int_item(self, name, key, settings):
"""
Load the UI control for int property.
"""
builder = Utility.create_gtk_builder(SETTINGS_ITEM_INT_GLADE)
builder.get_object('lbl_name').set_label(_(name))
spin_value = builder.get_object('spin_value')
spin_value.set_value(settings[key])
box = builder.get_object('box')
box.set_visible(True)
self.property_controls.append({'key': key, 'settings': settings, 'value': spin_value.get_value})
return box
def __load_text_item(self, name, key, settings):
"""
Load the UI control for text property.
"""
builder = Utility.create_gtk_builder(SETTINGS_ITEM_TEXT_GLADE)
builder.get_object('lbl_name').set_label(_(name))
txt_value = builder.get_object('txt_value')
txt_value.set_text(settings[key])
box = builder.get_object('box')
box.set_visible(True)
self.property_controls.append({'key': key, 'settings': settings, 'value': txt_value.get_text})
return box
def __load_bool_item(self, name, key, settings):
"""
Load the UI control for boolean property.
"""
builder = Utility.create_gtk_builder(SETTINGS_ITEM_BOOL_GLADE)
builder.get_object('lbl_name').set_label(_(name))
switch_value = builder.get_object('switch_value')
switch_value.set_active(settings[key])
box = builder.get_object('box')
box.set_visible(True)
self.property_controls.append({'key': key, 'settings': settings, 'value': switch_value.get_active})
return box
def on_window_delete(self, *args):
"""
Event handler for Properties dialog close action.
"""
for property_control in self.property_controls:
property_control['settings'][property_control['key']] = property_control['value']()
self.window.destroy()
def show(self):
"""
Show the Properties dialog.
"""
self.window.show_all()
class BreakSettingsDialog(object):
"""
Builds a settings dialog based on the configuration of a plugin.
"""
def __init__(self, break_config, is_short, parent_config, plugin_map, on_remove, on_close, on_add):
self.break_config = break_config
self.parent_config = parent_config
self.plugin_check_buttons = {}
self.on_remove = on_remove
self.on_close = on_close
self.is_short = is_short
self.on_add = on_add
builder = Utility.create_gtk_builder(SETTINGS_DIALOG_BREAK_GLADE)
builder.connect_signals(self)
self.window = builder.get_object('dialog_settings_break')
self.txt_break = builder.get_object('txt_break')
self.switch_override_duration = builder.get_object('switch_override_duration')
self.switch_override_plugins = builder.get_object('switch_override_plugins')
self.spin_duration = builder.get_object('spin_duration')
self.img_break = builder.get_object('img_break')
self.cmb_type = builder.get_object('cmb_type')
# User cannot remove the last break
not_last_break = (len(parent_config.get('short_breaks')) + len(parent_config.get('long_breaks'))) > 1
builder.get_object('btn_remove').set_sensitive(not_last_break)
grid_plugins = builder.get_object('grid_plugins')
list_types = builder.get_object('lst_break_types')
duration_overriden = break_config.get('duration', None) is not None
plugins_overriden = break_config.get('plugins', None) is not None
# Set the values
self.window.set_title(_('Break Settings'))
self.txt_break.set_text(_(break_config['name']))
self.switch_override_duration.set_active(duration_overriden)
self.switch_override_plugins.set_active(plugins_overriden)
self.cmb_type.set_active(0 if is_short else 1)
list_types[0][0] = _(list_types[0][0])
list_types[1][0] = _(list_types[1][0])
if duration_overriden:
self.spin_duration.set_value(break_config['duration'])
else:
if is_short:
self.spin_duration.set_value(parent_config.get('short_break_duration'))
else:
self.spin_duration.set_value(parent_config.get('long_break_duration'))
row = 0
col = 0
for plugin_id in plugin_map.keys():
chk_button = Gtk.CheckButton(_(plugin_map[plugin_id]))
self.plugin_check_buttons[plugin_id] = chk_button
grid_plugins.attach(chk_button, row, col, 1, 1)
if plugins_overriden:
chk_button.set_active(plugin_id in break_config['plugins'])
else:
chk_button.set_active(True)
row += 1
if row > 2:
col += 1
row = 0
# GtkSwitch state-set signal is available only from 3.14
if Gtk.get_minor_version() >= 14:
self.switch_override_duration.connect('state-set', self.on_switch_override_duration_activate)
self.switch_override_plugins.connect('state-set', self.on_switch_override_plugins_activate)
self.on_switch_override_duration_activate(self.switch_override_duration, self.switch_override_duration.get_active())
self.on_switch_override_plugins_activate(self.switch_override_plugins, self.switch_override_plugins.get_active())
def on_switch_override_duration_activate(self, switch_button, state):
"""
switch_override_duration state change event handler.
"""
self.spin_duration.set_sensitive(state)
def on_switch_override_plugins_activate(self, switch_button, state):
"""
switch_override_plugins state change event handler.
"""
for chk_box in self.plugin_check_buttons.values():
chk_box.set_sensitive(state)
def select_image(self, button):
"""
Show a file chooser dialog and let the user to select an image.
"""
dialog = Gtk.FileChooserDialog(_('Please select an image'), self.window, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
png_filter = Gtk.FileFilter()
png_filter.set_name("PNG files")
png_filter.add_mime_type("image/png")
png_filter.add_pattern("*.png")
dialog.add_filter(png_filter)
response = dialog.run()
if response == Gtk.ResponseType.OK:
self.break_config['image'] = dialog.get_filename()
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.break_config['image'], 16, 16, True)
self.img_break.set_from_pixbuf(pixbuf)
elif response == Gtk.ResponseType.CANCEL:
self.break_config.pop('image', None)
self.img_break.set_from_stock('gtk-missing-image', Gtk.IconSize.BUTTON)
dialog.destroy()
def remove_break(self, button):
"""
Remove the break
"""
if self.is_short:
self.parent_config.get('short_breaks').remove(self.break_config)
else:
self.parent_config.get('long_breaks').remove(self.break_config)
self.on_remove()
self.window.destroy()
def on_window_delete(self, *args):
"""
Event handler for Properties dialog close action.
"""
break_name = self.txt_break.get_text().strip()
if break_name:
self.break_config['name'] = break_name
if self.switch_override_duration.get_active():
self.break_config['duration'] = int(self.spin_duration.get_value())
else:
self.break_config.pop('duration', None)
if self.switch_override_plugins.get_active():
selected_plugins = []
for plugin_id in self.plugin_check_buttons:
if self.plugin_check_buttons[plugin_id].get_active():
selected_plugins.append(plugin_id)
self.break_config['plugins'] = selected_plugins
else:
self.break_config.pop('plugins', None)
if self.is_short and self.cmb_type.get_active() == 1:
# Changed from short to long
self.parent_config.get('short_breaks').remove(self.break_config)
self.parent_config.get('long_breaks').append(self.break_config)
self.on_remove()
self.on_add(not self.is_short, self.break_config)
elif not self.is_short and self.cmb_type.get_active() == 0:
# Changed from long to short
self.parent_config.get('long_breaks').remove(self.break_config)
self.parent_config.get('short_breaks').append(self.break_config)
self.on_remove()
self.on_add(not self.is_short, self.break_config)
else:
self.on_close(self.break_config)
self.window.destroy()
def show(self):
"""
Show the Properties dialog.
"""
self.window.show_all()
class NewBreakDialog(object):
"""
Builds a new break dialog.
"""
def __init__(self, parent_config, on_add):
self.parent_config = parent_config
self.on_add = on_add
builder = Utility.create_gtk_builder(SETTINGS_DIALOG_NEW_BREAK_GLADE)
builder.connect_signals(self)
self.window = builder.get_object('dialog_new_break')
self.txt_break = builder.get_object('txt_break')
self.cmb_type = builder.get_object('cmb_type')
list_types = builder.get_object('lst_break_types')
list_types[0][0] = _(list_types[0][0])
list_types[1][0] = _(list_types[1][0])
# Set the values
self.window.set_title(_('New Break'))
def discard(self, button):
"""
Close the dialog.
"""
self.window.destroy()
def save(self, button):
"""
Event handler for Properties dialog close action.
"""
break_config = {'name': self.txt_break.get_text().strip()}
if self.cmb_type.get_active() == 0:
self.parent_config.get('short_breaks').append(break_config)
self.on_add(True, break_config)
else:
self.parent_config.get('long_breaks').append(break_config)
self.on_add(False, break_config)
self.window.destroy()
def on_window_delete(self, *args):
"""
Event handler for dialog close action.
"""
self.window.destroy()
def show(self):
"""
Show the Properties dialog.
"""
self.window.show_all()

View File

@ -1,4 +1,5 @@
import os
import subprocess
import setuptools
@ -7,27 +8,57 @@ requires = [
'psutil',
'babel']
extras = {
'audible_alert': ['pyaudio']
}
_ROOT = os.path.abspath(os.path.dirname(__file__))
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.md')) as f:
with open(os.path.join(_ROOT, 'README.md')) as f:
long_description = '\n' + f.read()
def __compile_po_files():
"""
Compile the *.po trainslation files.
"""
localedir = 'safeeyes/config/locale'
po_dirs = [localedir + '/' + l + '/LC_MESSAGES/'
for l in next(os.walk(localedir))[1]]
for po_dir in po_dirs:
po_files = [f
for f in next(os.walk(po_dir))[2]
if os.path.splitext(f)[1] == '.po']
for po_file in po_files:
filename, ext = os.path.splitext(po_file)
mo_file = filename + '.mo'
msgfmt_cmd = 'msgfmt {} -o {}'.format(po_dir + po_file, po_dir + mo_file)
subprocess.call(msgfmt_cmd, shell=True)
def _data_files(path):
"""
Collect the data files.
"""
for root, dirs, files in os.walk(path):
if not files:
continue
yield (os.path.join('/usr', root), [os.path.join(root, f) for f in files])
def __package_files(directory):
"""
Collect the package files.
"""
paths = []
for (path, dirs, filenames) in os.walk(directory):
for filename in filenames:
paths.append(os.path.join('..', path, filename))
return paths
__compile_po_files()
__package_data = ['glade/*.glade', 'resource/*']
__package_data.extend(__package_files('safeeyes/config'))
__package_data.extend(__package_files('safeeyes/plugins'))
__data_files = list(_data_files('share'))
setuptools.setup(
name="safeeyes",
version="1.2.2",
version="2.0.0",
description="Protect your eyes from eye strain using this continuous breaks reminder.",
long_description=long_description,
author="Gobinath Loganathan",
@ -35,25 +66,9 @@ setuptools.setup(
url="https://github.com/slgobinath/SafeEyes",
download_url="https://github.com/slgobinath/SafeEyes/archive/v1.2.2.tar.gz",
packages=setuptools.find_packages(),
package_data={'safeeyes': ['config/*.json',
'config/style/*.css',
'config/lang/*.json',
'glade/*.glade',
'resource/*']},
data_files=[('/usr/share/applications', ['share/applications/safeeyes.desktop']),
('/usr/share/icons/hicolor/16x16/apps', ['share/icons/hicolor/16x16/apps/safeeyes.png']),
('/usr/share/icons/hicolor/24x24/apps', ['share/icons/hicolor/24x24/apps/safeeyes.png']),
('/usr/share/icons/hicolor/48x48/apps', ['share/icons/hicolor/48x48/apps/safeeyes.png']),
('/usr/share/icons/hicolor/32x32/apps', ['share/icons/hicolor/32x32/apps/safeeyes.png']),
('/usr/share/icons/hicolor/64x64/apps', ['share/icons/hicolor/64x64/apps/safeeyes.png']),
('/usr/share/icons/hicolor/128x128/apps', ['share/icons/hicolor/128x128/apps/safeeyes.png']),
('/usr/share/icons/hicolor/48x48/status', ['share/icons/hicolor/48x48/status/safeeyes_enabled.png', 'share/icons/hicolor/48x48/status/safeeyes_disabled.png']),
('/usr/share/icons/hicolor/32x32/status', ['share/icons/hicolor/32x32/status/safeeyes_enabled.png', 'share/icons/hicolor/32x32/status/safeeyes_disabled.png']),
('/usr/share/icons/hicolor/24x24/status', ['share/icons/hicolor/24x24/status/safeeyes_enabled.png', 'share/icons/hicolor/24x24/status/safeeyes_disabled.png', 'share/icons/hicolor/24x24/status/safeeyes_timer.png']),
('/usr/share/icons/hicolor/16x16/status', ['share/icons/hicolor/16x16/status/safeeyes_enabled.png', 'share/icons/hicolor/16x16/status/safeeyes_disabled.png', 'share/icons/hicolor/16x16/status/safeeyes_timer.png'])
],
package_data={'safeeyes': __package_data},
data_files=__data_files,
install_requires=requires,
extras_require=extras,
entry_points={'console_scripts': ['safeeyes = safeeyes.__main__:main']},
keywords='linux utility health eye-strain safe-eyes',
classifiers=[