diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbe75dd --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Safe Eyes +Protect your eyes from eye strain using this continous breaks reminder. A Free and Open Source Linux alternative for EyeLeo. + +## Installation +1: Add the PPA: `sudo add-apt-repository ppa:slgobinath/safeeyes` + +2: Download the package list: `sudo apt update` + +3: Install uget-chrome-wrapper: `sudo apt install safeeyes` + +4: Start Safe Eyes from start menu. + + +## Usage +Just install and forget; Safe Eyes will take care of your eyes. To customize the preferences, go to Settings from Safe Eyes tray icon. +For advanced configuration, go to `~/.config/safeeyes folder`. There you can change the Skip button text in `safeeyes.json` and the look and feel of the break screen in `style/safeeyes_style.css`. + +## Features +- Short breaks with eye exercises +- Long breaks to change physical position and to warm up +- Strict break for those who are addicted to computer +- Highly customizable +- Do not disturb when working with fullscreen applications( Eg: Watching movies) +- Notifications before every break +- Multi-workspace support +- Elegant and customizable design + +## Contributing +**Are you a user?** + +Please test Safe Eyes on your system and report any issues [here](https://github.com/slgobinath/SafeEyes/issues) + +**Are you a developer?** + +1. Fork it! +2. Create your feature branch: `git checkout -b my-new-feature` +3. Commit your changes: `git commit -am 'Add some feature'` +4. Push to the branch: `git push origin my-new-feature` +5. Submit a pull request + +**Are you using a different Linux system?** + +Please test Safe Eyes and create installers for your operating system + + +## History + +Version 1.0.0: +* Intial release + + +## License + +GNU General Public License v3 \ No newline at end of file diff --git a/icon-64.png b/icon-64.png deleted file mode 100644 index 289f7c2..0000000 Binary files a/icon-64.png and /dev/null differ diff --git a/icon.png b/icon.png deleted file mode 100644 index 0fceb6d..0000000 Binary files a/icon.png and /dev/null differ diff --git a/run b/run deleted file mode 100755 index 7d4f6a7..0000000 --- a/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python safeeyes diff --git a/safeeyes b/safeeyes deleted file mode 100755 index 3656674..0000000 --- a/safeeyes +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/python3 -''' -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see http://www.gnu.org/licenses. -''' - -import os -from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.schedulers.base import BaseScheduler -from gi.repository import Gtk as gtk -from gi.repository import AppIndicator3 as appindicator -from gi.repository import Notify as notify -from gi.repository import Gtk, Gdk, GLib, Pango -import threading -import cairo -import time -import ConfigParser - -config = ConfigParser.RawConfigParser() -config_file = "/home/gobinath/applications/safeeye/safeeyes.cfg" -active = True -initialized = False -scheduler = BackgroundScheduler() -APPINDICATOR_ID = 'safeeyes' -PATH = "/home/gobinath/applications/safeeye/" -job_break = None -screen = None -short_break_messages = ["Tightly close your eyes", "Roll your eyes", "Blink your eyes", "Have some water"] -long_break_messages = ["Walk for a while", "Lean back at your seat and relax"] - -class FullScreenBreak(Gtk.Window): - def __init__(self): - super(FullScreenBreak, self).__init__() - self.short_break_message_index = 0 - self.long_break_message_index = 0 - self.break_count = 0; - self.tran_setup() - self.init_ui() - - def close(self, button, name): - if(self.is_break): - self.is_break = False - if self.is_long_break(): - self.long_break_message_index = (self.long_break_message_index + 1) % len(long_break_messages) - else: - self.short_break_message_index = (self.short_break_message_index + 1) % len(short_break_messages) - - self.break_count = ((self.break_count + 1) % config.getint('SafeEyes', 'no_of_short_breaks_per_long_break')) - resume_eyesafe() - self.hide() - - - def init_ui(self): - - self.connect("draw", self.on_draw) - - self.set_title("Safe Eyes") - self.resize(300, 250) - self.set_keep_above(True) - self.set_position(Gtk.WindowPosition.CENTER) - - vbox = Gtk.VBox(False, 0) - vbox_center = Gtk.VBox(False, 10) - hbox = Gtk.HBox(False) - - valign = Gtk.Alignment() - valign.set(0.5, 0.5, 0, 0) - hbox.pack_start(valign, False, True, 0) - - halign = Gtk.Alignment() - halign.set(0.5, 0.5, 0, 0) - # vbox.pack_start(halign, False, True, 0) - - self.lbl_message = Gtk.Label("Take rest") - self.lbl_message.modify_font(Pango.FontDescription(config.get('SafeEyes', 'message_font'))) - self.lbl_time = Gtk.Label("MM:SS") - self.btn_skip = Gtk.Button(config.get('SafeEyes', 'skip_button_text')) - self.btn_skip.connect("clicked", self.close, None) - self.btn_skip.set_size_request(20, 20) - - halign.add(self.btn_skip) - vbox_center.add(self.lbl_message) - vbox_center.add(self.lbl_time) - hbox.add(halign) - vbox_center.add(hbox) - - - halign = Gtk.Alignment() - halign.set(0.5, 0.5, 0, 0) - halign.add(vbox_center) - - vbox.pack_start(halign, True, False, 0) - - self.add(vbox) - - - def tran_setup(self): - - self.set_app_paintable(True) - screen = self.get_screen() - - visual = screen.get_rgba_visual() - if visual is not None and screen.is_composited(): - self.set_visual(visual) - - def on_draw(self, wid, cr): - cr.set_source_rgba(.2, .2, .2, 0.9) - cr.set_operator(cairo.OPERATOR_SOURCE) - cr.paint() - cr.set_operator(cairo.OPERATOR_OVER) - - def is_long_break(self): - return self.break_count == config.getint('SafeEyes', 'no_of_short_breaks_per_long_break') - 1 - - - def countdown(self): - seconds = config.getint('SafeEyes', 'short_break_duration') - if self.is_long_break(): - seconds = config.getint('SafeEyes', 'long_break_duration') - - while seconds and self.is_break: - mins, secs = divmod(seconds, 60) - timeformat = '{:02d}:{:02d}'.format(mins, secs) - GLib.idle_add(lambda: self.show_time(timeformat)) - time.sleep(1) - seconds -= 1 - self.close(None, None) - - - def show_time(self, t): - self.lbl_time.set_markup("" + t + ""); - - - def break_message(self): - message = '' - if self.is_long_break(): - message = long_break_messages[self.long_break_message_index] - else: - message = short_break_messages[self.short_break_message_index] - - return "" + message + "" - - def show_screen(self): - self.is_break = True - self.lbl_message.set_markup(self.break_message()); - self.show_all() - self.fullscreen() - self.stick() # Show on all workplaces - - - if config.getboolean('SafeEyes', 'strict_break'): - self.btn_skip.hide() - else: - self.btn_skip.show() - - - # Start countdown timer - thread = threading.Thread(target=self.countdown) - thread.start() - - -def resume_eyesafe(): - global job_break - if job_break: - print("Resume") - scheduler.resume_job("take_rest") - - -def pause_eyesafe(): - global job_break - if job_break: - print("Pause") - scheduler.pause_job("take_rest") - - -# @scheduler.scheduled_job('interval', minutes=10) -def take_rest(): - global active - # Pause the job - pause_eyesafe() - - # Show a notification - notification = notify.Notification.new("Safe Eyes", "Ready for a break after 15 seconds.", icon=os.path.abspath(PATH + 'icon.png')) - notification.set_timeout(1000) - notification.show() - notification.close() - - time.sleep(config.getint('SafeEyes', 'pre_break_warning_time')) - - if active: - # Show the screen - GLib.idle_add(lambda: screen.show_screen()) - - - -def main(): - indicator = appindicator.Indicator.new(APPINDICATOR_ID, os.path.abspath(PATH + 'icon-64.png'), appindicator.IndicatorCategory.SYSTEM_SERVICES) - indicator.set_status(appindicator.IndicatorStatus.ACTIVE) - indicator.set_menu(build_menu()) - notify.init(APPINDICATOR_ID) - gtk.main() - - -def build_menu(): - menu = gtk.Menu() - item_enable = gtk.CheckMenuItem('Enable SafeEyes') - item_enable.connect('activate', enable) - item_enable.set_active(True) - item_quit = gtk.MenuItem('Quit') - item_quit.connect('activate', quit) - menu.append(item_enable) - menu.append(item_quit) - menu.show_all() - return menu - - -def quit(_): - notify.uninit() - gtk.main_quit() - scheduler.shutdown(False) - -def enable(menu): - global active - active = menu.get_active() - if active: - resume_eyesafe() - else: - pause_eyesafe() - -def start_eyesafe(): - screen.post_close = resume_eyesafe - scheduler.start() - global job_break - job_break = scheduler.add_job(take_rest, 'interval', minutes=config.getint('SafeEyes', 'break_interval'), id='take_rest') - - -if __name__ == "__main__": - # Initialize the properties - config.read(config_file) - screen = FullScreenBreak() - start_eyesafe() - main() diff --git a/safeeyes.cfg b/safeeyes.cfg deleted file mode 100644 index 437197b..0000000 --- a/safeeyes.cfg +++ /dev/null @@ -1,10 +0,0 @@ -[SafeEyes] -pre_break_warning_time = 15 -no_of_short_breaks_per_long_break = 5 -break_interval = 15 -short_break_duration = 15 -long_break_duration = 60 -strict_break = False -skip_button_text = Skip -message_font = Adele - diff --git a/safeeyes/debian/changelog b/safeeyes/debian/changelog new file mode 100644 index 0000000..b1e5bd1 --- /dev/null +++ b/safeeyes/debian/changelog @@ -0,0 +1,5 @@ +safeeyes (1.0.0-1) xenial; urgency=medium + + * Initial release + + -- Loganathan Gobinath Sat, 15 Oct 2016 06:28:40 +0530 diff --git a/safeeyes/debian/compat b/safeeyes/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/safeeyes/debian/compat @@ -0,0 +1 @@ +9 diff --git a/safeeyes/debian/control b/safeeyes/debian/control new file mode 100644 index 0000000..7f093c4 --- /dev/null +++ b/safeeyes/debian/control @@ -0,0 +1,23 @@ +Source: safeeyes +Section: utils +Priority: optional +Maintainer: Loganathan Gobinath +Build-Depends: debhelper (>= 9) +Standards-Version: 3.9.6 +Homepage: https://github.com/slgobinath/SafeEyes/ + +Package: safeeyes +Architecture: any +Depends: python-appindicator, python (>= 2.7.6), python-apscheduler (>= 2.1.2) +Description: Safe Eyes + Safe Eyes is a simple tool to remind you to take periodic breaks for your eyes. This is essential for anyone spending more time on the computer to avoid eye strain and other physical problems. + . + Features: + - Short breaks with eye exercises + - Long breaks to change physical position and to warm up + - Strict break for those who are addicted to computer + - Highly customizable + - Do not disturb when working with fullscreen applications( Eg: Watching movies) + - Notifications before every break + - Multi-workspace support + - Elegant and customizable design diff --git a/safeeyes/debian/copyright b/safeeyes/debian/copyright new file mode 100644 index 0000000..44ac486 --- /dev/null +++ b/safeeyes/debian/copyright @@ -0,0 +1,24 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: uget-chrome-wrapper +Source: https://github.com/slgobinath/SafeEyes/ + +Files: * +Copyright: 2016 Loganathan Gobinath +License: GPL-3.0+ + +License: GPL-3.0+ + 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 package 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 . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". \ No newline at end of file diff --git a/safeeyes/debian/install b/safeeyes/debian/install new file mode 100644 index 0000000..ae8cee4 --- /dev/null +++ b/safeeyes/debian/install @@ -0,0 +1,3 @@ +safeeyes /opt/safeeyes +share/applications/safeeyes.desktop /opt/safeeyes +share /usr \ No newline at end of file diff --git a/safeeyes/debian/rules b/safeeyes/debian/rules new file mode 100755 index 0000000..fac20c9 --- /dev/null +++ b/safeeyes/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/safeeyes/safeeyes/BreakScreen.py b/safeeyes/safeeyes/BreakScreen.py new file mode 100644 index 0000000..787fcef --- /dev/null +++ b/safeeyes/safeeyes/BreakScreen.py @@ -0,0 +1,87 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +import signal + +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk, GLib + +class BreakScreen: + """Full screen break window""" + def __init__(self, on_skip, glade_file, style_sheet_path): + self.on_skip = on_skip + self.style_sheet = style_sheet_path + self.is_pretified = False + + builder = Gtk.Builder() + builder.add_from_file(glade_file) + builder.connect_signals(self) + + self.lbl_message = builder.get_object("lbl_message") + self.lbl_count = builder.get_object("lbl_count") + self.btn_skip = builder.get_object("btn_skip") + + self.window = builder.get_object("window_main") + self.window.stick() + self.window.set_keep_above(True) + screen = self.window.get_screen() + self.window.resize(screen.get_width(), screen.get_height()) + + """ + Initialize the internal properties from configuration + """ + def initialize(self, config): + self.skip_button_text = config['skip_button_text'] + self.strict_break = config['strict_break'] + self.btn_skip.set_label(self.skip_button_text) + self.btn_skip.set_visible(not self.strict_break) + + def on_window_delete(self, *args): + self.close() + + def on_skip_clicked(self, button): + self.on_skip() + self.close() + + def show_count_down(self, count): + GLib.idle_add(lambda: self.lbl_count.set_text(count)) + + def show_message(self, message): + GLib.idle_add(lambda: self.__show_message(message)) + + def __show_message(self, message): + self.lbl_message.set_text(message) + self.window.show_all() + self.window.present() + + # Set the style only for the first time + if not self.is_pretified: + # Set style + css_provider = Gtk.CssProvider() + css_provider.load_from_path(self.style_sheet) + Gtk.StyleContext().add_provider_for_screen(Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + signal.signal(signal.SIGINT, signal.SIG_DFL) + self.is_pretified = True + + # If the style is changed, the visibility must be redefined + self.btn_skip.set_visible(not self.strict_break) + + def close(self): + GLib.idle_add(lambda: self.window.hide()) + diff --git a/safeeyes/safeeyes/Notification.py b/safeeyes/safeeyes/Notification.py new file mode 100644 index 0000000..dd0f54c --- /dev/null +++ b/safeeyes/safeeyes/Notification.py @@ -0,0 +1,50 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('AppIndicator3', '0.1') +gi.require_version('Notify', '0.7') +from gi.repository import Gtk, Gdk, GLib +from gi.repository import AppIndicator3 as appindicator +from gi.repository import Notify +APPINDICATOR_ID = 'safeeyes' + +class Notification: + def __init__(self): + Notify.init(APPINDICATOR_ID) + # self.notification.set_timeout(500) + + def show(self, warning_time): + # self.notification.show() + # self.notification.close() + self.notification = Notify.Notification.new("Safe Eyes", "\nReady for a break in " + str(warning_time) + " seconds.", icon="safeeyes_enabled") + # GLib.idle_add(lambda: self.notification.show()) + self.notification.show() + + def close(self): + self.notification.close() + # GLib.idle_add(lambda: self.notification.close()) + + def quite(self): + # Notify.uninit() + GLib.idle_add(lambda: Notify.uninit()) + +# notification = Notification() +# notification.show(10) +# Gtk.main() \ No newline at end of file diff --git a/safeeyes/safeeyes/SafeEyesCore.py b/safeeyes/safeeyes/SafeEyesCore.py new file mode 100644 index 0000000..cbe2d5e --- /dev/null +++ b/safeeyes/safeeyes/SafeEyesCore.py @@ -0,0 +1,187 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk, Gio, GLib +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.schedulers.base import BaseScheduler +import time, threading, sys, subprocess, logging + +logging.basicConfig() + +class SafeEyesCore: + scheduler_job_id = "safe_eyes_scheduler" + break_count = 0 + long_break_message_index = 0 + short_break_message_index = 0 + + def __init__(self, show_alert, start_break, end_break, on_countdown): + self.skipped = False + self.scheduler = None + self.show_alert = show_alert + self.start_break = start_break + self.end_break = end_break + self.on_countdown = on_countdown + + """ + Initialize the internal properties from configuration + """ + def initialize(self, config): + self.short_break_messages = config['short_break_messages'] + self.long_break_messages = config['long_break_messages'] + 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'] + + """ + Scheduler task to execute during every interval + """ + def scheduler_job(self): + if not self.active: + return + + # Pause the scheduler until the break + if self.scheduler: + self.scheduler.pause_job(self.scheduler_job_id) + + GLib.idle_add(lambda: self.process_job()) + + + def process_job(self): + if self.is_full_screen_app_found(): + if self.scheduler: + self.scheduler.resume_job(self.scheduler_job_id) + return + + self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break) + + thread = threading.Thread(target=self.show_notification) + thread.start() + + def show_notification(self): + # Show a notification + self.show_alert() + + # Wait for the pre break warning period + time.sleep(self.pre_break_warning_time) + + if self.active: + message = "" + if self.is_long_break(): + self.long_break_message_index = (self.long_break_message_index + 1) % len(self.long_break_messages) + message = self.long_break_messages[self.long_break_message_index] + else: + self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_messages) + message = self.short_break_messages[self.short_break_message_index] + + self.start_break(message) + # Start the countdown + thread = threading.Thread(target=self.countdown) + thread.start() + + """ + Countdown the seconds of break interval, call the on_countdown and finally call the end_break method + """ + def countdown(self): + seconds = 0 + if self.is_long_break(): + seconds = self.long_break_duration + else: + seconds = self.short_break_duration + + while seconds and self.active and not self.skipped: + mins, secs = divmod(seconds, 60) + timeformat = '{:02d}:{:02d}'.format(mins, secs) + self.on_countdown(timeformat) + time.sleep(1) # Sleep for 1 second + seconds -= 1 + + # Timeout -> Close the break alert + if not self.skipped: + self.end_break() + + # Resume the scheduler + if self.active: + if self.scheduler: + self.scheduler.resume_job(self.scheduler_job_id) + + self.skipped = False + + """ + Check if the current break is long break or short current + """ + def is_long_break(self): + return self.break_count == self.no_of_short_breaks_per_long_break - 1 + + def reset(self): + self.skipped = True + + """ + Resume the timer + """ + def resume(self): + if not self.active: + self.active = True + if self.scheduler: + self.scheduler.resume_job(self.scheduler_job_id) + + """ + Pause the timer + """ + def pause(self): + if self.active: + self.active = False + if self.scheduler: + self.scheduler.pause_job(self.scheduler_job_id) + + def stop(self): + if self.scheduler: + self.active = False + self.scheduler.pause_job(self.scheduler_job_id) + self.scheduler.shutdown(wait=False) + self.scheduler = None + + """ + Start the timer + """ + def start(self): + self.active = True + if not self.scheduler: + self.scheduler = BackgroundScheduler() + self.scheduler.add_job(self.scheduler_job, 'interval', minutes=self.break_interval, id=self.scheduler_job_id) + self.scheduler.start() + + """ + Check for full-screen applications + """ + def is_full_screen_app_found(self): + screen = Gdk.Screen.get_default() + active_xid = str(screen.get_active_window().get_xid()) + cmdlist = ['xprop', '-root', '-notype','-id',active_xid, '_NET_WM_STATE'] + + try: + stdout = subprocess.check_output(cmdlist) + except subprocess.CalledProcessError: + pass + else: + if stdout: + return 'FULLSCREEN' in stdout + diff --git a/safeeyes/safeeyes/SettingsDialog.py b/safeeyes/safeeyes/SettingsDialog.py new file mode 100644 index 0000000..cce8b66 --- /dev/null +++ b/safeeyes/safeeyes/SettingsDialog.py @@ -0,0 +1,67 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk + +class SettingsDialog: + """docstring for SettingsDialog""" + def __init__(self, config, on_save_settings, glade_file): + self.config = config + self.on_save_settings = on_save_settings + + builder = Gtk.Builder() + builder.add_from_file(glade_file) + builder.connect_signals(self) + + 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.switch_strict_break = builder.get_object("switch_strict_break") + + 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.switch_strict_break.set_state(config['strict_break']) + + + def show(self): + self.window.show_all() + + def on_window_delete(self, *args): + self.window.destroy() + + def on_save_clicked(self, button): + 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['strict_break'] = self.switch_strict_break.get_state() + + self.on_save_settings(self.config) # Call the provided save method + self.window.destroy() # Close the settings window + + def on_cancel_clicked(self, button): + self.window.destroy() diff --git a/safeeyes/safeeyes/TrayIcon.py b/safeeyes/safeeyes/TrayIcon.py new file mode 100644 index 0000000..e2f1908 --- /dev/null +++ b/safeeyes/safeeyes/TrayIcon.py @@ -0,0 +1,81 @@ +# Safe Eyes is a utility to remind you to take break frequently +# to protect your eyes from eye strain. + +# Copyright (C) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('AppIndicator3', '0.1') +from gi.repository import Gtk, Gdk, GLib +from gi.repository import AppIndicator3 as appindicator + +# Global variables +active = True +APPINDICATOR_ID = 'safeeyes' + +class TrayIcon: + def __init__(self, on_show_settings, on_enable, on_disable, on_quite): + self.on_show_settings = on_show_settings; + self.on_quite = on_quite + self.on_enable = on_enable + self.on_disable = on_disable + + # 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() + + self.item_enable = Gtk.CheckMenuItem('Enable SafeEyes') + self.item_enable.set_active(True) + self.item_enable.connect('activate', self.on_toogle_enable) + + item_settings = Gtk.MenuItem('Settings') + item_settings.connect('activate', self.show_settings) + + item_quit = Gtk.MenuItem('Quit') + item_quit.connect('activate', self.quit_safe_eyes) + + self.menu.append(self.item_enable) + self.menu.append(item_settings) + self.menu.append(item_quit) + self.menu.show_all() + + self.indicator.set_menu(self.menu) + + def show_icon(self): + GLib.idle_add(lambda: self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)) + + def hide_icon(self): + GLib.idle_add(lambda: self.indicator.set_status(appindicator.IndicatorStatus.PASSIVE)) + + def quit_safe_eyes(self, *args): + self.on_quite() + + def show_settings(self, *args): + self.on_show_settings() + + def on_toogle_enable(self, *args): + active = self.item_enable.get_active() + if active: + # resume_eyesafe() + self.indicator.set_icon("safeeyes_enabled") + self.on_enable() + else: + # pause_eyesafe() + self.indicator.set_icon("safeeyes_disabled") + self.on_disable() diff --git a/safeeyes/safeeyes/config/safeeyes.json b/safeeyes/safeeyes/config/safeeyes.json new file mode 100644 index 0000000..e33dec9 --- /dev/null +++ b/safeeyes/safeeyes/config/safeeyes.json @@ -0,0 +1,19 @@ +{ + "break_interval": 15, + "long_break_duration": 60, + "long_break_messages": [ + "Walk for a while", + "Lean back at your seat and relax" + ], + "no_of_short_breaks_per_long_break": 5, + "pre_break_warning_time": 10, + "short_break_duration": 15, + "short_break_messages": [ + "Tightly close your eyes", + "Roll your eyes", + "Blink your eyes", + "Have some water" + ], + "skip_button_text": "Skip", + "strict_break": false +} \ No newline at end of file diff --git a/safeeyes/safeeyes/config/style/safeeyes_style.css b/safeeyes/safeeyes/config/style/safeeyes_style.css new file mode 100644 index 0000000..297c8d8 --- /dev/null +++ b/safeeyes/safeeyes/config/style/safeeyes_style.css @@ -0,0 +1,52 @@ +/* +* 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 . +*/ + +.window_main { + background: black; + opacity: 0.9; +} + +.btn_skip { + color: white; + border-radius: 20px; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 25px; + padding-right: 25px; + border-color: white; + background: transparent; + border-width: 2px; +} + +.btn_skip:hover { + background: white; + color: black; +} + +.lbl_message { + font-size: 16pt; + color: white; + font-weight: bold; +} + +.lbl_count { + font-size: 10pt; + color: white; +} diff --git a/safeeyes/safeeyes/glade/break_screen.glade b/safeeyes/safeeyes/glade/break_screen.glade new file mode 100644 index 0000000..ee60a07 --- /dev/null +++ b/safeeyes/safeeyes/glade/break_screen.glade @@ -0,0 +1,118 @@ + + + + + + + + + False + popup + center + True + safeeyes + True + True + False + False + False + + + + True + False + center + center + 5 + True + + + True + False + Hello World + + + + 0 + 0 + 3 + + + + + True + False + 0.20000000298023224 + + + True + False + 00 + + + + + + 1 + 1 + + + + + Skip + True + True + False + center + center + + + + + 1 + 2 + + + + + + + + + + + + + + + + + + + diff --git a/safeeyes/safeeyes/glade/settings_dialog.glade b/safeeyes/safeeyes/glade/settings_dialog.glade new file mode 100644 index 0000000..bfdd461 --- /dev/null +++ b/safeeyes/safeeyes/glade/settings_dialog.glade @@ -0,0 +1,332 @@ + + + + + + + + + 1 + 60 + 1 + 5 + + + 1 + 120 + 5 + 10 + + + 1 + 20 + 1 + 1 + 5 + + + 1 + 60 + 1 + 5 + + + 10 + 60 + 1 + 5 + + + 400 + False + Safe Eyes + False + 440 + 250 + safeeyes + + + + True + False + 5 + 5 + 5 + 5 + vertical + 2 + + + True + True + False + 5 + 5 + True + True + 20 + 10 + + + True + False + start + center + Short break duration (in seconds) + + + 0 + 0 + + + + + True + False + start + center + Long break duration (in seconds) + + + 0 + 1 + + + + + True + False + start + center + Interval between two breaks (in minutes) + + + 0 + 2 + + + + + True + False + start + center + Number of short breaks between two long breaks + + + 0 + 3 + + + + + True + False + start + center + Time to prepare for break (in seconds) + + + 0 + 4 + + + + + True + False + start + center + Strict break (hide skip button) + + + 0 + 5 + + + + + True + True + end + center + Time in minutes + number + adjust_short_break_duration + 1 + True + if-valid + + + 1 + 0 + + + + + True + True + end + center + Time in minutes + number + adjust_long_break_duration + 1 + True + if-valid + + + 1 + 1 + + + + + True + True + end + center + 1 + number + adjust_interval_between_breaks + 1 + True + if-valid + 1 + + + 1 + 2 + + + + + True + True + end + center + 1 + number + adjust_no_short_per_long + 1 + True + if-valid + 1 + + + 1 + 3 + + + + + True + True + end + center + 1 + number + adjust_time_to_prepare + 1 + True + if-valid + 1 + + + 1 + 4 + + + + + True + True + + + 1 + 5 + + + + + True + True + 0 + + + + + True + False + 5 + 5 + + + False + True + 1 + + + + + True + False + 10 + end + + + Cancel + True + True + True + + + + True + True + 0 + + + + + Save + True + True + True + + + + True + True + 1 + + + + + False + True + 2 + + + + + + \ No newline at end of file diff --git a/safeeyes/safeeyes/safeeyes b/safeeyes/safeeyes/safeeyes new file mode 100755 index 0000000..3689eae --- /dev/null +++ b/safeeyes/safeeyes/safeeyes @@ -0,0 +1,143 @@ +#!/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) 2016 Gobinath + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import gi +import json +import shutil +import errno +from BreakScreen import BreakScreen +from TrayIcon import TrayIcon +from SettingsDialog import SettingsDialog +from SafeEyesCore import SafeEyesCore +from Notification import Notification +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Define necessary paths +bin_directory = os.path.dirname(os.path.realpath(__file__)) +config_file_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/safeeyes.json') +style_sheet_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/style/safeeyes_style.css') +break_screen_glade = os.path.join(bin_directory, "glade/break_screen.glade") +settings_dialog_glade = os.path.join(bin_directory, "glade/settings_dialog.glade") +system_config_file_path = os.path.join(bin_directory, "config/safeeyes.json") +system_style_sheet_path = os.path.join(bin_directory, "config/style/safeeyes_style.css") +desktop_file = os.path.join(bin_directory, "../safeeyes.desktop") + +def show_settings(): + settings_dialog = SettingsDialog(config, save_settings, settings_dialog_glade) + settings_dialog.show() + +def show_notification(): + notification.show(config['pre_break_warning_time']) + +def show_alert(message): + notification.close() + # Hide and show tray icon if strict break is on + # This feature is required because, by disabling Safe Eyes, + # user can skip the break. + if config['strict_break']: + tray_icon.hide_icon() + break_screen.show_message(message) + +def close_alert(): + # Hide and show tray icon if strict break is on + # This feature is required because, by disabling Safe Eyes, + # user can skip the break. + if config['strict_break']: + tray_icon.show_icon() + break_screen.close() + +def on_countdown(count): + break_screen.show_count_down(count) + +def on_quite(): + core.stop() + notification.quite(); + Gtk.main_quit() + +def on_skipped(): + # Hide and show tray icon if strict break is on + # This feature is required because, by disabling Safe Eyes, + # user can skip the break. + if config['strict_break']: + tray_icon.show_icon() + core.reset() + +def save_settings(config): + # Write the configuration to file + with open(config_file_path, 'w') as config_file: + json.dump(config, config_file, indent=4, sort_keys=True) + + # Restart the core and intialize the components + core.stop() + core.initialize(config) + break_screen.initialize(config) + core.start() + +def enable_safeeyes(): + core.resume() + +def disable_safeeyes(): + core.pause() + +def prepare_local_config_dir(): + config_dir_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/style') + startup_file_path = os.path.join(os.path.expanduser('~'), '.config/autostart/safeeyes.desktop') + try: + os.makedirs(config_dir_path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(config_dir_path): + pass + else: + raise + + if not os.path.isfile(config_file_path): + shutil.copy2(system_config_file_path, config_file_path) + # Add to startup for the first time only + shutil.copy2(desktop_file, startup_file_path) + + if not os.path.isfile(style_sheet_path): + shutil.copy2(system_style_sheet_path, style_sheet_path) + +def main(): + prepare_local_config_dir() + + global break_screen + global core + global notification + global config + global tray_icon + + # Read the configuration + with open(config_file_path) as config_file: + config = json.load(config_file) + + tray_icon = TrayIcon(show_settings, enable_safeeyes, disable_safeeyes, on_quite) + break_screen = BreakScreen(on_skipped, break_screen_glade, style_sheet_path) + break_screen.initialize(config) + core = SafeEyesCore(show_notification, show_alert, close_alert, on_countdown) + core.initialize(config) + core.start() + notification = Notification() + + Gtk.main() + +main() \ No newline at end of file diff --git a/safeeyes/share/applications/safeeyes.desktop b/safeeyes/share/applications/safeeyes.desktop new file mode 100755 index 0000000..d71dce9 --- /dev/null +++ b/safeeyes/share/applications/safeeyes.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Safe Eyes +Comment=Protect your eyes from eye strain +Exec=/opt/safeeyes/safeeyes/safeeyes +Icon=safeeyes +Version=1.0.0 +Terminal=false +Type=Application +Categories=Utility; \ No newline at end of file diff --git a/safeeyes/share/icons/hicolor/128x128/apps/safeeyes.png b/safeeyes/share/icons/hicolor/128x128/apps/safeeyes.png new file mode 100644 index 0000000..0ce3591 Binary files /dev/null and b/safeeyes/share/icons/hicolor/128x128/apps/safeeyes.png differ diff --git a/safeeyes/share/icons/hicolor/16x16/status/safeeyes_disabled.png b/safeeyes/share/icons/hicolor/16x16/status/safeeyes_disabled.png new file mode 100644 index 0000000..a061774 Binary files /dev/null and b/safeeyes/share/icons/hicolor/16x16/status/safeeyes_disabled.png differ diff --git a/safeeyes/share/icons/hicolor/16x16/status/safeeyes_enabled.png b/safeeyes/share/icons/hicolor/16x16/status/safeeyes_enabled.png new file mode 100644 index 0000000..8c192f4 Binary files /dev/null and b/safeeyes/share/icons/hicolor/16x16/status/safeeyes_enabled.png differ diff --git a/safeeyes/share/icons/hicolor/24x24/status/safeeyes_disabled.png b/safeeyes/share/icons/hicolor/24x24/status/safeeyes_disabled.png new file mode 100644 index 0000000..38f6c31 Binary files /dev/null and b/safeeyes/share/icons/hicolor/24x24/status/safeeyes_disabled.png differ diff --git a/safeeyes/share/icons/hicolor/24x24/status/safeeyes_enabled.png b/safeeyes/share/icons/hicolor/24x24/status/safeeyes_enabled.png new file mode 100644 index 0000000..86ff35d Binary files /dev/null and b/safeeyes/share/icons/hicolor/24x24/status/safeeyes_enabled.png differ diff --git a/safeeyes/share/icons/hicolor/32x32/apps/safeeyes.png b/safeeyes/share/icons/hicolor/32x32/apps/safeeyes.png new file mode 100644 index 0000000..2c41795 Binary files /dev/null and b/safeeyes/share/icons/hicolor/32x32/apps/safeeyes.png differ diff --git a/safeeyes/share/icons/hicolor/32x32/status/safeeyes_disabled.png b/safeeyes/share/icons/hicolor/32x32/status/safeeyes_disabled.png new file mode 100644 index 0000000..c955d31 Binary files /dev/null and b/safeeyes/share/icons/hicolor/32x32/status/safeeyes_disabled.png differ diff --git a/safeeyes/share/icons/hicolor/32x32/status/safeeyes_enabled.png b/safeeyes/share/icons/hicolor/32x32/status/safeeyes_enabled.png new file mode 100644 index 0000000..2908b85 Binary files /dev/null and b/safeeyes/share/icons/hicolor/32x32/status/safeeyes_enabled.png differ diff --git a/safeeyes/share/icons/hicolor/48x48/apps/safeeyes.png b/safeeyes/share/icons/hicolor/48x48/apps/safeeyes.png new file mode 100644 index 0000000..8cacf08 Binary files /dev/null and b/safeeyes/share/icons/hicolor/48x48/apps/safeeyes.png differ diff --git a/safeeyes/share/icons/hicolor/48x48/status/safeeyes_disabled.png b/safeeyes/share/icons/hicolor/48x48/status/safeeyes_disabled.png new file mode 100644 index 0000000..8cb9cb5 Binary files /dev/null and b/safeeyes/share/icons/hicolor/48x48/status/safeeyes_disabled.png differ diff --git a/safeeyes/share/icons/hicolor/48x48/status/safeeyes_enabled.png b/safeeyes/share/icons/hicolor/48x48/status/safeeyes_enabled.png new file mode 100644 index 0000000..05b6db8 Binary files /dev/null and b/safeeyes/share/icons/hicolor/48x48/status/safeeyes_enabled.png differ diff --git a/safeeyes/share/icons/hicolor/64x64/apps/safeeyes.png b/safeeyes/share/icons/hicolor/64x64/apps/safeeyes.png new file mode 100644 index 0000000..0f7aef1 Binary files /dev/null and b/safeeyes/share/icons/hicolor/64x64/apps/safeeyes.png differ