Disable keyboard on break and bug fixes
This commit is contained in:
parent
6f23157974
commit
3cd6ba4e82
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
.
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"short_break_messages": [
|
||||
"Tightly close your eyes",
|
||||
"Roll your eyes",
|
||||
"Rotate your eyes",
|
||||
"Blink your eyes",
|
||||
"Have some water"
|
||||
],
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
border-color: white;
|
||||
background: transparent;
|
||||
border-width: 2px;
|
||||
border-image: none;
|
||||
}
|
||||
|
||||
.btn_skip:hover {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue