Add image to break screen

This commit is contained in:
Gobinath 2017-04-07 16:20:23 -04:00
parent 6c5faca8ad
commit af8e9d51ab
16 changed files with 265 additions and 133 deletions

View File

@ -97,8 +97,8 @@ class BreakScreen:
""" """
Show the break screen with the given message on all displays. Show the break screen with the given message on all displays.
""" """
def show_message(self, message): def show_message(self, message, image_path):
GLib.idle_add(lambda: self.__show_break_screen(message)) GLib.idle_add(lambda: self.__show_break_screen(message, image_path))
""" """
@ -115,7 +115,7 @@ class BreakScreen:
""" """
Show an empty break screen on all screens. Show an empty break screen on all screens.
""" """
def __show_break_screen(self, message): def __show_break_screen(self, message, image_path):
# Lock the keyboard # Lock the keyboard
thread = threading.Thread(target=self.__lock_keyboard) thread = threading.Thread(target=self.__lock_keyboard)
thread.start() thread.start()
@ -138,12 +138,11 @@ class BreakScreen:
lbl_count = builder.get_object("lbl_count") lbl_count = builder.get_object("lbl_count")
btn_skip = builder.get_object("btn_skip") btn_skip = builder.get_object("btn_skip")
btn_postpone = builder.get_object("btn_postpone") btn_postpone = builder.get_object("btn_postpone")
lbl_left = builder.get_object("lbl_left")
lbl_right = builder.get_object("lbl_right")
img_break = builder.get_object("img_break")
lbl_message.set_label(message) window.move(x, y)
btn_skip.set_label(self.skip_button_text)
btn_skip.set_visible(not self.strict_break)
btn_postpone.set_label(self.postpone_button_text)
btn_postpone.set_visible(not self.strict_break)
self.windows.append(window) self.windows.append(window)
self.count_labels.append(lbl_count) self.count_labels.append(lbl_count)
@ -151,12 +150,26 @@ class BreakScreen:
# Set visual to apply css theme. It should be called before show method. # Set visual to apply css theme. It should be called before show method.
window.set_visual(window.get_screen().get_rgba_visual()) window.set_visual(window.get_screen().get_rgba_visual())
window.move(x, y)
window.stick() window.stick()
window.set_keep_above(True) window.set_keep_above(True)
window.present() window.present()
window.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
window.resize(monitor_gemoetry.width, monitor_gemoetry.height)
window.fullscreen() window.fullscreen()
# Set values
if image_path:
img_break.set_from_file(image_path)
lbl_message.set_label(message)
btn_skip.set_label(self.skip_button_text)
btn_postpone.set_label(self.postpone_button_text)
# Set the visibility of buttons
btn_postpone.set_visible(not self.strict_break)
btn_skip.set_visible(not self.strict_break)
window.present()
""" """
Update the countdown on all break screens. Update the countdown on all break screens.

View File

@ -78,12 +78,14 @@ class SafeEyesCore:
break_time = short_break_config.get('time', self.short_break_duration) break_time = short_break_config.get('time', self.short_break_duration)
audible_alert = short_break_config.get('audible_alert', config['audible_alert']) audible_alert = short_break_config.get('audible_alert', config['audible_alert'])
image = short_break_config.get('image')
# Validate time value # Validate time value
if not isinstance(break_time, int) or break_time <= 0: if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in short break: ' + str(short_break_config)) logging.error('Invalid time in short break: ' + str(short_break_config))
continue continue
self.short_break_exercises.append([name, break_time, audible_alert]) self.short_break_exercises.append([name, break_time, audible_alert, image])
for long_break_config in config['long_breaks']: for long_break_config in config['long_breaks']:
exercise_name = long_break_config['name'] exercise_name = long_break_config['name']
@ -96,13 +98,14 @@ class SafeEyesCore:
break_time = long_break_config.get('time', self.long_break_duration) break_time = long_break_config.get('time', self.long_break_duration)
audible_alert = long_break_config.get('audible_alert', config['audible_alert']) audible_alert = long_break_config.get('audible_alert', config['audible_alert'])
image = long_break_config.get('image')
# Validate time value # Validate time value
if not isinstance(break_time, int) or break_time <= 0: if not isinstance(break_time, int) or break_time <= 0:
logging.error('Invalid time in long break: ' + str(long_break_config)) logging.error('Invalid time in long break: ' + str(long_break_config))
continue continue
self.long_break_exercises.append([name, break_time, audible_alert]) self.long_break_exercises.append([name, break_time, audible_alert, image])
""" """
@ -270,6 +273,7 @@ class SafeEyesCore:
# User can disable SafeEyes during notification # User can disable SafeEyes during notification
if self.__is_running(): if self.__is_running():
message = "" message = ""
image = None
seconds = 0 seconds = 0
audible_alert = None audible_alert = None
if self.__is_long_break(): if self.__is_long_break():
@ -278,15 +282,17 @@ class SafeEyesCore:
message = self.long_break_exercises[self.long_break_message_index][0] message = self.long_break_exercises[self.long_break_message_index][0]
seconds = self.long_break_exercises[self.long_break_message_index][1] seconds = self.long_break_exercises[self.long_break_message_index][1]
audible_alert = self.long_break_exercises[self.long_break_message_index][2] audible_alert = self.long_break_exercises[self.long_break_message_index][2]
image = self.long_break_exercises[self.long_break_message_index][3]
else: else:
logging.info("Count is {}; get a short beak message".format(self.break_count)) logging.info("Count is {}; get a short beak message".format(self.break_count))
self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises) self.short_break_message_index = (self.short_break_message_index + 1) % len(self.short_break_exercises)
message = self.short_break_exercises[self.short_break_message_index][0] message = self.short_break_exercises[self.short_break_message_index][0]
seconds = self.short_break_exercises[self.short_break_message_index][1] seconds = self.short_break_exercises[self.short_break_message_index][1]
audible_alert = self.short_break_exercises[self.short_break_message_index][2] audible_alert = self.short_break_exercises[self.short_break_message_index][2]
image = self.short_break_exercises[self.short_break_message_index][3]
# Show the break screen # Show the break screen
self.start_break(message) self.start_break(message, image)
# Use self.active instead of self.__is_running to avoid idle pause interrupting the break # Use self.active instead of self.__is_running to avoid idle pause interrupting the break
while seconds and self.active and not self.skipped and not self.postponed: while seconds and self.active and not self.skipped and not self.postponed:

View File

@ -25,9 +25,10 @@ import pyaudio, wave
bin_directory = os.path.dirname(os.path.realpath(__file__)) bin_directory = os.path.dirname(os.path.realpath(__file__))
home_directory = os.path.expanduser('~') home_directory = os.path.expanduser('~')
system_language_directory = os.path.join(bin_directory, "config/lang") system_language_directory = os.path.join(bin_directory, "config/lang")
config_directory = os.path.join(home_directory, '.config/safeeyes')
""" """
Play the alert.mp3 Play the alert.wav
""" """
def play_notification(): def play_notification():
logging.info("Playing audible alert") logging.info("Playing audible alert")
@ -35,7 +36,9 @@ def play_notification():
try: try:
# Open the sound file # Open the sound file
path = os.path.join(bin_directory, 'resource','alert.wav') path = get_resource_path('alert.wav')
if path is None:
return
sound = wave.open(path, 'rb') sound = wave.open(path, 'rb')
# Create a sound stream # Create a sound stream
@ -61,6 +64,23 @@ def play_notification():
logging.warning('Unable to play audible alert') logging.warning('Unable to play audible alert')
logging.exception(e) logging.exception(e)
"""
Return the user-defined resource if a system resource is overridden by the user.
Otherwise, return the system resource. Return None if the specified resource does not exist.
"""
def get_resource_path(resource_name):
if resource_name is None:
return None
resource_location = os.path.join(config_directory, 'resource', resource_name)
if not os.path.isfile(resource_location):
resource_location = os.path.join(bin_directory, 'resource', resource_name)
if not os.path.isfile(resource_location):
logging.error('Resource not found: ' + resource_name)
resource_location = None
return resource_location
""" """
Get system idle time in minutes. Get system idle time in minutes.
Return the idle time if xprintidle is available, otherwise return 0. Return the idle time if xprintidle is available, otherwise return 0.
@ -97,33 +117,38 @@ def execute_main_thread(target_function, args=None):
def is_active_window_skipped(skip_break_window_classes, take_break_window_classes, unfullscreen_allowed=False): def is_active_window_skipped(skip_break_window_classes, take_break_window_classes, unfullscreen_allowed=False):
logging.info("Searching for full-screen application") logging.info("Searching for full-screen application")
screen = Gdk.Screen.get_default() screen = Gdk.Screen.get_default()
active_xid = str(screen.get_active_window().get_xid())
cmdlist = ['xprop', '-root', '-notype','-id',active_xid, 'WM_CLASS', '_NET_WM_STATE']
try: active_window = screen.get_active_window()
stdout = subprocess.check_output(cmdlist).decode('utf-8') if active_window:
except subprocess.CalledProcessError: active_xid = str(active_window.get_xid())
logging.warning("Error in finding full-screen application") cmdlist = ['xprop', '-root', '-notype','-id',active_xid, 'WM_CLASS', '_NET_WM_STATE']
pass
else:
if stdout:
is_fullscreen = 'FULLSCREEN' in stdout
# Extract the process name
process_names = re.findall('"(.+?)"', stdout)
if process_names:
process = process_names[1].lower()
if process in skip_break_window_classes:
return True
elif process in take_break_window_classes:
if is_fullscreen and unfullscreen_allowed:
try:
screen.get_active_window().unfullscreen()
except:
logging.error('Error in unfullscreen the window ' + process)
pass
return False
return is_fullscreen try:
stdout = subprocess.check_output(cmdlist).decode('utf-8')
except subprocess.CalledProcessError:
logging.warning("Error in finding full-screen application")
pass
else:
if stdout:
is_fullscreen = 'FULLSCREEN' in stdout
# Extract the process name
process_names = re.findall('"(.+?)"', stdout)
if process_names:
process = process_names[1].lower()
if process in skip_break_window_classes:
return True
elif process in take_break_window_classes:
if is_fullscreen and unfullscreen_allowed:
try:
active_window.unfullscreen()
except:
logging.error('Error in unfullscreen the window ' + process)
pass
return False
return is_fullscreen
return False
""" """

View File

@ -43,7 +43,7 @@ system_style_sheet_path = os.path.join(Utility.bin_directory, "config/style/safe
is_active = True is_active = True
CONFIGURATION_VERSION = 4 CONFIGURATION_VERSION = 4
SAFE_EYES_VERSION = "1.1.8" SAFE_EYES_VERSION = "1.2.0"
""" """
Listen to tray icon Settings action and send the signal to Settings dialog. Listen to tray icon Settings action and send the signal to Settings dialog.
@ -72,10 +72,10 @@ def show_notification():
""" """
Receive the break signal from core and pass it to the break screen. Receive the break signal from core and pass it to the break screen.
""" """
def show_alert(message): def show_alert(message, image_name):
logging.info("Show the break screen") logging.info("Show the break screen")
notification.close() notification.close()
break_screen.show_message(message) break_screen.show_message(message, Utility.get_resource_path(image_name))
if config['strict_break'] and is_active: if config['strict_break'] and is_active:
Utility.execute_main_thread(tray_icon.unlock_menu) Utility.execute_main_thread(tray_icon.unlock_menu)
@ -258,7 +258,7 @@ def running():
try: try:
# Check if safeeyes is in process arguments # Check if safeeyes is in process arguments
cmd_line = proc.cmdline() cmd_line = proc.cmdline()
if 'python2' == cmd_line[0] and 'safeeyes' in cmd_line[1]: if 'python3' == cmd_line[0] and 'safeeyes' in cmd_line[1]:
process_count += 1 process_count += 1
if process_count > 1: if process_count > 1:
return True return True

View File

@ -40,33 +40,42 @@
], ],
"short_breaks": [ "short_breaks": [
{ {
"name": "short_break_close_eyes" "name": "short_break_close_eyes",
"image": "short_break_close_eyes.png"
}, },
{ {
"name": "short_break_roll_eyes" "name": "short_break_roll_eyes",
"image": "short_break_roll_eyes.png"
}, },
{ {
"name": "short_break_rotate_clockwise" "name": "short_break_rotate_clockwise",
"image": "short_break_rotate_clockwise.png"
}, },
{ {
"name": "short_break_rotate_counter_clockwise" "name": "short_break_rotate_counter_clockwise",
"image": "short_break_rotate_counter_clockwise.png"
}, },
{ {
"name": "short_break_blink" "name": "short_break_blink",
"image": "short_break_blink.png"
}, },
{ {
"name": "short_break_focus_far_distance" "name": "short_break_focus_far_distance",
"image": "short_break_focus_far_distance.png"
}, },
{ {
"name": "short_break_drink_water" "name": "short_break_drink_water",
"image": "short_break_drink_water.png"
} }
], ],
"long_breaks": [ "long_breaks": [
{ {
"name": "long_break_walk" "name": "long_break_walk",
"image": "long_break_walk.png"
}, },
{ {
"name": "long_break_lean_back" "name": "long_break_lean_back",
"image": "long_break_lean_back.png"
} }
], ],
"custom_exercises": { "custom_exercises": {

View File

@ -71,3 +71,9 @@
font-size: 12pt; font-size: 12pt;
color: white; color: white;
} }
.panel_left {
}
.panel_right {
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 --> <!-- Generated with glade 3.18.3 -->
<!-- <!--
~ Safe Eyes is a utility to remind you to take break frequently ~ Safe Eyes is a utility to remind you to take break frequently
~ to protect your eyes from eye strain. ~ to protect your eyes from eye strain.
@ -20,7 +19,6 @@
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<interface> <interface>
<requires lib="gtk+" version="3.10"/> <requires lib="gtk+" version="3.10"/>
<object class="GtkWindow" id="window_main"> <object class="GtkWindow" id="window_main">
@ -33,120 +31,195 @@
<property name="focus_on_map">False</property> <property name="focus_on_map">False</property>
<property name="decorated">False</property> <property name="decorated">False</property>
<property name="deletable">False</property> <property name="deletable">False</property>
<property name="gravity">center</property>
<signal name="delete-event" handler="on_window_delete" swapped="no"/> <signal name="delete-event" handler="on_window_delete" swapped="no"/>
<child> <child>
<object class="GtkGrid" id="grid_parent"> <object class="GtkBox" id="box1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</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> <child>
<object class="GtkLabel" id="lbl_message"> <object class="GtkLabel" id="lbl_left">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Hello World</property> <property name="halign">start</property>
<property name="valign">start</property>
<property name="margin_left">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<style> <style>
<class name="lbl_message"/> <class name="panel_left"/>
</style> </style>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="expand">True</property>
<property name="top_attach">0</property> <property name="fill">True</property>
<property name="width">3</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkAlignment" id="alignment_button"> <object class="GtkGrid" id="grid1">
<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="GtkBox" id="box_buttons">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">center</property> <property name="halign">center</property>
<property name="spacing">50</property> <property name="valign">center</property>
<property name="homogeneous">True</property> <property name="row_spacing">10</property>
<child> <child>
<object class="GtkButton" id="btn_postpone"> <object class="GtkGrid" id="grid_parent">
<property name="label" translatable="yes">Postpone</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="receives_default">True</property> <property name="halign">center</property>
<property name="valign">center</property> <property name="valign">center</property>
<signal name="clicked" handler="on_postpone_clicked" swapped="no"/> <property name="hexpand">True</property>
<style> <property name="row_spacing">15</property>
<class name="btn_postpone"/> <child>
</style> <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>
<property name="justify">center</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="GtkBox" id="box_buttons">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="spacing">50</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="btn_postpone">
<property name="label" translatable="yes">Postpone</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_postpone_clicked" swapped="no"/>
<style>
<class name="btn_postpone"/>
</style>
</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_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="valign">center</property>
<signal name="clicked" handler="on_skip_clicked" swapped="no"/>
<style>
<class name="btn_skip"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="left_attach">0</property>
<property name="fill">True</property> <property name="top_attach">1</property>
<property name="position">0</property> <property name="height">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="btn_skip"> <object class="GtkImage" id="img_break">
<property name="label" translatable="yes">Skip</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_skip_clicked" swapped="no"/>
<style>
<class name="btn_skip"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="left_attach">0</property>
<property name="fill">True</property> <property name="top_attach">0</property>
<property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="expand">False</property>
<property name="top_attach">3</property> <property name="fill">True</property>
<property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
<placeholder/> <object class="GtkLabel" id="lbl_right">
</child> <property name="visible">True</property>
<child> <property name="can_focus">False</property>
<placeholder/> <property name="halign">end</property>
</child> <property name="valign">start</property>
<child> <property name="margin_right">10</property>
<placeholder/> <property name="margin_top">10</property>
</child> <property name="margin_bottom">10</property>
<child> <style>
<placeholder/> <class name="panel_right"/>
</child> </style>
<child> </object>
<placeholder/> <packing>
</child> <property name="expand">True</property>
<child> <property name="fill">True</property>
<placeholder/> <property name="position">2</property>
</child> </packing>
<child>
<placeholder/>
</child> </child>
</object> </object>
</child> </child>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB