Safe Eyes v1.0.0
|
@ -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
|
BIN
icon-64.png
Before Width: | Height: | Size: 1.7 KiB |
252
safeeyes
|
@ -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()
|
10
safeeyes.cfg
|
@ -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
|
||||
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
9
|
|
@ -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
|
|
@ -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".
|
|
@ -0,0 +1,3 @@
|
|||
safeeyes /opt/safeeyes
|
||||
share/applications/safeeyes.desktop /opt/safeeyes
|
||||
share /usr
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh $@
|
|
@ -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())
|
||||
|
|
@ -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()
|
|
@ -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
|
||||
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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()
|
|
@ -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;
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 649 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 902 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.5 KiB |