Safe Eyes v1.0.0

This commit is contained in:
Gobinath 2016-10-15 09:41:27 +05:30
parent 427ff11ac4
commit 6ea7380efb
35 changed files with 1259 additions and 264 deletions

54
README.md Normal file
View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

2
run
View File

@ -1,2 +0,0 @@
#!/bin/bash
python safeeyes

252
safeeyes
View File

@ -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("<span size='x-large' font_weight='bold' color='white'>" + t + "</span>");
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 "<span size='xx-large' font_weight='bold' color='white'>" + message + "</span>"
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("<b>Safe Eyes</b>", "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()

View File

@ -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

View File

@ -0,0 +1,5 @@
safeeyes (1.0.0-1) xenial; urgency=medium
* Initial release
-- Loganathan Gobinath <slgobinath@gmail.com> Sat, 15 Oct 2016 06:28:40 +0530

1
safeeyes/debian/compat Normal file
View File

@ -0,0 +1 @@
9

23
safeeyes/debian/control Normal file
View File

@ -0,0 +1,23 @@
Source: safeeyes
Section: utils
Priority: optional
Maintainer: Loganathan Gobinath <slgobinath@gmail.com>
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

24
safeeyes/debian/copyright Normal file
View File

@ -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 <slgobinath@gmail.com>
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 <https://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

3
safeeyes/debian/install Normal file
View File

@ -0,0 +1,3 @@
safeeyes /opt/safeeyes
share/applications/safeeyes.desktop /opt/safeeyes
share /usr

3
safeeyes/debian/rules Executable file
View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
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())

View File

@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@ -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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
.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;
}

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!--
~ Safe Eyes is a utility to remind you to take break frequently
~ to protect your eyes from eye strain.
~ Copyright (C) 2016 Gobinath
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkWindow" id="window_main">
<property name="can_focus">False</property>
<property name="type">popup</property>
<property name="window_position">center</property>
<property name="hide_titlebar_when_maximized">True</property>
<property name="icon_name">safeeyes</property>
<property name="skip_taskbar_hint">True</property>
<property name="urgency_hint">True</property>
<property name="focus_on_map">False</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child>
<object class="GtkGrid" id="grid_parent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="row_spacing">5</property>
<property name="row_homogeneous">True</property>
<child>
<object class="GtkLabel" id="lbl_message">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hello World</property>
<style>
<class name="lbl_message"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">3</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="yscale">0.20000000298023224</property>
<child>
<object class="GtkLabel" id="lbl_count">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">00</property>
<style>
<class name="lbl_count"/>
</style>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_skip">
<property name="label" translatable="yes">Skip</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_skip_clicked" swapped="no"/>
<style>
<class name="btn_skip"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<style>
<class name="window_main"/>
</style>
</object>
</interface>

View File

@ -0,0 +1,332 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!--
~ Safe Eyes is a utility to remind you to take break frequently
~ to protect your eyes from eye strain.
~ Copyright (C) 2016 Gobinath
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkAdjustment" id="adjust_interval_between_breaks">
<property name="lower">1</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_long_break_duration">
<property name="lower">1</property>
<property name="upper">120</property>
<property name="step_increment">5</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjust_no_short_per_long">
<property name="lower">1</property>
<property name="upper">20</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_short_break_duration">
<property name="lower">1</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkAdjustment" id="adjust_time_to_prepare">
<property name="lower">10</property>
<property name="upper">60</property>
<property name="step_increment">1</property>
<property name="page_increment">5</property>
</object>
<object class="GtkWindow" id="window_settings">
<property name="height_request">400</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Safe Eyes</property>
<property name="resizable">False</property>
<property name="default_width">440</property>
<property name="default_height">250</property>
<property name="icon_name">safeeyes</property>
<signal handler="on_window_delete" name="delete-event" swapped="no"/>
<child>
<object class="GtkBox" id="layout_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkGrid" id="layoutt_grid">
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="row_spacing">20</property>
<property name="column_spacing">10</property>
<child>
<object class="GtkLabel" id="lbl_short_break">
<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">Short break duration (in seconds)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_long_break">
<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">Long break duration (in seconds)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_interval_bettween_breaks">
<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">Interval between two breaks (in minutes)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_short_per_long">
<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">Number of short breaks between two long breaks</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_time_to_prepare">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Time to prepare for break (in seconds)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_strict_break">
<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">Strict break (hide skip button)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_short_break_duration">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="primary_icon_tooltip_text" translatable="yes">Time in minutes</property>
<property name="input_purpose">number</property>
<property name="adjustment">adjust_short_break_duration</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
<property name="update_policy">if-valid</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_long_break_duration">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="primary_icon_tooltip_text" translatable="yes">Time in minutes</property>
<property name="input_purpose">number</property>
<property name="adjustment">adjust_long_break_duration</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
<property name="update_policy">if-valid</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_interval_between_two_breaks">
<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_interval_between_breaks</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">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_short_between_long">
<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_no_short_per_long</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">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_time_to_prepare">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="text" translatable="yes">1</property>
<property name="input_purpose">number</property>
<property name="adjustment">adjust_time_to_prepare</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">4</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_strict_break">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btn_cancel">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal handler="on_cancel_clicked" name="clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_save">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal handler="on_save_clicked" name="clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

143
safeeyes/safeeyes/safeeyes Executable file
View File

@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB