Disable keyboard on break and bug fixes

This commit is contained in:
Gobinath 2016-10-23 17:26:54 +05:30
parent 6f23157974
commit 3cd6ba4e82
12 changed files with 146 additions and 81 deletions

View File

@ -1,5 +1,7 @@
safeeyes (1.0.4-1) xenial; urgency=medium
safeeyes (1.0.5-1) xenial; urgency=medium
* Bug fixes for Ubuntu 14.04 and keyboard lock during break
* Reducing minimal Python requirement
* Fixing appindicator version mismatch

View File

@ -8,7 +8,7 @@ Homepage: https://github.com/slgobinath/SafeEyes/
Package: safeeyes
Architecture: any
Depends: gir1.2-appindicator3-0.1, python (>= 2.7.0), python-apscheduler (>= 2.1.2)
Depends: gir1.2-appindicator3-0.1, python (>= 2.7.0), python-apscheduler (>= 2.1.2), python-xlib
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.
.

View File

@ -18,16 +18,21 @@
import gi
import signal
from Xlib import Xatom, Xutil
from Xlib.display import Display, X
import sys, threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib
from gi.repository import Gtk, Gdk, GLib, GdkX11
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
self.key_lock_condition = threading.Condition()
builder = Gtk.Builder()
builder.add_from_file(glade_file)
@ -41,11 +46,12 @@ class BreakScreen:
self.window.stick()
self.window.set_keep_above(True)
screen = self.window.get_screen()
self.window.resize(screen.get_width(), screen.get_height())
# 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']
@ -53,6 +59,7 @@ class BreakScreen:
self.btn_skip.set_visible(not self.strict_break)
def on_window_delete(self, *args):
self.lock_keyboard = False
self.close()
def on_skip_clicked(self, button):
@ -65,10 +72,35 @@ class BreakScreen:
def show_message(self, message):
GLib.idle_add(lambda: self.__show_message(message))
"""
Lock the keyboard to prevent the user from using keyboard shortcuts
"""
def block_keyboard(self):
self.lock_keyboard = True
display = Display()
root = display.screen().root
# Grap the keyboard
root.grab_keyboard(owner_events = False, pointer_mode = X.GrabModeAsync, keyboard_mode = X.GrabModeAsync, time = X.CurrentTime)
# Consume keyboard events
self.key_lock_condition.acquire()
while self.lock_keyboard:
self.key_lock_condition.wait()
self.key_lock_condition.release()
# Ungrap the keyboard
display.ungrab_keyboard(X.CurrentTime)
display.flush()
def release_keyboard(self):
self.key_lock_condition.acquire()
self.lock_keyboard = False
self.key_lock_condition.notify()
self.key_lock_condition.release()
def __show_message(self, message):
self.lbl_message.set_text(message)
self.window.show_all()
self.window.present()
self.window.fullscreen()
# Set the style only for the first time
if not self.is_pretified:
@ -81,7 +113,11 @@ class BreakScreen:
# If the style is changed, the visibility must be redefined
self.btn_skip.set_visible(not self.strict_break)
# Lock the keyboard
thread = threading.Thread(target=self.block_keyboard)
thread.start()
def close(self):
self.release_keyboard()
GLib.idle_add(lambda: self.window.hide())

View File

@ -20,7 +20,7 @@ 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 Gtk, Gdk, GLib, GdkX11
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Notify
APPINDICATOR_ID = 'safeeyes'

View File

@ -18,24 +18,26 @@
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, Gio, GLib
from gi.repository import Gdk, Gio, GLib, GdkX11
from apscheduler.scheduler import Scheduler
import time, threading, sys, subprocess, logging
logging.basicConfig()
class SafeEyesCore:
break_count = 0
long_break_message_index = 0
short_break_message_index = 0
def __init__(self, show_alert, start_break, end_break, on_countdown):
def __init__(self, show_notification, start_break, end_break, on_countdown):
# Initialize the variables
self.break_count = 0
self.long_break_message_index = 0
self.short_break_message_index = 0
self.skipped = False
self.scheduler = None
self.show_alert = show_alert
self.show_notification = show_notification
self.start_break = start_break
self.end_break = end_break
self.on_countdown = on_countdown
self.notification_condition = threading.Condition()
"""
Initialize the internal properties from configuration
@ -57,32 +59,39 @@ class SafeEyesCore:
return
# Pause the scheduler until the break
if self.scheduler and self.scheduled_job:
self.scheduler.unschedule_job(self.scheduled_job)
self.scheduled_job = None
if self.scheduler and self.scheduled_job_id:
self.scheduler.unschedule_job(self.scheduled_job_id)
self.scheduled_job_id = None
GLib.idle_add(lambda: self.process_job())
"""
Used to process the job in default thread because is_full_screen_app_found must be run by default thread
"""
def process_job(self):
if self.is_full_screen_app_found():
# If full screen app found, do not show break screen.
# Resume the scheduler
if self.scheduler:
self.scheduled_job = self.scheduler.add_interval_job(self.scheduler_job, minutes=self.break_interval)
self.schedule_job()
return
self.break_count = ((self.break_count + 1) % self.no_of_short_breaks_per_long_break)
thread = threading.Thread(target=self.show_notification)
thread = threading.Thread(target=self.notify_and_start_break)
thread.start()
def show_notification(self):
"""
Show notification and start the break after given number of seconds
"""
def notify_and_start_break(self):
# Show a notification
self.show_alert()
self.show_notification()
# Wait for the pre break warning period
time.sleep(self.pre_break_warning_time)
self.notification_condition.acquire()
self.notification_condition.wait(self.pre_break_warning_time)
self.notification_condition.release()
# User can disable SafeEyes during notification
if self.active:
@ -94,38 +103,33 @@ class SafeEyesCore:
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]
# Show the break screen
self.start_break(message)
# Start the countdown
thread = threading.Thread(target=self.countdown)
thread.start()
seconds = 0
if self.is_long_break():
seconds = self.long_break_duration
else:
seconds = self.short_break_duration
"""
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
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
# Loop terminated because of timeout (not skipped) -> Close the break alert
if not self.skipped:
self.end_break()
# Loop terminated because of timeout (not skipped) -> Close the break alert
if not self.skipped:
self.end_break()
# Resume the scheduler
if self.active:
if self.scheduler:
self.schedule_job()
# Resume the scheduler
if self.active:
if self.scheduler:
self.scheduled_job = self.scheduler.add_interval_job(self.scheduler_job, minutes=self.break_interval)
self.skipped = False
self.skipped = False
"""
Check if the current break is long break or short current
@ -134,47 +138,69 @@ class SafeEyesCore:
return self.break_count == self.no_of_short_breaks_per_long_break - 1
# User skipped the break using Skip button
def reset(self):
def skip_break(self):
self.skipped = True
"""
Resume the timer
Reschedule the job
"""
def resume(self):
if not self.active:
self.active = True
if self.scheduler:
self.scheduled_job = self.scheduler.add_interval_job(self.scheduler_job, minutes=self.break_interval)
"""
Pause the timer
"""
def pause(self):
def toggle_active_state(self):
if self.active:
self.active = False
if self.scheduler and self.scheduled_job:
self.scheduler.unschedule_job(self.scheduled_job)
self.scheduled_job = None
if self.scheduler and self.scheduled_job_id:
self.scheduler.unschedule_job(self.scheduled_job_id)
self.scheduled_job_id = None
# If waiting after notification, notify the thread to wake up and die
self.notification_condition.acquire()
self.notification_condition.notify()
self.notification_condition.release()
else:
self.active = True
if self.scheduler:
self.schedule_job()
"""
Unschedule the job and shutdown the scheduler
"""
def stop(self):
if self.scheduler:
self.active = False
if self.scheduled_job:
self.scheduler.unschedule_job(self.scheduled_job)
self.scheduled_job = None
if self.scheduled_job_id:
self.scheduler.unschedule_job(self.scheduled_job_id)
self.scheduled_job_id = None
self.scheduler.shutdown(wait=False)
self.scheduler = None
# If waiting after notification, notify the thread to wake up and die
self.notification_condition.acquire()
self.notification_condition.notify()
self.notification_condition.release()
"""
Start the timer
Schedule the job and start the scheduler
"""
def start(self):
self.active = True
if not self.scheduler:
self.scheduler = Scheduler()
self.scheduled_job = self.scheduler.add_interval_job(self.scheduler_job, minutes=self.break_interval)
self.schedule_job()
self.scheduler.start()
"""
Restart the scheduler after changing settings
"""
def restart(self):
if self.active:
self.stop()
self.start()
"""
Schedule the job
"""
def schedule_job(self):
self.scheduled_job_id = self.scheduler.add_interval_job(self.scheduler_job, seconds=self.break_interval)
"""
Check for full-screen applications
"""

View File

@ -18,7 +18,7 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from gi.repository import Gtk, Gdk, GdkX11
class SettingsDialog:
"""docstring for SettingsDialog"""
@ -43,7 +43,7 @@ class SettingsDialog:
self.spin_interval_between_two_breaks.set_value(config['break_interval'])
self.spin_short_between_long.set_value(config['no_of_short_breaks_per_long_break'])
self.spin_time_to_prepare.set_value(config['pre_break_warning_time'])
self.switch_strict_break.set_state(config['strict_break'])
self.switch_strict_break.set_active(config['strict_break'])
def show(self):
@ -58,7 +58,7 @@ class SettingsDialog:
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.config['strict_break'] = self.switch_strict_break.get_active()
self.on_save_settings(self.config) # Call the provided save method
self.window.destroy() # Close the settings window

View File

@ -19,7 +19,7 @@
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 Gtk, Gdk, GLib, GdkX11
from gi.repository import AppIndicator3 as appindicator
# Global variables

View File

@ -11,6 +11,7 @@
"short_break_messages": [
"Tightly close your eyes",
"Roll your eyes",
"Rotate your eyes",
"Blink your eyes",
"Have some water"
],

View File

@ -33,6 +33,7 @@
border-color: white;
background: transparent;
border-width: 2px;
border-image: none;
}
.btn_skip:hover {

View File

@ -25,7 +25,7 @@
<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="type">popup</property> -->
<property name="window_position">center</property>
<property name="hide_titlebar_when_maximized">True</property>
<property name="icon_name">safeeyes</property>

View File

@ -22,7 +22,7 @@
-->
<interface>
<requires lib="gtk+" version="3.12"/>
<requires lib="gtk+" version="3.10"/>
<object class="GtkAdjustment" id="adjust_interval_between_breaks">
<property name="lower">1</property>
<property name="upper">60</property>

View File

@ -53,16 +53,16 @@ def show_alert(message):
# 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()
# 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()
# if config['strict_break']:
# tray_icon.show_icon()
break_screen.close()
def on_countdown(count):
@ -79,7 +79,7 @@ def on_skipped():
# user can skip the break.
if config['strict_break']:
tray_icon.show_icon()
core.reset()
core.skip_break()
def save_settings(config):
# Write the configuration to file
@ -87,16 +87,15 @@ def save_settings(config):
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()
core.restart()
def enable_safeeyes():
core.resume()
core.toggle_active_state()
def disable_safeeyes():
core.pause()
core.toggle_active_state()
def prepare_local_config_dir():
config_dir_path = os.path.join(os.path.expanduser('~'), '.config/safeeyes/style')