|
|
|
@ -20,14 +20,7 @@ import datetime
|
|
|
|
|
from safeeyes.model import BreakType
|
|
|
|
|
import gi
|
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
|
|
|
try:
|
|
|
|
|
gi.require_version('AppIndicator3', '0.1')
|
|
|
|
|
from gi.repository import AppIndicator3 as appindicator
|
|
|
|
|
except:
|
|
|
|
|
#fall back to Ayatana
|
|
|
|
|
gi.require_version('AyatanaAppIndicator3', '0.1')
|
|
|
|
|
from gi.repository import AyatanaAppIndicator3 as appindicator
|
|
|
|
|
from gi.repository import Gtk
|
|
|
|
|
from gi.repository import Gio, GLib
|
|
|
|
|
import logging
|
|
|
|
|
from safeeyes import utility
|
|
|
|
|
import threading
|
|
|
|
@ -37,11 +30,291 @@ import time
|
|
|
|
|
Safe Eyes tray icon plugin
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
APPINDICATOR_ID = 'safeeyes_2'
|
|
|
|
|
context = None
|
|
|
|
|
tray_icon = None
|
|
|
|
|
safeeyes_config = None
|
|
|
|
|
|
|
|
|
|
SNI_NODE_INFO = Gio.DBusNodeInfo.new_for_xml("""
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<node>
|
|
|
|
|
<interface name="org.kde.StatusNotifierItem">
|
|
|
|
|
<property name="Category" type="s" access="read"/>
|
|
|
|
|
<property name="Id" type="s" access="read"/>
|
|
|
|
|
<property name="Title" type="s" access="read"/>
|
|
|
|
|
<property name="ToolTip" type="(sa(iiay)ss)" access="read"/>
|
|
|
|
|
<property name="Menu" type="o" access="read"/>
|
|
|
|
|
<property name="ItemIsMenu" type="b" access="read"/>
|
|
|
|
|
<property name="IconName" type="s" access="read"/>
|
|
|
|
|
<property name="IconThemePath" type="s" access="read"/>
|
|
|
|
|
<property name="Status" type="s" access="read"/>
|
|
|
|
|
<signal name="NewIcon"/>
|
|
|
|
|
<signal name="NewTooltip"/>
|
|
|
|
|
</interface>
|
|
|
|
|
</node>""").interfaces[0]
|
|
|
|
|
|
|
|
|
|
MENU_NODE_INFO = Gio.DBusNodeInfo.new_for_xml("""
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<node>
|
|
|
|
|
<interface name="com.canonical.dbusmenu">
|
|
|
|
|
<method name="GetLayout">
|
|
|
|
|
<arg type="i" direction="in"/>
|
|
|
|
|
<arg type="i" direction="in"/>
|
|
|
|
|
<arg type="as" direction="in"/>
|
|
|
|
|
<arg type="u" direction="out"/>
|
|
|
|
|
<arg type="(ia{sv}av)" direction="out"/>
|
|
|
|
|
</method>
|
|
|
|
|
<method name="Event">
|
|
|
|
|
<arg type="i" direction="in"/>
|
|
|
|
|
<arg type="s" direction="in"/>
|
|
|
|
|
<arg type="v" direction="in"/>
|
|
|
|
|
<arg type="u" direction="in"/>
|
|
|
|
|
</method>
|
|
|
|
|
<method name="AboutToShow">
|
|
|
|
|
<arg type="i" direction="in"/>
|
|
|
|
|
<arg type="b" direction="out"/>
|
|
|
|
|
</method>
|
|
|
|
|
<signal name="LayoutUpdated">
|
|
|
|
|
<arg type="u"/>
|
|
|
|
|
<arg type="i"/>
|
|
|
|
|
</signal>
|
|
|
|
|
</interface>
|
|
|
|
|
</node>""").interfaces[0]
|
|
|
|
|
|
|
|
|
|
class DBusService:
|
|
|
|
|
def __init__(self, interface_info, object_path, bus):
|
|
|
|
|
self.interface_info = interface_info
|
|
|
|
|
self.bus = bus
|
|
|
|
|
self.object_path = object_path
|
|
|
|
|
self.registration_id = None
|
|
|
|
|
|
|
|
|
|
def register(self):
|
|
|
|
|
self.registration_id = self.bus.register_object(
|
|
|
|
|
object_path=self.object_path,
|
|
|
|
|
interface_info=self.interface_info,
|
|
|
|
|
method_call_closure=self.on_method_call,
|
|
|
|
|
get_property_closure=self.on_get_property
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not self.registration_id:
|
|
|
|
|
raise GLib.Error(f"Failed to register object with path {self.object_path}")
|
|
|
|
|
|
|
|
|
|
self.interface_info.cache_build()
|
|
|
|
|
|
|
|
|
|
def unregister(self):
|
|
|
|
|
self.interface_info.cache_release()
|
|
|
|
|
|
|
|
|
|
if self.registration_id is not None:
|
|
|
|
|
self.bus.unregister_object(self.registration_id)
|
|
|
|
|
self.registration_id = None
|
|
|
|
|
|
|
|
|
|
def on_method_call(self, _connection, _sender, _path, _interface_name, method_name, parameters, invocation):
|
|
|
|
|
method_info = self.interface_info.lookup_method(method_name)
|
|
|
|
|
method = getattr(self, method_name)
|
|
|
|
|
result = method(*parameters.unpack())
|
|
|
|
|
out_arg_types = "".join([arg.signature for arg in method_info.out_args])
|
|
|
|
|
return_value = None
|
|
|
|
|
|
|
|
|
|
if method_info.out_args:
|
|
|
|
|
return_value = GLib.Variant(f"({out_arg_types})", result)
|
|
|
|
|
|
|
|
|
|
invocation.return_value(return_value)
|
|
|
|
|
|
|
|
|
|
def on_get_property(self, _connection, _sender, _path, _interface_name, property_name):
|
|
|
|
|
property_info = self.interface_info.lookup_property(property_name)
|
|
|
|
|
return GLib.Variant(property_info.signature, getattr(self, property_name))
|
|
|
|
|
|
|
|
|
|
def emit_signal(self, signal_name, args = None):
|
|
|
|
|
signal_info = self.interface_info.lookup_signal(signal_name)
|
|
|
|
|
if len(signal_info.args) == 0:
|
|
|
|
|
parameters = None
|
|
|
|
|
else:
|
|
|
|
|
arg_types = "".join([arg.signature for arg in signal_info.args])
|
|
|
|
|
parameters = GLib.Variant(f"({arg_types})", args)
|
|
|
|
|
self.bus.emit_signal(
|
|
|
|
|
destination_bus_name=None,
|
|
|
|
|
object_path=self.object_path,
|
|
|
|
|
interface_name=self.interface_info.name,
|
|
|
|
|
signal_name=signal_name,
|
|
|
|
|
parameters=parameters
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class DBusMenuService(DBusService):
|
|
|
|
|
DBUS_SERVICE_PATH = '/io/github/slgobinath/SafeEyes/Menu'
|
|
|
|
|
|
|
|
|
|
revision = 0
|
|
|
|
|
|
|
|
|
|
items = []
|
|
|
|
|
idToCallback = {}
|
|
|
|
|
|
|
|
|
|
def __init__(self, session_bus, context, items):
|
|
|
|
|
super().__init__(
|
|
|
|
|
interface_info=MENU_NODE_INFO,
|
|
|
|
|
object_path=self.DBUS_SERVICE_PATH,
|
|
|
|
|
bus=session_bus
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.set_items(items)
|
|
|
|
|
|
|
|
|
|
def set_items(self, items):
|
|
|
|
|
self.items = items
|
|
|
|
|
|
|
|
|
|
self.idToCallback = self.getItemCallbacks(items, {})
|
|
|
|
|
|
|
|
|
|
self.revision += 1
|
|
|
|
|
|
|
|
|
|
self.LayoutUpdated(self.revision, 0)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def getItemCallbacks(items, idToCallback):
|
|
|
|
|
for item in items:
|
|
|
|
|
if item.get('hidden', False) == True:
|
|
|
|
|
continue
|
|
|
|
|
if 'callback' in item:
|
|
|
|
|
idToCallback[item['id']] = item['callback']
|
|
|
|
|
if 'children' in item:
|
|
|
|
|
idToCallback = DBusMenuService.getItemCallbacks(item['children'], idToCallback)
|
|
|
|
|
|
|
|
|
|
return idToCallback
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def itemToDbus(item, recursion_depth):
|
|
|
|
|
result = {}
|
|
|
|
|
|
|
|
|
|
if item.get('hidden', False) == True:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
string_props = ['label', 'icon-name', 'type', 'children-display']
|
|
|
|
|
for key in string_props:
|
|
|
|
|
if key in item:
|
|
|
|
|
result[key] = GLib.Variant('s', item[key])
|
|
|
|
|
|
|
|
|
|
bool_props = ['enabled']
|
|
|
|
|
for key in bool_props:
|
|
|
|
|
if key in item:
|
|
|
|
|
result[key] = GLib.Variant('b', item[key])
|
|
|
|
|
|
|
|
|
|
children = []
|
|
|
|
|
if recursion_depth > 1 or recursion_depth == -1:
|
|
|
|
|
if "children" in item:
|
|
|
|
|
children = [DBusMenuService.itemToDbus(item, recursion_depth - 1) for item in item['children']]
|
|
|
|
|
children = [i for i in children if i is not None]
|
|
|
|
|
|
|
|
|
|
return GLib.Variant("(ia{sv}av)", (item['id'], result, children))
|
|
|
|
|
|
|
|
|
|
def findItemsWithParent(self, parent_id, items):
|
|
|
|
|
for item in items:
|
|
|
|
|
if item.get('hidden', False) == True:
|
|
|
|
|
continue
|
|
|
|
|
if 'children' in item:
|
|
|
|
|
if item['id'] == parent_id:
|
|
|
|
|
return item['children']
|
|
|
|
|
else:
|
|
|
|
|
ret = self.findItemsWithParent(parent_id, item['children'])
|
|
|
|
|
if ret is not None:
|
|
|
|
|
return ret
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def GetLayout(self, parent_id, recursion_depth, property_names):
|
|
|
|
|
children = []
|
|
|
|
|
|
|
|
|
|
if parent_id == 0:
|
|
|
|
|
children = self.items
|
|
|
|
|
else:
|
|
|
|
|
children = self.findItemsWithParent(parent_id, self.items)
|
|
|
|
|
if children is None:
|
|
|
|
|
children = []
|
|
|
|
|
|
|
|
|
|
children = [self.itemToDbus(item, recursion_depth) for item in children]
|
|
|
|
|
children = [i for i in children if i is not None]
|
|
|
|
|
|
|
|
|
|
ret = (
|
|
|
|
|
self.revision,
|
|
|
|
|
(
|
|
|
|
|
0,
|
|
|
|
|
{ 'children-display': GLib.Variant('s', 'submenu') },
|
|
|
|
|
children
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
def Event(self, idx, event_id, data, timestamp):
|
|
|
|
|
if event_id != "clicked":
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if idx in self.idToCallback:
|
|
|
|
|
self.idToCallback[idx]()
|
|
|
|
|
|
|
|
|
|
def AboutToShow(self, item_id):
|
|
|
|
|
return (False,)
|
|
|
|
|
|
|
|
|
|
def LayoutUpdated(self, revision, parent):
|
|
|
|
|
self.emit_signal(
|
|
|
|
|
'LayoutUpdated',
|
|
|
|
|
(revision, parent)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class StatusNotifierItemService(DBusService):
|
|
|
|
|
DBUS_SERVICE_PATH = '/io/github/slgobinath/SafeEyes'
|
|
|
|
|
|
|
|
|
|
Category = 'ApplicationStatus'
|
|
|
|
|
Id = 'io.github.slgobinath.SafeEyes'
|
|
|
|
|
Title = _('Safe Eyes')
|
|
|
|
|
Status = 'Active'
|
|
|
|
|
IconName = 'io.github.slgobinath.SafeEyes-enabled'
|
|
|
|
|
IconThemePath = ''
|
|
|
|
|
ToolTip = ('', [], _('Safe Eyes'), '')
|
|
|
|
|
ItemIsMenu = True
|
|
|
|
|
Menu = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, session_bus, context, menu_items):
|
|
|
|
|
super().__init__(
|
|
|
|
|
interface_info=SNI_NODE_INFO,
|
|
|
|
|
object_path=self.DBUS_SERVICE_PATH,
|
|
|
|
|
bus=session_bus
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.bus = session_bus
|
|
|
|
|
|
|
|
|
|
self._menu = DBusMenuService(session_bus, context, menu_items)
|
|
|
|
|
self.Menu = self._menu.DBUS_SERVICE_PATH
|
|
|
|
|
|
|
|
|
|
def register(self):
|
|
|
|
|
self._menu.register()
|
|
|
|
|
super().register()
|
|
|
|
|
|
|
|
|
|
watcher = Gio.DBusProxy.new_sync(
|
|
|
|
|
connection=self.bus,
|
|
|
|
|
flags=Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES,
|
|
|
|
|
info=None,
|
|
|
|
|
name='org.kde.StatusNotifierWatcher',
|
|
|
|
|
object_path='/StatusNotifierWatcher',
|
|
|
|
|
interface_name='org.kde.StatusNotifierWatcher',
|
|
|
|
|
cancellable=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
watcher.RegisterStatusNotifierItem('(s)', self.DBUS_SERVICE_PATH)
|
|
|
|
|
|
|
|
|
|
def unregister(self):
|
|
|
|
|
super().unregister()
|
|
|
|
|
self._menu.unregister()
|
|
|
|
|
|
|
|
|
|
def set_items(self, items):
|
|
|
|
|
self._menu.set_items(items)
|
|
|
|
|
|
|
|
|
|
def set_icon(self, icon):
|
|
|
|
|
self.IconName = icon
|
|
|
|
|
|
|
|
|
|
self.emit_signal(
|
|
|
|
|
'NewIcon'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def set_tooltip(self, title, description):
|
|
|
|
|
self.ToolTip = ('', [], title, description)
|
|
|
|
|
|
|
|
|
|
self.emit_signal(
|
|
|
|
|
'NewTooltip'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class TrayIcon:
|
|
|
|
|
"""
|
|
|
|
@ -66,185 +339,185 @@ class TrayIcon:
|
|
|
|
|
self.lock = threading.Lock()
|
|
|
|
|
self.allow_disabling = plugin_config['allow_disabling']
|
|
|
|
|
self.animate = False
|
|
|
|
|
self.menu_locked = False
|
|
|
|
|
|
|
|
|
|
# Construct the tray icon
|
|
|
|
|
self.indicator = appindicator.Indicator.new(
|
|
|
|
|
APPINDICATOR_ID, "io.github.slgobinath.SafeEyes-enabled", appindicator.IndicatorCategory.APPLICATION_STATUS)
|
|
|
|
|
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
|
|
|
|
|
session_bus = Gio.bus_get_sync(Gio.BusType.SESSION)
|
|
|
|
|
|
|
|
|
|
# Construct the context menu
|
|
|
|
|
self.menu = Gtk.Menu()
|
|
|
|
|
self.sni_service = StatusNotifierItemService(
|
|
|
|
|
session_bus,
|
|
|
|
|
context,
|
|
|
|
|
menu_items = self.get_items()
|
|
|
|
|
)
|
|
|
|
|
self.sni_service.register()
|
|
|
|
|
|
|
|
|
|
# Next break info menu item
|
|
|
|
|
self.item_info = Gtk.ImageMenuItem()
|
|
|
|
|
img_timer = Gtk.Image()
|
|
|
|
|
img_timer.set_from_icon_name("io.github.slgobinath.SafeEyes-timer", 16)
|
|
|
|
|
self.item_info.set_image(img_timer)
|
|
|
|
|
|
|
|
|
|
self.item_separator = Gtk.SeparatorMenuItem()
|
|
|
|
|
|
|
|
|
|
self.item_enable = Gtk.MenuItem()
|
|
|
|
|
self.item_enable.connect('activate', self.on_enable_clicked)
|
|
|
|
|
|
|
|
|
|
self.item_disable = Gtk.MenuItem()
|
|
|
|
|
self.sub_menu_disable = Gtk.Menu()
|
|
|
|
|
self.sub_menu_disable_items = []
|
|
|
|
|
|
|
|
|
|
# Read disable options and build the sub menu
|
|
|
|
|
for disable_option in plugin_config['disable_options']:
|
|
|
|
|
time_in_minutes = disable_option['time']
|
|
|
|
|
label = []
|
|
|
|
|
# Validate time value
|
|
|
|
|
if not isinstance(time_in_minutes, int) or time_in_minutes <= 0:
|
|
|
|
|
logging.error('Invalid time in disable option: ' + str(time_in_minutes))
|
|
|
|
|
continue
|
|
|
|
|
time_unit = disable_option['unit'].lower()
|
|
|
|
|
if time_unit == 'seconds' or time_unit == 'second':
|
|
|
|
|
time_in_minutes = int(time_in_minutes / 60)
|
|
|
|
|
label = ['For %d Second', 'For %d Seconds']
|
|
|
|
|
elif time_unit == 'minutes' or time_unit == 'minute':
|
|
|
|
|
time_in_minutes = int(time_in_minutes * 1)
|
|
|
|
|
label = ['For %d Minute', 'For %d Minutes']
|
|
|
|
|
elif time_unit == 'hours' or time_unit == 'hour':
|
|
|
|
|
time_in_minutes = int(time_in_minutes * 60)
|
|
|
|
|
label = ['For %d Hour', 'For %d Hours']
|
|
|
|
|
else:
|
|
|
|
|
# Invalid unit
|
|
|
|
|
logging.error('Invalid unit in disable option: ' + str(disable_option))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Create submenu
|
|
|
|
|
sub_menu_item = Gtk.MenuItem()
|
|
|
|
|
sub_menu_item.connect('activate', self.on_disable_clicked, time_in_minutes)
|
|
|
|
|
self.sub_menu_disable_items.append([sub_menu_item, label, disable_option['time']])
|
|
|
|
|
self.sub_menu_disable.append(sub_menu_item)
|
|
|
|
|
|
|
|
|
|
# Disable until restart submenu
|
|
|
|
|
self.sub_menu_item_until_restart = Gtk.MenuItem()
|
|
|
|
|
self.sub_menu_item_until_restart.connect('activate', self.on_disable_clicked, -1)
|
|
|
|
|
self.sub_menu_disable.append(self.sub_menu_item_until_restart)
|
|
|
|
|
|
|
|
|
|
# Add the sub menu to the enable/disable menu
|
|
|
|
|
self.item_disable.set_submenu(self.sub_menu_disable)
|
|
|
|
|
|
|
|
|
|
# Manual break menu item
|
|
|
|
|
self.item_manual_break = Gtk.MenuItem()
|
|
|
|
|
|
|
|
|
|
self.sub_menu_manual_next_break = Gtk.MenuItem()
|
|
|
|
|
self.sub_menu_manual_next_break.connect('activate', self.on_manual_break_clicked, None)
|
|
|
|
|
self.sub_menu_manual_next_short_break = Gtk.MenuItem()
|
|
|
|
|
self.sub_menu_manual_next_short_break.connect('activate', self.on_manual_break_clicked, BreakType.SHORT_BREAK)
|
|
|
|
|
self.sub_menu_manual_next_long_break = Gtk.MenuItem()
|
|
|
|
|
self.sub_menu_manual_next_long_break.connect('activate', self.on_manual_break_clicked, BreakType.LONG_BREAK)
|
|
|
|
|
|
|
|
|
|
self.sub_menu_manual_break = Gtk.Menu()
|
|
|
|
|
self.sub_menu_manual_break.append(self.sub_menu_manual_next_break)
|
|
|
|
|
self.sub_menu_manual_break.append(self.sub_menu_manual_next_short_break)
|
|
|
|
|
self.sub_menu_manual_break.append(self.sub_menu_manual_next_long_break)
|
|
|
|
|
self.item_manual_break.set_submenu(self.sub_menu_manual_break)
|
|
|
|
|
|
|
|
|
|
# Settings menu item
|
|
|
|
|
self.item_settings = Gtk.MenuItem()
|
|
|
|
|
self.item_settings.connect('activate', self.show_settings)
|
|
|
|
|
|
|
|
|
|
# About menu item
|
|
|
|
|
self.item_about = Gtk.MenuItem()
|
|
|
|
|
self.item_about.connect('activate', self.show_about)
|
|
|
|
|
|
|
|
|
|
# Quit menu item
|
|
|
|
|
self.item_quit = Gtk.MenuItem()
|
|
|
|
|
self.item_quit.connect('activate', self.quit_safe_eyes)
|
|
|
|
|
|
|
|
|
|
self.set_labels()
|
|
|
|
|
|
|
|
|
|
# At startup, no need for activate menu
|
|
|
|
|
self.item_enable.set_sensitive(False)
|
|
|
|
|
|
|
|
|
|
# Append all menu items and show the menu
|
|
|
|
|
self.menu.append(self.item_info)
|
|
|
|
|
self.menu.append(self.item_separator)
|
|
|
|
|
self.menu.append(self.item_enable)
|
|
|
|
|
self.menu.append(self.item_disable)
|
|
|
|
|
self.menu.append(self.item_manual_break)
|
|
|
|
|
self.menu.append(self.item_settings)
|
|
|
|
|
self.menu.append(self.item_about)
|
|
|
|
|
self.menu.append(self.item_quit)
|
|
|
|
|
self.menu.show_all()
|
|
|
|
|
|
|
|
|
|
self.item_enable.set_visible(self.allow_disabling)
|
|
|
|
|
self.item_disable.set_visible(self.allow_disabling)
|
|
|
|
|
self.item_quit.set_visible(self.allow_disabling)
|
|
|
|
|
self.item_quit.set_visible(self.allow_disabling)
|
|
|
|
|
|
|
|
|
|
self.indicator.set_menu(self.menu)
|
|
|
|
|
self.update_tooltip()
|
|
|
|
|
|
|
|
|
|
def initialize(self, plugin_config):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the tray icon by setting the config.
|
|
|
|
|
"""
|
|
|
|
|
self.plugin_config = plugin_config
|
|
|
|
|
self.set_labels()
|
|
|
|
|
self.allow_disabling = plugin_config['allow_disabling']
|
|
|
|
|
self.item_enable.set_visible(self.allow_disabling)
|
|
|
|
|
self.item_disable.set_visible(self.allow_disabling)
|
|
|
|
|
self.item_quit.set_visible(self.allow_disabling)
|
|
|
|
|
self.item_quit.set_visible(self.allow_disabling)
|
|
|
|
|
|
|
|
|
|
def set_labels(self):
|
|
|
|
|
"""
|
|
|
|
|
Update the text of menu items based on the selected language.
|
|
|
|
|
"""
|
|
|
|
|
for entry in self.sub_menu_disable_items:
|
|
|
|
|
# print(self.context['locale'].ngettext('For %d Hour', 'For %d Hours', 1) % 1)
|
|
|
|
|
entry[0].set_label(self.context['locale'].ngettext(entry[1][0], entry[1][1], entry[2]) % entry[2])
|
|
|
|
|
|
|
|
|
|
self.sub_menu_item_until_restart.set_label(_('Until restart'))
|
|
|
|
|
self.item_enable.set_label(_('Enable Safe Eyes'))
|
|
|
|
|
self.item_disable.set_label(_('Disable Safe Eyes'))
|
|
|
|
|
self.update_menu()
|
|
|
|
|
self.update_tooltip()
|
|
|
|
|
|
|
|
|
|
def get_items(self):
|
|
|
|
|
breaks_found = self.has_breaks()
|
|
|
|
|
|
|
|
|
|
info_message = _('No breaks available')
|
|
|
|
|
|
|
|
|
|
if breaks_found:
|
|
|
|
|
if self.active:
|
|
|
|
|
if self.date_time:
|
|
|
|
|
self.__set_next_break_info()
|
|
|
|
|
self.indicator.set_icon("io.github.slgobinath.SafeEyes-enabled")
|
|
|
|
|
next_break = self.get_next_break_time()
|
|
|
|
|
|
|
|
|
|
if next_break is not None:
|
|
|
|
|
(next_time, next_long_time, next_is_long) = next_break
|
|
|
|
|
|
|
|
|
|
if next_long_time:
|
|
|
|
|
if next_is_long:
|
|
|
|
|
info_message = _('Next long break at %s') % (next_long_time)
|
|
|
|
|
else:
|
|
|
|
|
info_message = _('Next breaks at %s/%s') % (next_time, next_long_time)
|
|
|
|
|
else:
|
|
|
|
|
info_message = _('Next break at %s') % (next_time)
|
|
|
|
|
else:
|
|
|
|
|
if self.wakeup_time:
|
|
|
|
|
self.item_info.set_label(_('Disabled until %s') % utility.format_time(self.wakeup_time))
|
|
|
|
|
info_message = _('Disabled until %s') % utility.format_time(self.wakeup_time)
|
|
|
|
|
else:
|
|
|
|
|
self.item_info.set_label(_('Disabled until restart'))
|
|
|
|
|
self.indicator.set_label('', '')
|
|
|
|
|
self.indicator.set_icon("io.github.slgobinath.SafeEyes-disabled")
|
|
|
|
|
info_message = _('Disabled until restart')
|
|
|
|
|
|
|
|
|
|
disable_items = []
|
|
|
|
|
|
|
|
|
|
if self.allow_disabling:
|
|
|
|
|
disable_option_dynamic_id = 13
|
|
|
|
|
|
|
|
|
|
for disable_option in self.plugin_config['disable_options']:
|
|
|
|
|
time_in_minutes = time_in_x = disable_option['time']
|
|
|
|
|
label = []
|
|
|
|
|
# Validate time value
|
|
|
|
|
if not isinstance(time_in_minutes, int) or time_in_minutes <= 0:
|
|
|
|
|
logging.error('Invalid time in disable option: ' + str(time_in_minutes))
|
|
|
|
|
continue
|
|
|
|
|
time_unit = disable_option['unit'].lower()
|
|
|
|
|
if time_unit == 'seconds' or time_unit == 'second':
|
|
|
|
|
time_in_minutes = int(time_in_minutes / 60)
|
|
|
|
|
label = ['For %d Second', 'For %d Seconds']
|
|
|
|
|
elif time_unit == 'minutes' or time_unit == 'minute':
|
|
|
|
|
time_in_minutes = int(time_in_minutes * 1)
|
|
|
|
|
label = ['For %d Minute', 'For %d Minutes']
|
|
|
|
|
elif time_unit == 'hours' or time_unit == 'hour':
|
|
|
|
|
time_in_minutes = int(time_in_minutes * 60)
|
|
|
|
|
label = ['For %d Hour', 'For %d Hours']
|
|
|
|
|
else:
|
|
|
|
|
# Invalid unit
|
|
|
|
|
logging.error('Invalid unit in disable option: ' + str(disable_option))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
label = self.context['locale'].ngettext(label[0], label[1], time_in_x) % time_in_x
|
|
|
|
|
|
|
|
|
|
disable_items.append({
|
|
|
|
|
'id': disable_option_dynamic_id,
|
|
|
|
|
'label': label,
|
|
|
|
|
'callback': lambda: self.on_disable_clicked(time_in_minutes),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
disable_option_dynamic_id += 1
|
|
|
|
|
|
|
|
|
|
disable_items.append({
|
|
|
|
|
'id': 12,
|
|
|
|
|
'label': _('Until restart'),
|
|
|
|
|
'callback': lambda: self.on_disable_clicked(-1),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
'id': 1,
|
|
|
|
|
'label': info_message,
|
|
|
|
|
'icon-name': "io.github.slgobinath.SafeEyes-timer",
|
|
|
|
|
'enabled': breaks_found and self.active,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 2,
|
|
|
|
|
'type': "separator",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 3,
|
|
|
|
|
'label': _("Enable Safe Eyes"),
|
|
|
|
|
'enabled': breaks_found and not self.active,
|
|
|
|
|
'callback': self.on_enable_clicked,
|
|
|
|
|
'hidden': not self.allow_disabling,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 4,
|
|
|
|
|
'label': _("Disable Safe Eyes"),
|
|
|
|
|
'enabled': breaks_found and self.active and not self.menu_locked,
|
|
|
|
|
'children-display': 'submenu',
|
|
|
|
|
'children': disable_items,
|
|
|
|
|
'hidden': not self.allow_disabling,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 5,
|
|
|
|
|
'label': _('Take a break now'),
|
|
|
|
|
'enabled': breaks_found and self.active and not self.menu_locked,
|
|
|
|
|
'children-display': 'submenu',
|
|
|
|
|
'children': [
|
|
|
|
|
{
|
|
|
|
|
'id': 9,
|
|
|
|
|
'label': _('Any Break'),
|
|
|
|
|
'callback': lambda: self.on_manual_break_clicked(None),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 10,
|
|
|
|
|
'label': _('Short Break'),
|
|
|
|
|
'callback': lambda: self.on_manual_break_clicked(BreakType.SHORT_BREAK),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 11,
|
|
|
|
|
'label': _('Long Break'),
|
|
|
|
|
'callback': lambda: self.on_manual_break_clicked(BreakType.LONG_BREAK),
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 6,
|
|
|
|
|
'label': _('Settings'),
|
|
|
|
|
'enabled': not self.menu_locked,
|
|
|
|
|
'callback': self.show_settings,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 7,
|
|
|
|
|
'label': _('About'),
|
|
|
|
|
'callback': self.show_about,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'id': 8,
|
|
|
|
|
'label': _('Quit'),
|
|
|
|
|
'enabled': not self.menu_locked,
|
|
|
|
|
'callback': self.quit_safe_eyes,
|
|
|
|
|
'hidden': not self.allow_disabling,
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def update_menu(self):
|
|
|
|
|
self.sni_service.set_items(self.get_items())
|
|
|
|
|
|
|
|
|
|
def update_tooltip(self):
|
|
|
|
|
next_break = self.get_next_break_time()
|
|
|
|
|
|
|
|
|
|
if next_break is not None and self.plugin_config.get('show_time_in_tray', False):
|
|
|
|
|
(next_time, next_long_time, _next_is_long) = next_break
|
|
|
|
|
|
|
|
|
|
if next_long_time and self.plugin_config.get('show_long_time_in_tray', False):
|
|
|
|
|
description = next_long_time
|
|
|
|
|
else:
|
|
|
|
|
description = next_time
|
|
|
|
|
else:
|
|
|
|
|
self.item_info.set_label(_('No breaks available'))
|
|
|
|
|
self.indicator.set_label('', '')
|
|
|
|
|
self.indicator.set_icon("io.github.slgobinath.SafeEyes-disabled")
|
|
|
|
|
self.item_info.set_sensitive(breaks_found and self.active)
|
|
|
|
|
self.item_enable.set_sensitive(breaks_found and not self.active)
|
|
|
|
|
self.item_disable.set_sensitive(breaks_found and self.active)
|
|
|
|
|
self.item_manual_break.set_sensitive(breaks_found and self.active)
|
|
|
|
|
description = ''
|
|
|
|
|
|
|
|
|
|
self.item_manual_break.set_label(_('Take a break now'))
|
|
|
|
|
self.sub_menu_manual_next_break.set_label(_('Any break'))
|
|
|
|
|
self.sub_menu_manual_next_short_break.set_label(_('Short break'))
|
|
|
|
|
self.sub_menu_manual_next_long_break.set_label(_('Long break'))
|
|
|
|
|
self.item_settings.set_label(_('Settings'))
|
|
|
|
|
self.item_about.set_label(_('About'))
|
|
|
|
|
self.item_quit.set_label(_('Quit'))
|
|
|
|
|
self.sni_service.set_tooltip(_('Safe Eyes'), description)
|
|
|
|
|
|
|
|
|
|
def show_icon(self):
|
|
|
|
|
"""
|
|
|
|
|
Show the tray icon.
|
|
|
|
|
"""
|
|
|
|
|
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
|
|
|
|
|
|
|
|
|
|
def hide_icon(self):
|
|
|
|
|
"""
|
|
|
|
|
Hide the tray icon.
|
|
|
|
|
"""
|
|
|
|
|
self.indicator.set_status(appindicator.IndicatorStatus.PASSIVE)
|
|
|
|
|
|
|
|
|
|
def quit_safe_eyes(self, *args):
|
|
|
|
|
def quit_safe_eyes(self):
|
|
|
|
|
"""
|
|
|
|
|
Handle Quit menu action.
|
|
|
|
|
This action terminates the application.
|
|
|
|
@ -257,14 +530,14 @@ class TrayIcon:
|
|
|
|
|
self.idle_condition.release()
|
|
|
|
|
self.quit()
|
|
|
|
|
|
|
|
|
|
def show_settings(self, *args):
|
|
|
|
|
def show_settings(self):
|
|
|
|
|
"""
|
|
|
|
|
Handle Settings menu action.
|
|
|
|
|
This action shows the Settings dialog.
|
|
|
|
|
"""
|
|
|
|
|
self.on_show_settings()
|
|
|
|
|
|
|
|
|
|
def show_about(self, *args):
|
|
|
|
|
def show_about(self):
|
|
|
|
|
"""
|
|
|
|
|
Handle About menu action.
|
|
|
|
|
This action shows the About dialog.
|
|
|
|
@ -277,50 +550,37 @@ class TrayIcon:
|
|
|
|
|
"""
|
|
|
|
|
logging.info("Update next break information")
|
|
|
|
|
self.date_time = dateTime
|
|
|
|
|
self.__set_next_break_info()
|
|
|
|
|
self.update_menu()
|
|
|
|
|
self.update_tooltip()
|
|
|
|
|
|
|
|
|
|
def get_next_break_time(self):
|
|
|
|
|
if not (self.has_breaks() and self.active and self.date_time):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def __set_next_break_info(self):
|
|
|
|
|
"""
|
|
|
|
|
A private method to be called within this class to update the next break information using self.dateTime.
|
|
|
|
|
"""
|
|
|
|
|
formatted_time = utility.format_time(self.get_break_time())
|
|
|
|
|
long_time = self.get_break_time(BreakType.LONG_BREAK)
|
|
|
|
|
|
|
|
|
|
if long_time:
|
|
|
|
|
long_time = utility.format_time(long_time)
|
|
|
|
|
if long_time == formatted_time:
|
|
|
|
|
message = _('Next long break at %s') % (long_time)
|
|
|
|
|
return (long_time, long_time, True)
|
|
|
|
|
else:
|
|
|
|
|
message = _('Next breaks at %s/%s') % (formatted_time, long_time)
|
|
|
|
|
else:
|
|
|
|
|
message = _('Next break at %s') % (formatted_time)
|
|
|
|
|
return (formatted_time, long_time, False)
|
|
|
|
|
|
|
|
|
|
# Update the menu item label
|
|
|
|
|
utility.execute_main_thread(self.item_info.set_label, message)
|
|
|
|
|
return (formatted_time, None, False)
|
|
|
|
|
|
|
|
|
|
# Update the tray icon label
|
|
|
|
|
if self.plugin_config.get('show_time_in_tray', False):
|
|
|
|
|
show_long = long_time and self.plugin_config.get('show_long_time_in_tray', False)
|
|
|
|
|
self.indicator.set_label(long_time if show_long else formatted_time, '')
|
|
|
|
|
else:
|
|
|
|
|
self.indicator.set_label('', '')
|
|
|
|
|
|
|
|
|
|
def on_manual_break_clicked(self, *args):
|
|
|
|
|
def on_manual_break_clicked(self, break_type):
|
|
|
|
|
"""
|
|
|
|
|
Trigger a break manually.
|
|
|
|
|
"""
|
|
|
|
|
if len(args) > 1:
|
|
|
|
|
break_type = args[1]
|
|
|
|
|
self.take_break(break_type)
|
|
|
|
|
else:
|
|
|
|
|
self.take_break()
|
|
|
|
|
self.take_break(break_type)
|
|
|
|
|
|
|
|
|
|
def on_enable_clicked(self, *args):
|
|
|
|
|
def on_enable_clicked(self):
|
|
|
|
|
"""
|
|
|
|
|
Handle 'Enable Safe Eyes' menu action.
|
|
|
|
|
This action enables the application if it is currently disabled.
|
|
|
|
|
"""
|
|
|
|
|
# active = self.item_enable.get_active()
|
|
|
|
|
if not self.active:
|
|
|
|
|
with self.lock:
|
|
|
|
|
self.enable_ui()
|
|
|
|
@ -330,41 +590,40 @@ class TrayIcon:
|
|
|
|
|
self.idle_condition.notify_all()
|
|
|
|
|
self.idle_condition.release()
|
|
|
|
|
|
|
|
|
|
def on_disable_clicked(self, *args):
|
|
|
|
|
def on_disable_clicked(self, time_to_wait):
|
|
|
|
|
"""
|
|
|
|
|
Handle the menu actions of all the sub menus of 'Disable Safe Eyes'.
|
|
|
|
|
This action disables the application if it is currently active.
|
|
|
|
|
"""
|
|
|
|
|
# active = self.item_enable.get_active()
|
|
|
|
|
if self.active and len(args) > 1:
|
|
|
|
|
if self.active:
|
|
|
|
|
self.disable_ui()
|
|
|
|
|
|
|
|
|
|
time_to_wait = args[1]
|
|
|
|
|
if time_to_wait <= 0:
|
|
|
|
|
info = _('Disabled until restart')
|
|
|
|
|
self.disable_safeeyes(info)
|
|
|
|
|
self.wakeup_time = None
|
|
|
|
|
self.item_info.set_label(info)
|
|
|
|
|
else:
|
|
|
|
|
self.wakeup_time = datetime.datetime.now() + datetime.timedelta(minutes=time_to_wait)
|
|
|
|
|
info = _('Disabled until %s') % utility.format_time(self.wakeup_time)
|
|
|
|
|
self.disable_safeeyes(info)
|
|
|
|
|
self.item_info.set_label(info)
|
|
|
|
|
utility.start_thread(self.__schedule_resume, time_minutes=time_to_wait)
|
|
|
|
|
self.update_menu()
|
|
|
|
|
|
|
|
|
|
def lock_menu(self):
|
|
|
|
|
"""
|
|
|
|
|
This method is called by the core to prevent user from disabling Safe Eyes after the notification.
|
|
|
|
|
"""
|
|
|
|
|
if self.active:
|
|
|
|
|
self.menu.set_sensitive(False)
|
|
|
|
|
self.menu_locked = True
|
|
|
|
|
self.update_menu()
|
|
|
|
|
|
|
|
|
|
def unlock_menu(self):
|
|
|
|
|
"""
|
|
|
|
|
This method is called by the core to activate the menu after the the break.
|
|
|
|
|
"""
|
|
|
|
|
if self.active:
|
|
|
|
|
self.menu.set_sensitive(True)
|
|
|
|
|
self.menu_locked = False
|
|
|
|
|
self.update_menu()
|
|
|
|
|
|
|
|
|
|
def disable_ui(self):
|
|
|
|
|
"""
|
|
|
|
@ -373,13 +632,9 @@ class TrayIcon:
|
|
|
|
|
if self.active:
|
|
|
|
|
logging.info('Disable Safe Eyes')
|
|
|
|
|
self.active = False
|
|
|
|
|
self.indicator.set_icon("io.github.slgobinath.SafeEyes-disabled")
|
|
|
|
|
self.item_info.set_label(_('Disabled until restart'))
|
|
|
|
|
self.indicator.set_label('', '')
|
|
|
|
|
self.item_info.set_sensitive(False)
|
|
|
|
|
self.item_enable.set_sensitive(True)
|
|
|
|
|
self.item_disable.set_sensitive(False)
|
|
|
|
|
self.item_manual_break.set_sensitive(False)
|
|
|
|
|
|
|
|
|
|
self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled")
|
|
|
|
|
self.update_menu()
|
|
|
|
|
|
|
|
|
|
def enable_ui(self):
|
|
|
|
|
"""
|
|
|
|
@ -388,11 +643,9 @@ class TrayIcon:
|
|
|
|
|
if not self.active:
|
|
|
|
|
logging.info('Enable Safe Eyes')
|
|
|
|
|
self.active = True
|
|
|
|
|
self.indicator.set_icon("io.github.slgobinath.SafeEyes-enabled")
|
|
|
|
|
self.item_info.set_sensitive(True)
|
|
|
|
|
self.item_enable.set_sensitive(False)
|
|
|
|
|
self.item_disable.set_sensitive(True)
|
|
|
|
|
self.item_manual_break.set_sensitive(True)
|
|
|
|
|
|
|
|
|
|
self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled")
|
|
|
|
|
self.update_menu()
|
|
|
|
|
|
|
|
|
|
def __schedule_resume(self, time_minutes):
|
|
|
|
|
"""
|
|
|
|
@ -404,14 +657,14 @@ class TrayIcon:
|
|
|
|
|
|
|
|
|
|
with self.lock:
|
|
|
|
|
if not self.active:
|
|
|
|
|
utility.execute_main_thread(self.item_enable.activate)
|
|
|
|
|
utility.execute_main_thread(self.on_enable_clicked)
|
|
|
|
|
|
|
|
|
|
def start_animation(self):
|
|
|
|
|
if not self.active or not self.animate:
|
|
|
|
|
return
|
|
|
|
|
utility.execute_main_thread(lambda: self.indicator.set_icon("io.github.slgobinath.SafeEyes-disabled"))
|
|
|
|
|
utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled"))
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
utility.execute_main_thread(lambda: self.indicator.set_icon("io.github.slgobinath.SafeEyes-enabled"))
|
|
|
|
|
utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled"))
|
|
|
|
|
if self.animate and self.active:
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
if self.animate and self.active:
|
|
|
|
@ -420,9 +673,9 @@ class TrayIcon:
|
|
|
|
|
def stop_animation(self):
|
|
|
|
|
self.animate = False
|
|
|
|
|
if self.active:
|
|
|
|
|
utility.execute_main_thread(lambda: self.indicator.set_icon("io.github.slgobinath.SafeEyes-enabled"))
|
|
|
|
|
utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled"))
|
|
|
|
|
else:
|
|
|
|
|
utility.execute_main_thread(lambda: self.indicator.set_icon("io.github.slgobinath.SafeEyes-disabled"))
|
|
|
|
|
utility.execute_main_thread(lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled"))
|
|
|
|
|
|
|
|
|
|
def init(ctx, safeeyes_cfg, plugin_config):
|
|
|
|
|
"""
|
|
|
|
|