From af8e9d51ab8360abdf724ef6d77ea4faf523af27 Mon Sep 17 00:00:00 2001 From: Gobinath Date: Fri, 7 Apr 2017 16:20:23 -0400 Subject: [PATCH] Add image to break screen --- safeeyes/BreakScreen.py | 31 ++- safeeyes/SafeEyesCore.py | 12 +- safeeyes/Utility.py | 79 ++++-- safeeyes/__main__.py | 8 +- safeeyes/config/safeeyes.json | 27 +- safeeyes/config/style/safeeyes_style.css | 6 + safeeyes/glade/break_screen.glade | 235 ++++++++++++------ safeeyes/resource/long_break_lean_back.png | Bin 0 -> 1318 bytes safeeyes/resource/long_break_walk.png | Bin 0 -> 1517 bytes safeeyes/resource/short_break_blink.png | Bin 0 -> 1775 bytes safeeyes/resource/short_break_close_eyes.png | Bin 0 -> 1512 bytes safeeyes/resource/short_break_drink_water.png | Bin 0 -> 1364 bytes .../short_break_focus_far_distance.png | Bin 0 -> 1899 bytes safeeyes/resource/short_break_roll_eyes.png | Bin 0 -> 975 bytes .../resource/short_break_rotate_clockwise.png | Bin 0 -> 1373 bytes .../short_break_rotate_counter_clockwise.png | Bin 0 -> 1386 bytes 16 files changed, 265 insertions(+), 133 deletions(-) create mode 100644 safeeyes/resource/long_break_lean_back.png create mode 100644 safeeyes/resource/long_break_walk.png create mode 100644 safeeyes/resource/short_break_blink.png create mode 100644 safeeyes/resource/short_break_close_eyes.png create mode 100644 safeeyes/resource/short_break_drink_water.png create mode 100644 safeeyes/resource/short_break_focus_far_distance.png create mode 100644 safeeyes/resource/short_break_roll_eyes.png create mode 100644 safeeyes/resource/short_break_rotate_clockwise.png create mode 100644 safeeyes/resource/short_break_rotate_counter_clockwise.png diff --git a/safeeyes/BreakScreen.py b/safeeyes/BreakScreen.py index b27dbd1..f9beade 100644 --- a/safeeyes/BreakScreen.py +++ b/safeeyes/BreakScreen.py @@ -97,8 +97,8 @@ class BreakScreen: """ Show the break screen with the given message on all displays. """ - def show_message(self, message): - GLib.idle_add(lambda: self.__show_break_screen(message)) + def show_message(self, message, image_path): + 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. """ - def __show_break_screen(self, message): + def __show_break_screen(self, message, image_path): # Lock the keyboard thread = threading.Thread(target=self.__lock_keyboard) thread.start() @@ -138,12 +138,11 @@ class BreakScreen: lbl_count = builder.get_object("lbl_count") btn_skip = builder.get_object("btn_skip") 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) - 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) + window.move(x, y) self.windows.append(window) 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. window.set_visual(window.get_screen().get_rgba_visual()) - window.move(x, y) window.stick() window.set_keep_above(True) window.present() + window.set_position(Gtk.WindowPosition.CENTER_ALWAYS) + window.resize(monitor_gemoetry.width, monitor_gemoetry.height) 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. diff --git a/safeeyes/SafeEyesCore.py b/safeeyes/SafeEyesCore.py index 355f4d0..7a96b8c 100644 --- a/safeeyes/SafeEyesCore.py +++ b/safeeyes/SafeEyesCore.py @@ -78,12 +78,14 @@ class SafeEyesCore: break_time = short_break_config.get('time', self.short_break_duration) audible_alert = short_break_config.get('audible_alert', config['audible_alert']) + image = short_break_config.get('image') + # Validate time value if not isinstance(break_time, int) or break_time <= 0: logging.error('Invalid time in short break: ' + str(short_break_config)) 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']: exercise_name = long_break_config['name'] @@ -96,13 +98,14 @@ class SafeEyesCore: break_time = long_break_config.get('time', self.long_break_duration) audible_alert = long_break_config.get('audible_alert', config['audible_alert']) + image = long_break_config.get('image') # Validate time value if not isinstance(break_time, int) or break_time <= 0: logging.error('Invalid time in long break: ' + str(long_break_config)) 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 if self.__is_running(): message = "" + image = None seconds = 0 audible_alert = None if self.__is_long_break(): @@ -278,15 +282,17 @@ class SafeEyesCore: message = self.long_break_exercises[self.long_break_message_index][0] seconds = self.long_break_exercises[self.long_break_message_index][1] audible_alert = self.long_break_exercises[self.long_break_message_index][2] + image = self.long_break_exercises[self.long_break_message_index][3] else: 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) message = self.short_break_exercises[self.short_break_message_index][0] seconds = self.short_break_exercises[self.short_break_message_index][1] 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 - self.start_break(message) + self.start_break(message, image) # 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: diff --git a/safeeyes/Utility.py b/safeeyes/Utility.py index e57edfb..cabe621 100644 --- a/safeeyes/Utility.py +++ b/safeeyes/Utility.py @@ -25,9 +25,10 @@ import pyaudio, wave bin_directory = os.path.dirname(os.path.realpath(__file__)) home_directory = os.path.expanduser('~') 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(): logging.info("Playing audible alert") @@ -35,7 +36,9 @@ def play_notification(): try: # 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') # Create a sound stream @@ -61,6 +64,23 @@ def play_notification(): logging.warning('Unable to play audible alert') 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. 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): logging.info("Searching for full-screen application") 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: - 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: - screen.get_active_window().unfullscreen() - except: - logging.error('Error in unfullscreen the window ' + process) - pass - return False + active_window = screen.get_active_window() + if active_window: + active_xid = str(active_window.get_xid()) + cmdlist = ['xprop', '-root', '-notype','-id',active_xid, 'WM_CLASS', '_NET_WM_STATE'] - 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 """ diff --git a/safeeyes/__main__.py b/safeeyes/__main__.py index 92e0a15..73a3077 100755 --- a/safeeyes/__main__.py +++ b/safeeyes/__main__.py @@ -43,7 +43,7 @@ system_style_sheet_path = os.path.join(Utility.bin_directory, "config/style/safe is_active = True 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. @@ -72,10 +72,10 @@ def show_notification(): """ 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") 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: Utility.execute_main_thread(tray_icon.unlock_menu) @@ -258,7 +258,7 @@ def running(): try: # Check if safeeyes is in process arguments 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 if process_count > 1: return True diff --git a/safeeyes/config/safeeyes.json b/safeeyes/config/safeeyes.json index 561ada1..05603c9 100644 --- a/safeeyes/config/safeeyes.json +++ b/safeeyes/config/safeeyes.json @@ -40,33 +40,42 @@ ], "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": [ { - "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": { diff --git a/safeeyes/config/style/safeeyes_style.css b/safeeyes/config/style/safeeyes_style.css index b909f34..0ce8703 100644 --- a/safeeyes/config/style/safeeyes_style.css +++ b/safeeyes/config/style/safeeyes_style.css @@ -71,3 +71,9 @@ font-size: 12pt; color: white; } + +.panel_left { +} + +.panel_right { +} diff --git a/safeeyes/glade/break_screen.glade b/safeeyes/glade/break_screen.glade index 1d007ee..7e368df 100644 --- a/safeeyes/glade/break_screen.glade +++ b/safeeyes/glade/break_screen.glade @@ -1,6 +1,5 @@ - - @@ -33,120 +31,195 @@ False False False + center - + True False - center - center - 5 - True - + True False - Hello World + start + start + 10 + 10 + 10 - 0 - 0 - 3 + True + True + 0 - - True - False - 0.20000000298023224 - - - True - False - 00 - - - - - - 1 - 1 - - - - + True False center - 50 - True + center + 10 - - Postpone + True - True - True + False + center center - - + True + 15 + + + True + False + Hello World + center + + + + 0 + 0 + 3 + + + + + True + False + 0.20000000298023224 + + + True + False + 00 + + + + + + 1 + 1 + + + + + True + False + center + 50 + True + + + Postpone + True + True + True + center + + + + + True + True + 0 + + + + + Skip + True + True + False + center + + + + + True + True + 1 + + + + + 1 + 3 + + + + + + + + + + + + + + + + + + + + + + + - True - True - 0 + 0 + 1 + 3 - - Skip + True - True - False - center - - + False - True - True - 1 + 0 + 0 - 1 - 3 + False + True + 1 - - - - - - - - - - - - - - - - - - - + + True + False + end + start + 10 + 10 + 10 + + + + True + True + 2 + diff --git a/safeeyes/resource/long_break_lean_back.png b/safeeyes/resource/long_break_lean_back.png new file mode 100644 index 0000000000000000000000000000000000000000..ae5f5849975b968b352538642d94b491d6f93a04 GIT binary patch literal 1318 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&tqAZ5aRt)dCzYp7JwyES-}i zd9OazdO>eZeCTs$1_tJDo-U3d6}R5rzFBt5K!EMR{0)+4(xjUW1-?0WFni7_x%dBU zRo2SxP)oCw={qv^Nf#9zt{0f(rrhj5zoKIE^mlcZ$EMz~UUEfT?#WU9FVV&PnV;pB z-f;>mab{zm)Fk$IDYqkE^6I1GvO#t(W9;}VH50y%k5m&S{?gcEnwzb{(hJHgi`lRQSn7dMlBPzZt6L3X{OVeiI=Pzl&wTprJ1ff z-xaU8*sYCK?(6CvGs$NM)hcbBU1FAR(LHzawOT{R`ezeUTB9%e?o2$_DDkSFIb}w1 zg7}#wQ7PXnFE}`tIRA2LtInBxokPiHv-_Tle`j5jQhL|!)*vq9I$N0AOZwd3bD_^2 zPOnyd?Zj$yETzIFyo7JhM8!jHpHd#QKjU~1_ccRZKyY^P1L?j+r9UsNabu73-s1Q@ zg=NM1tGg~V_H^*{#fkS#*rTOP zE_SeJU^f+dtn+#VY&Nsd+Wy(>0VfkRX)Pkrpd7KYi&$k*eU(wIMcGiN) zWh>JajxRB741UXlTJrj*$UkrqpJBvu<09(;fd>^Q>wx5>yDNi}IUnpZsS7AQb+-61 zf1lOl*Z&Q(px8EY7CZA*S1w$@IGF^9YN9^-sfCYcg`EnTl`^8*wb z>Z?qpKeK%Z`F*j~b;Y_970f$Ms^sjwy!!QL zHi?&R8>a4)Pv9*`m-(aH{cw(H!@l0i_xIMEmT>5I5G`XjWt+p;w@Wy8`?4c5^&JHE z-LHDNT6Ust!@YueP2cAy6zpJnvfpt5^Cnh~V!I?;7rpR?Kl(HOO)6rwSm7^m^Gn=G zQHA=2oL`)d3MiznG@X_;kHPU@T5?{P3~yIm<9*4k5=>7-WZVuGrEyFME}E$tEgAK4 z{!1P%(YftCe?Q5~cUNl1m96IUv=rXxck2TC0}<>j6#qIJ@KWbW(_IIt@E@N)m7wd{?-1?wclUt9ndA`G6c KelF{r5}E+!J+V>% literal 0 HcmV?d00001 diff --git a/safeeyes/resource/long_break_walk.png b/safeeyes/resource/long_break_walk.png new file mode 100644 index 0000000000000000000000000000000000000000..7241e90adab8d61c718bc8fa430b36928b4c3433 GIT binary patch literal 1517 zcmdUv=Q|q+0LEih5N8uqDb?CZj9QJoBlf!J*|DN1HO}LjDxyY(S}kgpQX&0+^Nj1$rsz#(?@&Fs`yO3;=MxF>hAPM_&%4|m?CmEbcJ#|9rR z^#0vYkr?~CPD_xA%)~GjuE9g7DU5=UZu9>P2LRaHtj&yFf@z=U)*dfFggKU$@%(wj zG}#R{4$d1-T6iI4yT8@OIMoC>~Hv~#@9tB3(_em^_(J@+N{c6Y>33lC)hIR$K~Q3?&HFdRns1Iw5H5r z23T)p9lET)vitP)16FlJh-oT1(^@(o@)Xi&3WSD14+4z~$|RH6u6}Q6ts)cR&f$j(EX$85EGH+-eTnn3+HcdmGi$JA{vZYg2?$` z;Z@kCs$`z7HiX4+T!kbHZtPUirEMQYD@E_2xrA_>;BGHvSNnLlQO+!R8LA3i~ z>Fv!voLP2dsIP>`dYQ@edjb-91t5yq6LZ{7@A5qZlG| zo(sX?1>YcVY200d)x(UJ=FnDR@w9YkA|77*7&}WJ<*}IL z3>-UktCqh!`-qqL5s&3lHm8junK&Ya?I{Iot4nkqJ z&Nq=g)~t><({}zq)j-WYV#yU?D5LGYg?7hudE1c@Z%x>MDpQV7QRcRHfB~$w(1tL5-fRQD zeR{%fUsK^B&RPRb*0(KJ84!-vWQ}Bm)VdN^g|!dPjItb-!D@v}w|?lki=I1_%BJHt zMQpE|=ymA2(%V^YfvW^j`t8;TFLpYZaeneyj{0zKeP-i3|Hphf{Nn2nq3@#&#@%hI z&!OiNlYi!G_{Cws|Jr4;PBxOY7-~fZZ%9>kPq^-#N__6~#q*?M z*8@6zw@ONbaX}MA;`iJG_tZ1yW+_H@W6IS!_WY`9Vb`0|q%UHP;a($mAi**p3LII; zL`QnHp@{ZfxngA3Ko#*>P{_|}?m)Tj@rR53_ZbCw!?npJwiho5ur^1U5lnoN{{!Xz BG&2AI literal 0 HcmV?d00001 diff --git a/safeeyes/resource/short_break_blink.png b/safeeyes/resource/short_break_blink.png new file mode 100644 index 0000000000000000000000000000000000000000..e94f7fb8e975d185504ddb7a7eddb3889892a068 GIT binary patch literal 1775 zcmdUw`#%#31ICry&yvyV<+wCsNpg9a3TMV#hKaRGE}_VJH;hKPbs5gwXPB8O*|FTV z6z1q+EZ%=N3zZXDLUsFj*34roOVs=Q| z$t`u|opvrrv)(~dfFIUlhui-r8fdO(?#w+-!uX$7qB?y?5im!URaa(!^HW;uA7Qr| z^skN#4_?1wbEvcC@+;8zWBOr3VHgsk-gaKQ`AkkGJd#P1CI-rtlvJLgkO=Jcv4yeO zWVX#g)v0p`m0lG_LKi2P@gxNAG`ST%+W)mU3R9*8tcxicI;A)0Yod|9%dr9NIM<#2 ze(mSXpI*)_%HSicD}Zg0?8>Gzuwuh*o$^i1FB4pL@2mG7ju-f_3lK3&Q}>}F15zR6 zVN;#5HnYYX-{c&?iwk$Rw7(cYf>9wqCQT%zZG46I*MB_;J>ps9rTixgSIBRBr`--B zUSrv1*nti-bFOZP+u1Kxv11d-ulfPLSN zcy(JYO4T&1VSHm zkmBAd{;We;%|=HY7=7&^Umwl%G*}MO`?kyFLl)iFIjEm7^RRx;%gH`tD{@$sGU>4S z^HZ@flDwuTg!nZhFT`5Ski43tniIbd^4V353fDDzp_zd4?8HHdU5}o6lMBavCEJM- zSIOsy`wgm7DKjIB82dBqyb4{iRUMXU?P1f|59DQ9Wk8mV=J~_LB0mSy`OMC{f-r;5 zm~xShyKkes`G#zRwyW>csZCUYyY;l;8FqS`Y6rIG%hiVr%dwNjR*J4pn6y>FoFo3! z*AIz#H1d6T-PB`aXnAjA&&djhv>aaFeLjGMgN8K#O+ERl03;&5q|ImFF?LjFy{%tw zX^a!D37o=}_d0!wrJy}v_9>iO)Z?1p@i!@Z&4QwWme?TseeRg~+i9Yv1WR0R{$0a& zypJn&@8e`)&m+kp2c3s)o@A#o{ZhPHiK4ZqxM$%cY!=GihL0-ONS=cRD)|>+3W`DIQh9tj3C9 zS0C5DPSl}(DyZ<5rpS~dy@R| z7or0de~4Bo)yUJ@T1L9bUD%CbqvJW^RGudnAh?FY{W^5Vv1D1#7H}89j{9XX^ljmd zuH+1fd!4$*4IVXgEp36K#h(q`Qk@?x$iWD;wqU_8kC4v39f&GV3ABHuSpB)$`u9f& z|G)&6K9@EgkN`+PfcG1+g>?F71>*c(p8)NZ=?AVxGI(hcwq^m+=po1{;zgKpM(EAW z`Pl8uO|h9xB|>*~Y(q_Gxt~IlX%pHB6aV$eVbr5<(*TAOiZV(??rX?}mG?E>sjd5+ zXyQu#gWqT}sk==yo5-9}X5yEm`FBio!n27mQAI|rs}Txl)fIO#Y)%G-~c2mBLMFCDfKy>;*!}7M>&hgkHyD3t((G za=){sA#6qV2GV_Ebv9PmloSBLxCps4jV7^dGx!zqST&HQQN8<%H&Z=h`E2=ZP7vDr z&dduh>Y=!6dRoUCbi4YO@<-goQ}~)$-AHqzocPNQ=GDI^{kV@*malU4L3V+}4_W0W zb%MEIdf{xtvuC!nxsTeLGj6g!1r5K5&a5 F{{swOx6}Xt literal 0 HcmV?d00001 diff --git a/safeeyes/resource/short_break_close_eyes.png b/safeeyes/resource/short_break_close_eyes.png new file mode 100644 index 0000000000000000000000000000000000000000..8b08649c49fb4d7ed9fecb0f98b6b16c839d3ae6 GIT binary patch literal 1512 zcmdUv`8yK~0LPbv=6rHwYp!98dc77REM}LpH8gr7cdo`hQsC3mJbNpqAj|hB=_g%E+=b|6n@hvii69W%}}JO*>CXq^-V*# zEpHR+)=ysz0+4vtji6`---ZoRNy@!+&*qixv?q-=A8pB0;^s9>ckSDO4f5_~+I_Pg z2P?0q2YbRn`OkzY!C7I=LUs={4^%XZ2(&n+QE4DDIj7;O`EhD6nN0a^q2AF-`Rg)jGlv#JBzgNhslc5Is1#Qppunr?~T?mD5q3KZ*Slg zJ+Jd{$h9l?Hi_*P-1LtNG-g6EjT$Td4O|~)=dc?c#j-`T({G}qe(y+J_IGrnpeX6X zydMie1}^STfQb8|)YS8x%`5@dV6<$wt3L)p1rF)k5g*TGx}6}g92V-?P>jXkiwoLZrs<%*JxJ!!GImh9p0Gu>V?;~0y! zz`AZPX9(6h7u+8|D(_If+Lr+<*w)r9nulz7!50#yJI}<84p{|P`&DjuHzf&9QxQX1 zTe@qnxi}{MDZjCjDd92B<@-UIGt!=DI1K}IXQsgPLLXd%2~-WOc_<#}rSEx9A2un= zSh2yAWZej8c>?vKli3|J*&{s_xcqh`D><$w@r>XQ=RJ^ zRX(o$q&u(yFTc82<9lwd-odW|zWr8P(?B2Tdq?M2QG7;4Rf8`ca0-p0Tt~1PmOpzW zlP@8V!|xYLYXFWzl2d+G=;irxIka+Hv)h8OQp{&JxewpkH>&y)w*^MTL$PwDQYUjCpVSTikf7xNES-B7@V483=T`hvlB_(| zs<3djmZp*8-Y1tb+3xRt*{ZNYSlg$o9(HWDc0yjH9(gArWHdCf$as@Ow*-9+wWv;3 zoaD7PLZ-S1Vx>-z&}sm`m?G(a{CJ4r)Mb+9&8pdVd$FrjUNaXf2`>PXolyoUzbNLs zJcSp%brmPGE!yxpKl-&TAJOBan^f>$`U^lPXMI{;I&L0b=yq&!b&j{@Xt4|z8iGM_K(-+NK k<@R|&?z#VoX8-DYK+FpcE{h-+a9}@xJ;nviLHVTq4ZqDTMgRZ+ literal 0 HcmV?d00001 diff --git a/safeeyes/resource/short_break_drink_water.png b/safeeyes/resource/short_break_drink_water.png new file mode 100644 index 0000000000000000000000000000000000000000..0e7331216728f5d3b8b2ff039fb3c78776219f9e GIT binary patch literal 1364 zcmeAS@N?(olHy`uVBq!ia0vp^nLymY!3-qr#kV^FsTBb}A+A9B|4;zR*KKowZZ$6n z@(X6*GYb(DD$hXUVWnf@Y@pN$v$+)-W#?48K3?$kf`nNWiT;foAC;e#Ome=L~%gweK zf3wheub2`38!H1yWOVjKUKec2z^7tOe?dEM>!i6e2#_?3&A%UO?e zD)-nmSPJjdy0^G2M(u}RrO|YiHlMU94I2;c;AhPbn5Mhzqez0Jzv=gi!-pO-r#nO+ zabn!pyT9Q<$f4{q)9(H?EDZ;q?saCqZ??=*YCG@cW5u)Tp9oLu;@+DswO#%!Ys0Yz zYy3_opHym(GQ99{!sX}1cI*E5E{|DVdHG%>gMe(&-ep_=e!9W;D&>FRtJU|OaChwp z{QJTC&rh2PrFZW!KAv>h;N-R@lUoN=;&qSqY6Xkl*lyi=KRG4J!N@Rca{lyRYnWGX z?RXNo-8*sn>-Mi(nsVWpYUsT(D%q9MGu`I) z0$-_yx0BYMSoCM5Yns+VgP=yeY~Bnhu@rd?laurMtXoY_=BVc8zZT%CkqtjOH$HFP z-(L$FtRFKQPw#r-x#q@k{{EDX`X22waZ(dy=d0vxQS)T~euY_e%8?D>qOo}&lAWhr zJ>lx%#woSv*tWjv)>%`6BIG&l&f{Lft+6yj-P^jTp~Tp7sp88UDf}<(o=(~FQ*Kec zh)4P-S7GIaOEi1$i)>w`;iO@|%KVkQ!H-$a7rLfydHd?{=RN1;oaFs>SO1bpPCHc>zh~%Ny7tZ`tt%6PW_jvJZk27=aw#D0W%0s? zjwdIEX6cG5tbOuek@^wIP%Xb_389S!lgxX>r#I%{Sg-G#89W*KB*%w)R!T{ z=gIU1)fXF=G8V-AR9z8wNq66qQ~C${m#y1AEtgN#;_Z^_KN9&=d+N3(tMBGwaujJh zzqd(YTbEm5kF%4DOy|D;yEWZ>FCV?M+3@Rn>z`u26a6HgS1|VrHMQLNx!&zxaTDjH zh<9s0uDGw{@vdl9uTlN4T27Ber~W*ena&YZYP$c&8WoP9j9|mKqj%qXf9StvT5Bbs zBC_{;@-4f4Y91e^Jw2W##`-3Vg;V0m?>|yq+$tVhO{;Z0gNi4qXs4-oezRBg+_WVy zc<&Jp)u|yto^WENkLuLA$gHMIVP1+FysRl6K}t(>7?wtOq!)Zj=SvBiaLYP~DXcz{ z^;N}!&^tOAQ<@_Ff00|2Va~WUq3EQ~mfYN9j2U+sId?|9T`S(W4Orwbc)I$ztaD0e F0ss!b(f|Me literal 0 HcmV?d00001 diff --git a/safeeyes/resource/short_break_focus_far_distance.png b/safeeyes/resource/short_break_focus_far_distance.png new file mode 100644 index 0000000000000000000000000000000000000000..f6502f3b900c7e666c497e18f0d8bfb058018051 GIT binary patch literal 1899 zcmdUw`#%#31I8zrjd7aG+^2{QN3z9|Im9N8>mhPNnp?y+<*vnOF3BaM*DW0MO1+!h zMJ%Hs5nIMwb1SUlkXH_p9q*sPv+7sGW=Sr)1l`QVgfylBo$`>d)+Z6) zmJLIW?Ve0G&K!kEIbZL%^B%y-_>3&@c;St7j8x_0h1}-*{&BOTifcb3;8Fp6Uun(l zB%-xry4~@+3QO@qf|AnWpLHwwBn;_VE7b6O*B$KC7N&(|DMe5YRg@854lt2^RcCbW z5I#qGi|F%>hn2_WyFQEH*}@sApV5j};=>b@VP2S?sGX{hz}vI5E0mFOo2rddlGW4W zX&b+584XhrliLq-qq7mlx4>o~7?v;7xH5FkFTViPG2r76mmr3+`5beQ0Yy!?QHIar z#V5l$p-BnoU|E#uqr!ZTy*I(u*TxXpVvU!tI@y}AYKq2{+sZn-vw*!=_GsOFKK^=E1SC;GiN`e=jF<>fAnw_GEoN9A-@)Yz^JE zZuq(8`EF^_fs(53_HR1YCBy4*6;O0%?KjoHS-l|s7Z171#=&u9;(mpQQpMHDejw83J=TV0G%z**huXNp9H(VwR*h!PqhJVblN#=By)YIRFMT88o`5};RZh0VqNYC=|l zWWUUTX$5ii=Wmejq-&t^mNL(tqnA`thZ1yqM^gknl9q~u2!uy>Iv1hrlkc$0FX$fq z3!13J9-Xl`M5YnmsvS(q0{OMO|51bta6Y=KNdQsM&9f2N%Ts@vr`0v!a;fPHi7PI6OJG43#zfw zZ2dRP-$BT1_+DGz)(`TQ%lgrg3`f>gXV)7tu_L&7r^gj91XBa`&QhCgOboCU#>@x2 z)ZQV`m|&q$_F%0+-apLE^7ZP#&aqa6M&LXNRw`h#c*(DDoHNZXS`oVAg8QDgTS4QQ45)wMg4~wnSS~!&>X0{SNP6pJfi&-sf#;4}#1gd-edj|l#CXz|2a%pD=GqDm z<)1;WQhKWe$%P8vh(X2L%LAQr6&qd=F-tcu)VLUe2R^L+kujoa&b+<FJZg2|4PjZ8bNRxdtA7(TL5?ENNvI%=tutE0|64i`(w7p|Ko));j% zygb_j)`v4>7S@4;Wdzgc6+_U(nGF&PNV;(|t@8Q8L`ETMYzvz{4k~3==?UpTukU4{ zH?Jz-({8obhAV4etWlouD4URO(`vDaM@=fZhJAnkupn|n`U^qf9PFBw)!%@a<6j#( zm@mvsf;@AY2;yA01J@s-^t!y)K=6giBF?z08x=<1#lmINpX-yqPzK-Aau&AMk;(_m z8Fy+z`*m2{imrp)MDzpq5?;YlWry$6_kCNIIg3!XJ}VZKMdKZgcY6z*id=#d>IDgN z2vxdHvvzhUuKoRFFy%5~pT6|aKQ@guC@sQNR`&!8&&W*Y^Sxctc Sr|XYC2w;zQ#?@J0Px%K9M&ld+ literal 0 HcmV?d00001 diff --git a/safeeyes/resource/short_break_roll_eyes.png b/safeeyes/resource/short_break_roll_eyes.png new file mode 100644 index 0000000000000000000000000000000000000000..67bc68d4743a84092f45c0f636d0350dc094ef71 GIT binary patch literal 975 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&O%Cu0aRt)<2N}3%tr`b(mSIVd zUogYJA9Air%y!n&5;~e3*NsI4`Cj*j26|c;sLn|Gym$NNuG-uAX_3B9ADlk2w)y<> zvXX0Qrx+NRc6quuhE&{od*gN9BLe}}2a@eAF^w!UGnd|A zi;nskymZT7Bn}kH9EYIFwqp4=PBpd-zBeTHvnKGyb$YT)dvI2bReQpkUkQx& zs_!;?ia0#z{lRc}!^LwS8lD?!SI2(MG)s+WUvS*r(&q5%D;W&4e2@J;!=lUQ|K`ZOd?dZ1A%j4g4jt4}>>Y+>J3u|WBQm9*mex}CiT zW&T{RkZOqS4Da6W}G!f|$=>J5wbg!7a4&HV8E!bO{=DV=aSsUgGgz;U6AaSb)yEIr=)1a#Rr{7s@~@E6EzX_Mv?*z?zE-G|zGv)M~C zCtdkkV6kSE!5j0c-c2@g{g+>!zM3fbqW3UPlzod%PF-cc=d#4vY6GUsn|VF=g*H8I^=e2ucWh&*!snYNY??2{eN%Zogg!A= zrKgl=cP!XE{r0@;bq^w$i$p6fMDCNm%$?JJF@n|Z#EiO4oV!GSC@hbA7{7;UYp6l6 zMG5ydRxLphhoEm|w>u+5esTo`7}cip#++Q=6!QG+gtW~c=c+Eyxwk1=%Q7~A!FB)i z9P0_8&GQaFFE3yC#+3bl!J1XiY$C3EPFnIZ=h^Rl&s_s$`rRtyL|mt(Itnywdva*| zBAFhoXC`x*B&J?`7<0k!_?k~GDJ*7JviEcz?8{KnXplWv*VD_PGmB57LG^Gid*5RV d2>QR!o*_o{z*eoPbAefa!PC{xWt~$(69E4x1X=(9 literal 0 HcmV?d00001 diff --git a/safeeyes/resource/short_break_rotate_clockwise.png b/safeeyes/resource/short_break_rotate_clockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..53dea906cb488e38c324545b53b95c8b6b2fcdb5 GIT binary patch literal 1373 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&tq$-BaRt)ke#K0nb1h1O z{DK*{zp=A?`lT=Y`kB`|iF2-xu5AgucQSqbaud}(ZT?Dgd#X#VF6~&@sLg+1$>gMX zd$}3WM+$GQ&N)+cpooEiMbp#8F{I+w+nLv!W*Z2&x|$vpNxoD4K5qT?C;$FWZ_PL| zt7XFU4hw!O{*2Tan=>R%E6i|O7@#4-)e56N?tkpie?M)vhw;1=iOzz5IqbOIhu+v&?1w6n3y5f2t*a^s1|Mc9z)|VOjgfXTy3v ztbL}E{mO)8uiB}KbT=0d>B!*uibaYW`fle6DW}T@B*tDS-n32c@KHAR`|pkxh{<)R z&E5X3EAQHslpRqbceq~!95;$;Ms@kC(p-U=YdcpEB7zG|4ZB- zhVQy-mm00vmdT}}^SJl@-HbI2mUCwHYoF!caQseA>&>vXMy>DDY8}i4YI{RBvdA7h z>>L*^#FM79Z#H9XX8P5K;RS9rljL6l6+UG1_Sf}J4xXj8{pv$;!_^-@^v=yqm6$d+ z`%b^r`=3AS|0y;u{X9MK$FjGV{grnZE1&UR(Pe3|{J7|pWQld|Hcvb*&j^;B7r^^o z&Q#~PfQ+qN-L|79L9MaB#Jr7i1UK|ppH2R}DnnjmU;UZnNj%(lDovxOI4)MIVL7+U zV6wN-k85iRT36*ou$x(QAIhJp()>A5)cDGuT2XmN8Ls`$Hu0zjm>pU_bCb_?ru4WS z^CzZ#c*~m~?=rDQ+kJhI;ST4AeSY&6wle0&A2s|QzgSvUVEQB5wKmStCHYFN?{=T; z^0}t)>*(&PnQZ~{1GthW9!~jsqtbe}g4N|m#l1d<85TFVU1E7Ct6t+}8n#@bX-iYPO_O4&%mpOI4+F_P&H#9`d+C$eDpEBnOYGGbnwKV*UvZ>UZ$$gQJKRn<1u8Mau z%ZvNc&(^R0d-8f#{av>7OWP-YE1P`v?1T5#VN1WumP+&e{;s@WccwPaVX?g*Uv`<> z{XdZ)ke#K0nb1h1O z{DK*{zp=A?`lT=Y`kB`|iF2-xu5AgucQSqbaud}(ZT?Dgd#X#VF6~&@sLg+1$>gMX zd$}3WM+$GQ&N)+cpooEi#mv*iF{I+w+nG0;W*LaI9oE~S_^9poj`yc6E`R>#e{Gh% zUykNm*8|^_?)Rpg`KdNX#>rvX#S9avUbn?C>d*WLw_4LWeqXJ>+|3hZl$16L)t$+f zW{b<%@M=0&Yq#g;DH1$;t`xko+LG}-&6l$$Zi&g075Z^OJSF4u(F2ts94x_x z558Vf>8nmyz~Fo}XN_THqr{sQ!_^^1A9C55*j2+?5&Ca;k!7u;U_x-(@ za!2Ht*|*Hjh@Ab0C(hvc{ME}J8qa50yM7htgaAR41g28w%SRHK|Gr){i$!=gC)9_r#MiJk~jL`#Ihl1Vr7vo%}d+YyZ#C z(ud`CKd0_m`0PV-74{d%ty7 zTe3Ibe15uA{^9XA9}L$$UbW+%@|?N54#r0Ktcz)X^FejmO+jNBbu(F~(v?zAe*B+x z;Qa9)`D?4pZMIzqy{M30(Eh=*><5!^#>VWczk048GiRH^v#Bk7UBt0X8i|Uxi$d%B zQeCc2*w=OUOacG>_mveJj@tU&RJguz>&(jCEW&G&!}lFuq{@7XZRe!(g7?mMY@1-; z9{x`B`+XapPjP0hJ8qx8_08_=2Z{3vl@d?x);K5p{>`<{=K1xX8iotfv`JaqTW zqtnj|qy<)(T-@ouD7l-P zAQ*0vvj2WW>y%}iOkCXWJG!Sjf0LHWm%n`e$KCbsrFJ~vGI$^>{$i`RXjQ^Ke}6XS zuWV*EmC~2;e|jyxnfphwblT;rpuekTI5%7rPdQNS*LikgZuSGAS35KgzwpnmS{v7! zbfvJc?M?Yz>3pAOeD_5%4&46t;`;W#+sfYxxq8GMWcyitT*vU;+0QvUULAQ-)-Lgf zo5$_aF@3w