Merge pull request #3 from slgobinath/master

Up-to-date with original repository
This commit is contained in:
Wojciech Ceret 2017-08-02 10:41:00 +02:00 committed by GitHub
commit 041e260d43
36 changed files with 919 additions and 417 deletions

View File

@ -53,21 +53,26 @@ Optional Features:
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.

12
debian/changelog vendored
View File

@ -1,4 +1,14 @@
safeeyes (1.2.1-1) xenial; urgency=low
safeeyes (1.2.2-1) xenial; urgency=low
* Show next break time in tray icon in supported environments
* Enable keyboard shortcuts
* Fix screen flickering in KDE
* Show the break type in the notification
* Make pyaudio optional
* Support postponing the break

View File

@ -18,18 +18,15 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkX11
from gi.repository import Gtk
"""
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:
"""
"""
Read the about_dialog.glade and build the user interface.
"""
def __init__(self, glade_file, version, language):
builder = Gtk.Builder()
builder.add_from_file(glade_file)
@ -42,23 +39,20 @@ class AboutDialog:
# Set the version at the runtime
builder.get_object("lbl_app_name").set_label("Safe Eyes " + version)
"""
Show the About dialog.
"""
def show(self):
"""
Show the About dialog.
"""
self.window.show_all()
"""
Window close event handler.
"""
def on_window_delete(self, *args):
"""
Window close event handler.
"""
self.window.destroy()
"""
Close button click event handler.
"""
def on_close_clicked(self, button):
"""
Close button click event handler.
"""
self.window.destroy()

View File

@ -16,104 +16,114 @@
# 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, signal, sys, threading, logging
from Xlib import Xatom, Xutil
import gi, threading, logging, time
from Xlib.display import Display, X
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkX11
from gi.repository import Gtk, Gdk, GLib
"""
The fullscreen window which prevents users from using the computer.
"""
class BreakScreen:
"""
The fullscreen window which prevents users from using the computer.
This class reads the break_screen.glade and build the user interface.
"""
"""
Read the break_screen.glade and build the user interface.
"""
def __init__(self, on_skip, on_postpone, glade_file, style_sheet_path):
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.key_lock_condition = threading.Condition()
self.windows = []
self.count_labels = []
self.glade_file = glade_file
self.enable_shortcut = False
self.display = Display()
# 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 internal properties from configuration
"""
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)
"""
Window close event handler.
"""
def on_window_delete(self, *args):
logging.info("Closing the break screen")
self.lock_keyboard = False
self.close()
"""
Skip button press event handler.
"""
def on_skip_clicked(self, button):
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()
"""
Postpone button press event handler.
"""
def on_postpone_clicked(self, button):
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_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 show_count_down(self, count_down, seconds):
"""
Show/update the count down on all screens.
"""
def show_count_down(self, count):
GLib.idle_add(lambda: self.__update_count_down(count))
"""
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))
"""
Show the break screen with the given message on all displays.
"""
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))
"""
Hide the break screen from active window and destroy all other windows
"""
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())
"""
Show an empty break screen on 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()
@ -156,72 +166,76 @@ class BreakScreen:
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']);
lbl_left.set_markup(plugins_data['left'])
lbl_right.set_markup(plugins_data['right'])
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)
window.move(x, y)
window.stick()
window.set_keep_above(True)
window.present()
window.move(x, y)
window.fullscreen()
"""
Update the countdown on all break screens.
"""
def __update_count_down(self, count):
"""
Update the countdown on all break screens.
"""
for label in self.count_labels:
label.set_text(count)
"""
Lock the keyboard to prevent the user from using keyboard shortcuts
"""
def __lock_keyboard(self):
"""
Lock the keyboard to prevent the user from using keyboard shortcuts
"""
logging.info("Lock the keyboard")
self.lock_keyboard = True
display = Display()
root = display.screen().root
# Grap the keyboard
root.grab_keyboard(owner_events = False, pointer_mode = X.GrabModeAsync, keyboard_mode = X.GrabModeAsync, time = 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
self.key_lock_condition.acquire()
while self.lock_keyboard:
self.key_lock_condition.wait()
self.key_lock_condition.release()
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)
# Ungrap the keyboard
logging.info("Unlock the keyboard")
display.ungrab_keyboard(X.CurrentTime)
display.flush()
"""
Release the locked keyboard.
"""
def __release_keyboard(self):
self.key_lock_condition.acquire()
"""
Release the locked keyboard.
"""
logging.info("Unlock the keyboard")
self.lock_keyboard = False
self.key_lock_condition.notify()
self.key_lock_condition.release()
self.display.ungrab_keyboard(X.CurrentTime)
self.display.flush()
"""
Close all the break screens.
"""
def __destroy_all_screens(self):
"""
Close all the break screens.
"""
for win in self.windows:
win.destroy()
del self.windows[:]

View File

@ -24,33 +24,43 @@ from safeeyes import Utility
APPINDICATOR_ID = 'safeeyes'
class Notification:
"""
This class is responsible for the notification to the user before the break.
This class is responsible for the notification to the user before the break.
"""
def __init__(self, language):
"""
Initialize the notification.
"""
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')
self.notification = Notify.Notification.new('Safe Eyes', '\n' + self.language['messages']['ready_for_a_break'].format(warning_time), icon='safeeyes_enabled')
# 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.
@ -62,10 +72,9 @@ class Notification:
# 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)
Utility.execute_main_thread(Notify.uninit)

View File

@ -23,16 +23,13 @@ from safeeyes import Utility
plugins_directory = os.path.join(Utility.config_directory, 'plugins')
sys.path.append(os.path.abspath(plugins_directory))
class Plugins:
"""
This class manages imports the plugins and calls the methods defined in those plugins.
Imports the Safe Eyes plugins and calls the methods defined in those plugins.
"""
def __init__(self, config):
"""
Load the plugins.
"""
logging.info('Load all the plugins')
self.__plugins = []
@ -47,18 +44,17 @@ class Plugins:
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'])
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
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,))
@ -70,14 +66,13 @@ class Plugins:
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
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.
@ -87,14 +82,14 @@ class Plugins:
"""
output = {'left': ' \n', 'right': ' \n'}
if self.__plugins:
context = copy.deepcopy(context) # If plugins change the context, it should not affect Safe Eyes
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()))
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
@ -105,13 +100,12 @@ class Plugins:
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
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,))
@ -123,7 +117,7 @@ class Plugins:
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
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]
@ -135,12 +129,11 @@ class Plugins:
pass
try:
self.__thread_pool.terminate() # Shutdown the pool
self.__thread_pool.terminate() # Shutdown the pool
except Exception as e:
pass
def __has_method(self, module, method_name, no_of_args = 1):
def __has_method(self, module, method_name, no_of_args=1):
"""
Check whether the given function is defined in the module or not.
"""

View File

@ -17,18 +17,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time, datetime, threading, sys, subprocess, logging
import time, datetime, threading, logging
from safeeyes import Utility
"""
Core of Safe Eyes which runs the scheduler and notifies the breaks.
"""
class SafeEyesCore:
"""
Core of Safe Eyes which runs the scheduler and notifies the breaks.
"""
"""
Initialize the internal variables of the core.
"""
def __init__(self, context, show_notification, start_break, end_break, on_countdown, update_next_break_info):
# Initialize the variables
self.break_count = -1
@ -48,14 +45,13 @@ class SafeEyesCore:
self.context['skipped'] = False
self.context['postponed'] = False
"""
Initialize the internal properties from configuration
"""
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']
self.short_break_exercises = [] # language['exercises']['short_break_exercises']
self.long_break_exercises = [] # language['exercises']['long_break_exercises']
self.no_of_short_breaks_per_long_break = config['no_of_short_breaks_per_long_break']
self.pre_break_warning_time = config['pre_break_warning_time']
@ -111,11 +107,10 @@ class SafeEyesCore:
self.long_break_exercises.append([name, break_time, audible_alert, image])
"""
Start Safe Eyes is it is not running already.
"""
def start(self):
"""
Start Safe Eyes is it is not running already.
"""
with self.lock:
if not self.active:
logging.info("Scheduling next break")
@ -125,18 +120,23 @@ class SafeEyesCore:
if self.context['idle_pause_enabled']:
Utility.start_thread(self.__start_idle_monitor)
"""
Stop Safe Eyes if it is running.
"""
def stop(self):
"""
Stop Safe Eyes if it is running.
"""
with self.lock:
if self.active:
logging.info("Stop the core")
# Reset the state properties in case of restart
# self.break_count = 0
# self.long_break_message_index = -1
# self.short_break_message_index = -1
# 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.notification_condition.acquire()
@ -150,11 +150,10 @@ class SafeEyesCore:
self.idle_condition.notify_all()
self.idle_condition.release()
"""
Pause Safe Eyes if it is running.
"""
def pause(self):
"""
Pause Safe Eyes if it is running.
"""
with self.lock:
if self.active and self.running:
self.notification_condition.acquire()
@ -162,38 +161,35 @@ class SafeEyesCore:
self.notification_condition.notify_all()
self.notification_condition.release()
"""
Resume Safe Eyes if it is not running.
"""
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)
"""
User skipped the break using Skip button
"""
def skip_break(self):
"""
User skipped the break using Skip button
"""
self.context['skipped'] = True
"""
User postponed the break using Postpone button
"""
def postpone_break(self):
"""
User postponed the break using Postpone button
"""
self.context['postponed'] = True
"""
Scheduler task to execute during every interval
"""
def __scheduler_job(self):
"""
Scheduler task to execute during every interval
"""
if not self.__is_running():
return
time_to_wait = self.break_interval # In minutes
time_to_wait = self.break_interval # In minutes
if self.context['postponed']:
# Reduce the break count by 1 to show the same break again
@ -213,11 +209,16 @@ class SafeEyesCore:
next_break_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
self.update_next_break_info(next_break_time)
self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break)
if self.__is_long_break():
self.context['break_type'] = 'long'
else:
self.context['break_type'] = 'short'
# Wait for the pre break warning period
logging.info("Pre-break waiting for {} minutes".format(time_to_wait))
self.notification_condition.acquire()
self.notification_condition.wait(time_to_wait * 60) # Convert to seconds
self.notification_condition.wait(time_to_wait * 60) # Convert to seconds
self.notification_condition.release()
logging.info("Pre-break waiting is over")
@ -227,16 +228,13 @@ class SafeEyesCore:
logging.info("Ready to show the break")
self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break)
self.is_before_break = False
Utility.execute_main_thread(self.__check_active_window)
"""
Show the notification and start the break after the notification.
"""
def __show_notification(self):
"""
Show the notification and start the break after the notification.
"""
# Show the notification
self.show_notification()
@ -249,13 +247,12 @@ class SafeEyesCore:
self.is_before_break = True
Utility.execute_main_thread(self.__check_active_window)
"""
Check the active window for full-screen and user defined exceptions.
"""
def __check_active_window(self):
"""
Check the active window 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 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():
@ -269,12 +266,10 @@ class SafeEyesCore:
else:
Utility.start_thread(self.__show_notification)
"""
Start the break screen.
"""
def __start_break(self):
"""
Start the break screen.
"""
# User can disable SafeEyes during notification
if self.__is_running():
message = ""
@ -288,7 +283,6 @@ class SafeEyesCore:
seconds = self.long_break_exercises[self.long_break_message_index][1]
audible_alert = self.long_break_exercises[self.long_break_message_index][2]
image = self.long_break_exercises[self.long_break_message_index][3]
self.context['break_type'] = 'long'
else:
logging.info("Count is {}; get a short beak message".format(self.break_count))
self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises)
@ -296,7 +290,6 @@ class SafeEyesCore:
seconds = self.short_break_exercises[self.short_break_message_index][1]
audible_alert = self.short_break_exercises[self.short_break_message_index][2]
image = self.short_break_exercises[self.short_break_message_index][3]
self.context['break_type'] = 'short'
self.context['break_length'] = seconds
self.context['audible_alert'] = audible_alert
@ -307,11 +300,10 @@ class SafeEyesCore:
# 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']:
self.context['count_down'] = total_break_time - seconds
mins, secs = divmod(seconds, 60)
timeformat = '{:02d}:{:02d}'.format(mins, secs)
self.on_countdown(timeformat)
time.sleep(1) # Sleep for 1 second
count_down = total_break_time - seconds
self.context['count_down'] = count_down
self.on_countdown(count_down, seconds)
time.sleep(1) # Sleep for 1 second
seconds -= 1
# Loop terminated because of timeout (not skipped) -> Close the break alert
@ -327,26 +319,22 @@ class SafeEyesCore:
# Schedule the break again
Utility.start_thread(self.__scheduler_job)
"""
Tells whether Safe Eyes is running or not.
"""
def __is_running(self):
"""
Tells whether Safe Eyes is running or not.
"""
return self.active and self.running
"""
Check if the current break is long break or short current
"""
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
"""
Continuously check the system idle time and pause/resume Safe Eyes based on it.
"""
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()

View File

@ -18,9 +18,10 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkX11, GObject
from gi.repository import Gtk, GObject
from safeeyes import Utility
class SettingsDialog:
"""
Create and initialize SettingsDialog instance.
@ -29,11 +30,14 @@ class SettingsDialog:
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')
@ -43,6 +47,7 @@ class SettingsDialog:
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')
@ -60,6 +65,7 @@ class SettingsDialog:
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'])
@ -75,15 +81,16 @@ class SettingsDialog:
self.spin_interval_between_two_breaks.set_value(config['break_interval'])
self.spin_short_between_long.set_value(config['no_of_short_breaks_per_long_break'])
self.spin_time_to_prepare.set_value(config['pre_break_warning_time'])
self.spin_idle_time_to_pause.set_value(config['idle_time'])
self.spin_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'])
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(Utility.command_exist('xprintidle'))
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'])
@ -99,6 +106,9 @@ class SettingsDialog:
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
@ -108,7 +118,7 @@ class SettingsDialog:
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
self.languages.append('system') # Dummy record for row separator
if 'system' == lang_code:
self.cmb_language.set_active(0)
@ -120,19 +130,17 @@ class SettingsDialog:
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) == '-')
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.
@ -140,13 +148,12 @@ class SettingsDialog:
"""
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()
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)
@ -158,13 +165,22 @@ class SettingsDialog:
"""
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.
@ -176,20 +192,35 @@ class SettingsDialog:
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['audible_alert'] = self.switch_audible_alert.get_active()
self.config['language'] = self.languages[self.cmb_language.get_active()]
self.config['time_to_screen_lock'] = self.spin_time_to_screen_lock.get_value_as_int()
self.config['enable_screen_lock'] = self.switch_screen_lock.get_active()
self.config['allow_postpone'] = self.switch_postpone.get_active()
# 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
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

@ -28,6 +28,9 @@ 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")
@ -133,9 +136,15 @@ class TrayIcon:
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]))
@ -158,12 +167,22 @@ class TrayIcon:
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
@ -173,17 +192,31 @@ class TrayIcon:
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
@ -195,6 +228,10 @@ class TrayIcon:
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:
@ -211,6 +248,10 @@ class TrayIcon:
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')
@ -231,30 +272,26 @@ class TrayIcon:
Utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait)
self.item_info.set_label(self.language['messages']['disabled_until_x'].format(Utility.format_time(self.wakeup_time)))
"""
This method is called by the core to prevent user from disabling Safe Eyes after the notification.
"""
def lock_menu(self):
"""
This method is called by the core to prevent user from disabling Safe Eyes after the notification.
"""
if self.active:
# self.item_disable.set_sensitive(False)
# self.item_settings.set_sensitive(False)
# self.item_quit.set_sensitive(False)
self.menu.set_sensitive(False)
"""
This method is called by the core to activate the disable menu after the the break.
"""
def unlock_menu(self):
"""
This method is called by the core to activate the menu after the the break.
"""
if self.active:
# self.item_disable.set_sensitive(True)
# self.item_settings.set_sensitive(True)
# self.item_quit.set_sensitive(True)
self.menu.set_sensitive(True)
def __schedule_resume(self, time_minutes):
"""
Schedule a local timer to enable Safe Eyes after the given timeout.
"""
self.idle_condition.acquire()
self.idle_condition.wait(time_minutes * 60) # Convert to seconds
self.idle_condition.wait(time_minutes * 60) # Convert to seconds
self.idle_condition.release()
with self.lock:

View File

@ -18,11 +18,11 @@
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, GLib
from gi.repository import Gtk, Gdk, GLib, GdkX11
from html.parser import HTMLParser
from distutils.version import LooseVersion
from logging.handlers import RotatingFileHandler
import babel.dates, os, errno, re, subprocess, threading, logging, locale, json, shutil, pyaudio, wave
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('~')
@ -33,43 +33,59 @@ 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
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
"""
logging.info('Playing audible alert')
CHUNK = 1024
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')
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)
# 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)
# 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()
# 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)
except Exception as e:
logging.warning('Unable to play audible alert')
logging.exception(e)
def get_resource_path(resource_name):
@ -95,7 +111,7 @@ def system_idle_time():
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
return int(subprocess.check_output(['xprintidle']).decode('utf-8')) / 60000 # Convert to minutes
except:
return 0
@ -129,7 +145,7 @@ def is_active_window_skipped(skip_break_window_classes, take_break_window_classe
active_window = screen.get_active_window()
if active_window:
active_xid = str(active_window.get_xid())
cmdlist = ['xprop', '-root', '-notype','-id',active_xid, 'WM_CLASS', '_NET_WM_STATE']
cmdlist = ['xprop', '-root', '-notype', '-id', active_xid, 'WM_CLASS', '_NET_WM_STATE']
try:
stdout = subprocess.check_output(cmdlist).decode('utf-8')
@ -191,6 +207,7 @@ def mkdir(path):
logging.error('Error while creating ' + str(path))
raise
def parse_language_code(lang_code):
"""
Convert the user defined language code to a valid one.
@ -249,6 +266,30 @@ def read_lang_files():
return languages
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'
def lock_screen_command():
"""
Function tries to detect the screensaver command based on the current envinroment
@ -275,14 +316,14 @@ def lock_screen_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'):
elif desktop_session in ['gnome', 'unity', 'budgie-desktop'] or desktop_session.startswith('ubuntu'):
if command_exist('gnome-screensaver-command'):
return ['gnome-screensaver-command', '--lock']
else:
# From Gnome 3.8 no gnome-screensaver-command
return ['dbus-send', '--type=method_call', '--dest=org.gnome.ScreenSaver', '/org/gnome/ScreenSaver', 'org.gnome.ScreenSaver.Lock']
elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
if not 'deprecated' in os.environ.get('GNOME_DESKTOP_SESSION_ID') and command_exist('gnome-screensaver-command'):
if 'deprecated' not in os.environ.get('GNOME_DESKTOP_SESSION_ID') and command_exist('gnome-screensaver-command'):
# Gnome 2
return ['gnome-screensaver-command', '--lock']
return None
@ -378,7 +419,7 @@ def intialize_logging():
log_formatter = logging.Formatter('%(asctime)s [%(levelname)s]:[%(threadName)s] %(message)s')
# Apped the logs and overwrite once reached 5MB
handler = RotatingFileHandler(log_file_path, mode='a', maxBytes=5*1024*1024, backupCount=2, encoding=None, delay=0)
handler = RotatingFileHandler(log_file_path, mode='a', maxBytes=5 * 1024 * 1024, backupCount=2, encoding=None, delay=0)
handler.setFormatter(log_formatter)
handler.setLevel(logging.INFO)
@ -445,7 +486,7 @@ class __HTMLTextExtractor(HTMLParser):
def __init__(self):
self.reset()
self.strict = False
self.convert_charrefs= True
self.convert_charrefs = True
self.fed = []
def handle_data(self, d):

View File

@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, gi, json, dbus, logging, operator, psutil, sys
import os, gi, json, dbus, logging, psutil, sys
from threading import Timer
from dbus.mainloop.glib import DBusGMainLoop
gi.require_version('Gtk', '3.0')
@ -37,14 +37,14 @@ break_screen_glade = os.path.join(Utility.bin_directory, "glade/break_screen.gla
settings_dialog_glade = os.path.join(Utility.bin_directory, "glade/settings_dialog.glade")
about_dialog_glade = os.path.join(Utility.bin_directory, "glade/about_dialog.glade")
is_active = True
SAFE_EYES_VERSION = "1.2.1"
SAFE_EYES_VERSION = "1.2.2"
"""
Listen to tray icon Settings action and send the signal to Settings dialog.
"""
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:
@ -52,27 +52,30 @@ def show_settings():
settings_dialog = SettingsDialog(config, language, Utility.read_lang_files(), able_to_lock_screen, save_settings, settings_dialog_glade)
settings_dialog.show()
"""
Listen to tray icon About action and send the signal to About dialog.
"""
def show_about():
"""
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()
"""
Receive the signal from core and pass it to the Notification.
"""
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'])
"""
Receive the break signal from core and pass it to the break screen.
"""
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)
@ -80,10 +83,11 @@ def show_alert(message, image_name):
if config['strict_break'] and is_active:
Utility.execute_main_thread(tray_icon.unlock_menu)
"""
Receive the stop break signal from core and pass it to the break screen.
"""
def close_alert(audible_alert_on):
"""
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
@ -94,21 +98,22 @@ def close_alert(audible_alert_on):
plugins.post_break(context)
"""
Listen to the tray menu quit action and stop the core, notification and the app itself.
"""
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();
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.
"""
def handle_suspend_callback(sleeping):
"""
if sleeping:
# Sleeping / suspending
if is_active:
@ -120,18 +125,20 @@ def handle_suspend_callback(sleeping):
core.start()
logging.info("Resumed Safe Eyes after system wakeup")
"""
Setup system suspend listener.
"""
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')
"""
Listen to break screen Skip action and send the signal to core.
"""
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
@ -139,20 +146,22 @@ def on_skipped():
core.skip_break()
plugins.post_break(context)
"""
Listen to break screen Postpone action and send the signal to core.
"""
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()
"""
Listen to Settings dialog Save action and write to the config file.
"""
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")
@ -175,22 +184,25 @@ def save_settings(config):
# 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()
"""
Listen to tray icon enable action and send the signal to core.
"""
def enable_safeeyes():
"""
Listen to tray icon enable action and send the signal to core.
"""
global is_active
is_active = True
core.start()
"""
Listen to tray icon disable action and send the signal to core.
"""
def disable_safeeyes():
"""
Listen to tray icon disable action and send the signal to core.
"""
global is_active
is_active = False
core.stop()
@ -198,11 +210,12 @@ def disable_safeeyes():
def running():
"""
Check if SafeEyes is already running.
Check if SafeEyes is already running.
"""
process_count = 0
for proc in psutil.process_iter():
if not proc.cmdline: continue
if not proc.cmdline:
continue
try:
# Check if safeeyes is in process arguments
if callable(proc.cmdline):
@ -231,6 +244,9 @@ def main():
logging.info("Starting Safe Eyes")
# Import the dependencies
Utility.import_dependencies()
if not running():
global break_screen
@ -253,14 +269,14 @@ def main():
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(on_skipped, on_postponed, break_screen_glade, Utility.style_sheet_path)
break_screen = BreakScreen(context, on_skipped, on_postponed, break_screen_glade, Utility.style_sheet_path)
break_screen.initialize(config, language)
notification = Notification(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)
@ -274,5 +290,6 @@ def main():
logging.info('Another instance of safeeyes is already running')
sys.exit(0)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,62 @@
{
"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

@ -4,7 +4,7 @@
"language_name_en": "Czech"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
"description": "Safe Eyes vám připomíná dělat si přestávky, když trávíte dlouhé hodiny u počítače a tak chrání vaše oči před únavou (asthenopií)."
},
"exercises": {
"short_break_close_eyes": "Zavřete oči",
@ -18,18 +18,22 @@
"long_break_lean_back": "Opřete se do židle a relaxujte"
},
"messages": {
"ready_for_a_break": "Připravte se na přestávku za {} sekund",
"audible_alert_disabled": "Nelze zapnout zvuková upozornění",
"ready_for_a_short_break": "Připravte se na přestávku za {} sekund",
"ready_for_a_long_break": "Připravte se na přestávku za {} sekund",
"disabled_until_restart": "Pozastaveno do restartu",
"disabled_until_x": "Pozastaveno na {}",
"next_break_at": "Příští přestávka v {}"
"next_break_at": "Příští přestávka v {}",
"software_required": "Nejdříve musíte nainstalovat {}"
},
"ui_controls": {
"about": "O aplikaci",
"allow_postpone": "Allow postponing the breaks",
"allow_postpone": "Povolit odložení přestávky",
"audible_alert": "Zvukové upozornění na konec přestávky",
"cancel": "Zrušit",
"close": "Close",
"disable": "Pozastavit Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Zapnout Safe Eyes",
"enable_screen_lock": "Po každé dlouhé přestávce uzamknout obrazovku",
"for_x_hour": "Na {} hodinu",
@ -38,7 +42,7 @@
"idle_time": "Pozastavit při nečinnosti delší než (minut)",
"interval_between_two_breaks": "Interval mezi dvěma přestávkami",
"language": "Jazyk",
"license": "License",
"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",
@ -47,12 +51,12 @@
"save": "Uložit",
"settings": "Nastavení",
"short_break_duration": "Trvání krátké přestávky (v sekundách)",
"show_time_in_tray": "Show the next break time in system tray",
"show_time_in_tray": "Zobrazit čas příští přestávky v systémové oblasti",
"skip": "Přeskočit",
"strict_break": "Povinná přestávka (skrýt tlačítko pro přeskočení)",
"system_language": "Systémový jazyk",
"system_language": "Jazyk systému",
"time_to_prepare_for_break": "Čas k přípravě na přestávku (v sekundách)",
"time_to_screen_lock": "O kolik nejvýše přeskočit, obcházející zámek obrazovky (v sekundách)",
"time_to_screen_lock": "O kolik sekund nejvýše přeskočit (obejde zámek obrazovky)",
"until_restart": "Do restartu"
}
}

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Lehnen Sie sich zurück und entspannen Sie sich"
},
"messages": {
"ready_for_a_break": "Nächste Pause in {} Sekunden",
"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 {}"
"next_break_at": "Nächste Pause um {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Über",
@ -30,6 +33,7 @@
"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",
@ -47,7 +51,7 @@
"save": "Speichern",
"settings": "Einstellungen",
"short_break_duration": "Kleine-Pause-Intervall (in Sekunden)",
"show_time_in_tray": "Show the next break time in system tray",
"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",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Lean back at your seat and relax"
},
"messages": {
"ready_for_a_break": "Ready for a break in {} seconds",
"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 {}"
"next_break_at": "Next break at {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "About",
@ -30,6 +33,7 @@
"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",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Reclínate sobre tu silla y relájate"
},
"messages": {
"ready_for_a_break": "Listo para una pausa en {} segundos",
"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 {}"
"next_break_at": "Próxima pausa a las {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Acerca de",
@ -30,6 +33,7 @@
"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",

View File

@ -0,0 +1,62 @@
{
"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

@ -0,0 +1,62 @@
{
"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

@ -18,10 +18,13 @@
"long_break_lean_back": "Adossez-vous à votre siège et relaxez-vous"
},
"messages": {
"ready_for_a_break": "Préparez-vous à une pause dans {} secondes",
"audible_alert_disabled": "Cannot enable audible notifications",
"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 à {}"
"next_break_at": "Prochaine pause à {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "À propos",
@ -30,6 +33,7 @@
"cancel": "Annuler",
"close": "Fermer",
"disable": "Désactiver Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Activer Safe Eyes",
"enable_screen_lock": "Verrouiller l'écran après chaque pause longue",
"for_x_hour": "Pendant {} heure",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "სავარძლის საზურგეზე გადაწექით და ცოტა დაისვენეთ"
},
"messages": {
"ready_for_a_break": "მოემზადეთ შესვენებისთვის {} წამში",
"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": "შემდეგი შესვენება {}"
"next_break_at": "შემდეგი შესვენება {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "პროგრამის შესახებ",
@ -30,6 +33,7 @@
"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": "{} საათით",
@ -47,7 +51,7 @@
"save": "დამახსოვრება",
"settings": "პარამეტრები",
"short_break_duration": "მცირე შესვენების ხანგრძლივობა (წამებში)",
"show_time_in_tray": "Show the next break time in system tray",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "გამოტოვება",
"strict_break": "აუცილებელი შესვენება (დავმალოთ ღილაკი 'გამოტოვება')",
"system_language": "System Language",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "पीछे हट कर आराम करें"
},
"messages": {
"ready_for_a_break": "{} पलों में आराम",
"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": "अगला आराम {} में"
"next_break_at": "अगला आराम {} में",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "हमारे बारे में",
@ -30,6 +33,7 @@
"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": "{} घंटे के लिए",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Dőljön hátra és pihenjen!"
},
"messages": {
"ready_for_a_break": "Tervezett szünet {} másodperc múlva!",
"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 {}"
"next_break_at": "A következő szünet {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Ról ről",
@ -30,6 +33,7 @@
"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",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Silakan bersandar ke kursi dan bersantai"
},
"messages": {
"ready_for_a_break": "Bersiap beristirahat dalam {} detik",
"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 {}"
"next_break_at": "Istirahat selanjutnya pada {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Tentang",
@ -30,6 +33,7 @@
"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",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Наслонете се на столот и одморете"
},
"messages": {
"ready_for_a_break": "Пауза за {} секунди",
"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": "Следна пауза во {}"
"next_break_at": "Следна пауза во {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "За",
@ -30,6 +33,7 @@
"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": "За {} час",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Oprzyj się wygodnie na krześle i zrelaksuj"
},
"messages": {
"ready_for_a_break": "Przygotuj się! Przerwa za {} sekund(y).",
"audible_alert_disabled": "Cannot enable audible notifications",
"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 {}"
"next_break_at": "Następna przerwa o {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "O programie",
@ -30,6 +33,7 @@
"cancel": "Anuluj",
"close": "Zamknij",
"disable": "Zatrzymaj Safe Eyes",
"disable_keyboard_shortcut": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Uruchom Safe Eyes",
"enable_screen_lock": "Zablokuj ekran po każdej długiej przerwie",
"for_x_hour": "Na {} godzinę",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Encoste na cadeira e relaxe"
},
"messages": {
"ready_for_a_break": "Pronto para uma pausa em {} segundos",
"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 {}"
"next_break_at": "Próxima pausa em {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "Sobre",
@ -30,6 +33,7 @@
"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",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Откиньтесь на спинку стула и расслабьтесь"
},
"messages": {
"ready_for_a_break": "Приготовьтесь к перерыву через {} секунд",
"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": "Следующий перерыв в {}"
"next_break_at": "Следующий перерыв в {}",
"software_required": "To enable this feature, please install {}"
},
"ui_controls": {
"about": "О программе",
@ -30,6 +33,7 @@
"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": "На {} час",
@ -47,7 +51,7 @@
"save": "Сохранить",
"settings": "Настройки",
"short_break_duration": "Продолжительность короткого перерыва (в секундах)",
"show_time_in_tray": "Show the next break time in system tray",
"show_time_in_tray": "Show the next break time in system tray",
"skip": "Пропустить",
"strict_break": "Обязательный перерыв (Скрыть кнопку 'Пропустить')",
"system_language": "System Language",

View File

@ -4,7 +4,7 @@
"language_name_en": "Slovak"
},
"app_info": {
"description": "Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you to take breaks while you're working long hours at the computer."
"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",
@ -18,41 +18,45 @@
"long_break_lean_back": "Opri sa o kreslo a relaxuj"
},
"messages": {
"ready_for_a_break": "Priprav sa na prestávku o {} sekúnd",
"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 {}"
"next_break_at": "Ďalšia prestávka o {}",
"software_required": "Pro ích povolenie nainstalujte {}"
},
"ui_controls": {
"about": "Ohľadom",
"allow_postpone": "Allow postponing the breaks",
"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": "Shortcut disabled period to prevent unintentional skip (in seconds)",
"enable": "Povoliť Safe Eyes",
"enable_screen_lock": "Lock the screen after every long break",
"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": "License",
"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": "Postpone",
"postpone_duration": "Postpone duration (in minutes)",
"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": "Show the next break time in system tray",
"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": "System Language",
"system_language": "Jazyk systému",
"time_to_prepare_for_break": "Čas na prípravu na prestávku (v sekundách)",
"time_to_screen_lock": "Maximum time to skip, bypassing the lock screen (in seconds)",
"time_to_screen_lock": "O koľko sekúnd najviac preskočiť (obchádza zámok obrazovky)",
"until_restart": "Do reštartu"
}
}

View File

@ -18,18 +18,22 @@
"long_break_lean_back": "கதிைரயில் பின்பக்கமாக சாய்ந்து ஓய்வெடுங்கள்"
},
"messages": {
"ready_for_a_break": "{} விநாடிகளில் இடைவேளைக்கு தயாராகுங்கள்",
"audible_alert_disabled": "ஒலிச் சமிக்ஞையை செயற்படுத்த முடியாதுள்ளது",
"ready_for_a_short_break": "{} விநாடிகளில் குறுகிய இடைவேளைக்கு தயாராகுங்கள்",
"ready_for_a_long_break": "{} விநாடிகளில் நீண்ட இடைவேளைக்கு தயாராகுங்கள்",
"disabled_until_restart": "மறுதுவக்கம் வரை நிறுத்தி வைக்கப்பட்டுள்ளது",
"disabled_until_x": "{} வரை நிறுத்தி வைக்கப்பட்டுள்ளது",
"next_break_at": "அடுத்த இடைவேளை {}"
"next_break_at": "அடுத்த இடைவேளை {}",
"software_required": "இந்த வசதியை செயற்படுத்த {} ஐ நிறுவவும்"
},
"ui_controls": {
"about": "Safe Eyes குறித்த தகவல்கள்",
"allow_postpone": "ஒத்திவைக்க அனுமதிக்கவும்",
"audible_alert": "இடைவேளையின் இறுதியில் ஒலி சமிக்ை",
"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": "{} மணித்தியாலத்திற்கு",

View File

@ -18,10 +18,13 @@
"long_break_lean_back": "Arkanıza yaslanın ve biraz gevşeyin"
},
"messages": {
"ready_for_a_break": "{} Saniye içerisinde bir molaya hazır olun",
"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ı: {}"
"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",
@ -30,6 +33,7 @@
"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",

View File

@ -0,0 +1,62 @@
{
"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

@ -22,10 +22,13 @@
},
"messages":
{
"ready_for_a_break": "Sẵn sàng để nghỉ ngơi {} giây",
"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à {}"
"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":
{
@ -35,12 +38,13 @@
"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": "Minimum idle time to pause (tính bằng 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",

View File

@ -10,7 +10,11 @@
"pre_break_warning_time": 10,
"short_break_duration": 15,
"idle_time": 5,
"persist_state": true,
"postpone_duration": 5,
"shortcut_disable_time": 2,
"shortcut_skip": 9,
"shortcut_postpone": 65,
"show_time_in_tray": false,
"strict_break": false,
"audible_alert": false,

View File

@ -52,6 +52,12 @@
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_disable_keyboard_shortcut_duration">
<property name="lower">0</property>
<property name="upper">15</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_short_break_duration">
<property name="lower">1</property>
<property name="upper">60</property>
@ -189,7 +195,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
@ -202,7 +208,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
@ -215,7 +221,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
@ -346,7 +352,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">9</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
@ -358,7 +364,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
@ -370,7 +376,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
@ -382,7 +388,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">10</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
@ -395,7 +401,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">10</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
@ -405,7 +411,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">13</property>
<property name="top_attach">14</property>
</packing>
</child>
<child>
@ -418,7 +424,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">13</property>
<property name="top_attach">14</property>
</packing>
</child>
<child>
@ -431,7 +437,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">11</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
@ -442,7 +448,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">11</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
@ -455,7 +461,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">12</property>
<property name="top_attach">13</property>
</packing>
</child>
<child>
@ -474,7 +480,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">12</property>
<property name="top_attach">13</property>
</packing>
</child>
<child>
@ -487,7 +493,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
@ -498,14 +504,40 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">8</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkLabel" id="lbl_disable_keyboard_shortcut">
<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">Disable keyboard shortcut</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkSpinButton" id="spin_disable_keyboard_shortcut">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="text" translatable="yes">1</property>
<property name="input_purpose">number</property>
<property name="adjustment">adjust_disable_keyboard_shortcut_duration</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
<property name="update_policy">if-valid</property>
<property name="value">1</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
</packing>
</child>
</object>
<packing>

View File

@ -1,13 +1,15 @@
import os
import setuptools
requires = [
'python-xlib',
'pyaudio',
'psutil',
'babel']
'python-xlib',
'psutil',
'babel']
extras = {
'audible_alert': ['pyaudio']
}
here = os.path.abspath(os.path.dirname(__file__))
@ -15,21 +17,23 @@ here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.md')) as f:
long_description = '\n' + f.read()
def _data_files(path):
for root, dirs, files in os.walk(path):
if not files:
continue
yield (os.path.join('/usr', root), [os.path.join(root, f) for f in files])
setuptools.setup(
name="safeeyes",
version="1.2.1",
version="1.2.2",
description="Protect your eyes from eye strain using this continuous breaks reminder.",
long_description=long_description,
author="Gobinath Loganathan",
author_email="slgobinath@gmail.com",
url="https://github.com/slgobinath/SafeEyes",
download_url="https://github.com/slgobinath/SafeEyes/archive/v1.2.1.tar.gz",
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',
@ -49,6 +53,7 @@ setuptools.setup(
('/usr/share/icons/hicolor/16x16/status', ['share/icons/hicolor/16x16/status/safeeyes_enabled.png', 'share/icons/hicolor/16x16/status/safeeyes_disabled.png', 'share/icons/hicolor/16x16/status/safeeyes_timer.png'])
],
install_requires=requires,
extras_require=extras,
entry_points={'console_scripts': ['safeeyes = safeeyes.__main__:main']},
keywords='linux utility health eye-strain safe-eyes',
classifiers=[

View File

@ -1,19 +1,25 @@
[Desktop Entry]
Name=Safe Eyes
Comment=Protect your eyes from eye strain
Comment[ge]=დაიცავით თქვენი თვალები დაღლილობისაგან
Comment[de]=Schützt die Augen vor Überanstrengung
Comment[ca]=Protegiu-vos els ulls de la fatiga visual
Comment[cs]=Chraňte své oči před únavou
Comment[fr]=Protégez vos yeux de la fatigue
Comment[id]=Melindungi mata Anda dari kelelahan
Comment[ta]=உங்கள் கண்களை சோர்வடையாது பாதுகாத்திடுங்கள்
Comment[pt]=Proteja seus olhos da tensão ocular
Comment[tr]=Gözünüzü yorgunluğa karşı koruyun
Comment[hi]=तनाव से आंखों की रक्षा
Comment[de]=Schützt die Augen vor Überanstrengung
Comment[es]=Protege tus ojos de la fatiga ocular
Comment[ru]=Защитите свои глаза от зрительного перенапряжения
Comment[pl]=Chroń oczy przed zmęczeniem
Comment[et]=Kaitse oma silmi väsimuse eest
Comment[fa]=محافظت چشم هااز ضعیف شدن
Comment[fr]=Protégez vos yeux de la fatigue
Comment[ge]=დაიცავით თქვენი თვალები დაღლილობისაგან
Comment[hi]=तनाव से आंखों की रक्षा
Comment[hu]=Protect your eyes from eye strain
Comment[id]=Melindungi mata Anda dari kelelahan
Comment[mk]=Заштитете се од замор на очите
Comment[pl]=Chroń oczy przed zmęczeniem
Comment[pt]=Proteja seus olhos da tensão ocular
Comment[ru]=Защитите свои глаза от зрительного перенапряжения
Comment[sk]=Chráňte svoje oči pred únavou
Comment[ta]=உங்கள் கண்களை சோர்வடையாது பாதுகாத்திடுங்கள்
Comment[tr]=Gözünüzü yorgunluğa karşı koruyun
Comment[uk]=Захистіть свої очі від втоми
Comment[vi]=Bảo vệ đôi mắt của bạn khỏi sự mệt mỏi
Exec=env GDK_BACKEND=x11 safeeyes
Icon=safeeyes