timekpr-next/ 000775 001750 001750 00000000000 15122253261 015174 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/ 000775 001750 001750 00000000000 14575617135 016471 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/gui/ 000775 001750 001750 00000000000 15122253261 017236 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/gui/admingui.py 000644 001750 001750 00000472120 15122253261 021411 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
import gi
import os
import webbrowser
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from datetime import timedelta, datetime
import re
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.client.interface.dbus.administration import timekprAdminConnector
from timekpr.common.constants import messages as msg
# constant
_NO_TIME_LABEL_SHORT = "--:--"
_NO_TIME_LABEL = "--:--:--"
_NO_TIME_LIMIT_LABEL = "--:--:--:--"
_HOUR_REGEXP = re.compile(r"^([0-9]{1,2}).*$")
_HOUR_MIN_REGEXP = re.compile(r"^([0-9]{1,2}):([0-9]{1,2}).*$")
_DAY_HOUR_MIN_REGEXP = re.compile(r"^([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}).*$")
_DAY_HOUR_MIN_SEC_REGEXP = re.compile(r"^([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}).*$")
class timekprAdminGUI(object):
"""Main class for supporting timekpr forms"""
def __init__(self, pTimekprVersion, pResourcePath, pUsername):
"""Initialize gui"""
# set up base variables
self._userName = pUsername
self._timekprVersion = pTimekprVersion
self._resourcePath = pResourcePath
self._timekprAdminConnector = None
self._isConnected = False
self._ROWCOL_OK = "#FFFFFF"
self._ROWSTYLE_OK = False
self._ROWCOL_NOK = "Yellow"
self._ROWSTYLE_NOK = True
# ## forms builders ##
# init config builder
self._timekprAdminFormBuilder = Gtk.Builder()
# get our dialog
self._timekprAdminFormBuilder.add_from_file(os.path.join(self._resourcePath, "admin.glade"))
# connect signals, so they get executed
self._timekprAdminFormBuilder.connect_signals(self)
# get window
self._timekprAdminForm = self._timekprAdminFormBuilder.get_object("TimekprApplicationWindow")
# set up GUI elements
self.initGUIElements()
# initialize internal stuff
self.initInternalConfiguration()
# disable all user config buttons firstly
self.toggleUserConfigControls(False)
# disable all timekpr config buttons firstly
self.toggleTimekprConfigControls(False)
# status
self.setTimekprStatus(True, msg.getTranslation("TK_MSG_STATUS_STARTED"))
# initialize internal stuff
GLib.timeout_add_seconds(0.1, self.initTimekprAdmin)
# periodic log flusher
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.autoFlushLogFile)
# loop
self._mainLoop = GLib.MainLoop()
# --------------- initialization / helper methods --------------- #
def startAdminGUI(self):
"""Start up main loop"""
# show up all
self._timekprAdminForm.show()
# this seems to be needed
self.dummyPageChanger()
# start main loop
self._mainLoop.run()
def finishTimekpr(self, signal=None, frame=None):
"""Exit timekpr gracefully"""
log.log(cons.TK_LOG_LEVEL_INFO, "Finishing up")
# exit main loop
self._mainLoop.quit()
log.log(cons.TK_LOG_LEVEL_INFO, "Finished")
log.autoFlushLogFile(True)
def autoFlushLogFile(self):
"""Periodically save file"""
log.autoFlushLogFile()
return True
def dummyPageChanger(self):
"""Switch tabs back and forth"""
# change pages (so objects get initialized, w/o this, spin butons don't get values when set :O)
for rIdx in (1, 0):
self._timekprAdminFormBuilder.get_object("TimekprMainTabBar").set_current_page(rIdx)
for rIdx in (1, 2, 0):
self._timekprAdminFormBuilder.get_object("TimekprConfigurationTabBar").set_current_page(rIdx)
# init timekpr admin client
def initTimekprAdmin(self):
"""Initialize admin client"""
# get our connector
self._timekprAdminConnector = timekprAdminConnector()
# connect
GLib.timeout_add_seconds(0, self._timekprAdminConnector.initTimekprConnection, False)
# check connection
GLib.timeout_add_seconds(0.1, self.checkConnection)
# user calculated info retriever
GLib.timeout_add_seconds(cons.TK_SAVE_INTERVAL, self.retrieveUserInfoAndConfig, None, cons.TK_CL_INF_SAVED)
# user "realtime" info retriever
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.retrieveUserInfoAndConfig, None, cons.TK_CL_INF_RT)
def checkConnection(self):
"""Check connection on the fly"""
# connection statuses
interfacesOk, connecting = self._timekprAdminConnector.isConnected()
# if not connected, give up and get out
if interfacesOk and connecting:
# status
self.setTimekprStatus(True, msg.getTranslation("TK_MSG_STATUS_CONNECTED"))
# in case we are connected, do not retrieve config again
if not self._isConnected:
# connected
self._isConnected = True
# get users
GLib.timeout_add_seconds(0, self.getAdminUserList)
GLib.timeout_add_seconds(0.1, self.retrieveTimekprConfig)
elif not interfacesOk and connecting:
# status
self.setTimekprStatus(True, msg.getTranslation("TK_MSG_STATUS_CONNECTING"))
# invoke again
GLib.timeout_add_seconds(1, self.checkConnection)
# not connected
self._isConnected = False
else:
# status
self.setTimekprStatus(True, msg.getTranslation("TK_MSG_STATUS_CONNECTION_FAILED"))
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_CONNECTION_ACCESS_DENIED"))
# not connected
self._isConnected = False
def initGUIElements(self):
"""Initialize all GUI elements for stores"""
# ## tracked session types ##
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_TRACKED_SESSIONS_PHLD_LABEL"))
rend.connect("edited", self.timekprTrackedSessionsEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_TRACKED_SESSIONS_LABEL"), rend, text=0)
col.set_min_width(125)
self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsTreeView").append_column(col)
# clear
self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsLS").clear()
# ## excluded session types ##
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_UNTRACKED_SESSIONS_PHLD_LABEL"))
rend.connect("edited", self.timekprExcludedSessionsEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_UNTRACKED_SESSIONS_LABEL"), rend, text=0)
col.set_min_width(125)
self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsTreeView").append_column(col)
# clear
self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsLS").clear()
# ## excluded users ##
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_EXCLUDED_USERS_PHLD_LABEL"))
rend.connect("edited", self.timekprExcludedUsersEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_EXCLUDED_USERS_LABEL"), rend, text=0)
col.set_min_width(125)
self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersTreeView").append_column(col)
# clear
self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersLS").clear()
# ## days ##
# day name
rend = Gtk.CellRendererText()
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_LIST_DAY_LABEL"), rend, text=1)
col.set_min_width(115)
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").append_column(col)
# limit
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.connect("edited", self.userLimitsDailyLimitsEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_LIST_LIMIT_LABEL"), rend, text=4)
col.set_min_width(60)
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").append_column(col)
# day enabled
rend = Gtk.CellRendererToggle()
rend.connect("toggled", self.dayAvailabilityChanged)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_LIST_ENABLED_LABEL"), rend, active=2)
rend.set_property("activatable", True)
col.set_min_width(35)
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").append_column(col)
# final col
col = Gtk.TreeViewColumn("", Gtk.CellRendererText())
col.set_min_width(20)
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").append_column(col)
# ## intervals ##
# from hour
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_DAY_INTERVALS_FROM_PHLD_LABEL"))
rend.connect("edited", self.userLimitsHourFromEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_INTERVALS_FROM_LABEL"), rend, text=1, background=6, underline=7)
col.set_min_width(62)
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").append_column(col)
# to hour
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_DAY_INTERVALS_TO_PHLD_LABEL"))
rend.connect("edited", self.userLimitsHourToEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_INTERVALS_TO_LABEL"), rend, text=2, background=6, underline=7)
col.set_min_width(62)
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").append_column(col)
# unaccountable interval column
rend = Gtk.CellRendererToggle()
rend.connect("toggled", self.userLimitsHourUnaccountableToggled)
col = Gtk.TreeViewColumn("∞", rend, active=8)
col.set_property("alignment", 0.5)
col.set_min_width(20)
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").append_column(col)
# final col
col = Gtk.TreeViewColumn("", Gtk.CellRendererText())
col.set_min_width(20)
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").append_column(col)
# clear out existing intervals
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS").clear()
# lets prepare week days
for rDay in range(1, 7+1):
# fill in the intervals
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS").append([str(rDay), (cons.TK_DATETIME_START + timedelta(days=rDay-1)).strftime("%A"), False, 0, _NO_TIME_LABEL])
# ## weekly / monthly limits ##
# type
rend = Gtk.CellRendererText()
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_WK_MON_LABEL"), rend, text=1)
col.set_min_width(90)
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsTreeView").append_column(col)
# weekly/monthly limit
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.connect("edited", self.userLimitsWeeklyLimitsEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_WK_MON_LIMIT_LABEL"), rend, text=3)
col.set_min_width(95)
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsTreeView").append_column(col)
# final col
col = Gtk.TreeViewColumn("", Gtk.CellRendererText())
col.set_min_width(20)
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsTreeView").append_column(col)
# clear out existing intervals
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsLS").clear()
# lets prepare week days
for rType in (("WK", msg.getTranslation("TK_MSG_WEEKLY_LABEL")), ("MON", msg.getTranslation("TK_MSG_MONTHLY_LABEL"))):
# fill in the intervals
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsLS").append([rType[0], rType[1], 0, _NO_TIME_LIMIT_LABEL])
# ## PlayTime elements ##
# day name
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_LIST_DAY_LABEL"), Gtk.CellRendererText(), text=1)
col.set_min_width(115)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").append_column(col)
# day enabled
rend = Gtk.CellRendererToggle()
rend.connect("toggled", self.dayPlayTimeAvailabilityChanged)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_LIST_ENABLED_LABEL"), rend, active=2)
col.set_min_width(35)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").append_column(col)
# limit
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.connect("edited", self.userLimitsDailyPlayTimeLimitsEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_DAY_LIST_LIMIT_LABEL"), rend, text=4)
col.set_min_width(60)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").append_column(col)
# final col
col = Gtk.TreeViewColumn("", Gtk.CellRendererText())
col.set_min_width(20)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").append_column(col)
# PT activity mask
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_PLAYTIME_ACTIVITY_MASK_PHLD_LABEL"))
rend.connect("edited", self.playTimeActivityMaskEntryEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_PLAYTIME_ACTIVITY_MASK_LABEL"), rend, text=1)
col.set_min_width(90)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesTreeView").append_column(col)
# PT activity name
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_PLAYTIME_ACTIVITY_DESCRIPTION_PHLD_LABEL"))
rend.connect("edited", self.playTimeActivityDescriptionEntryEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_PLAYTIME_ACTIVITY_DESCRIPTION_LABEL"), rend, text=2)
col.set_min_width(120)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesTreeView").append_column(col)
# lets prepare week days for PlayTime
for rDay in range(1, 7+1):
# fill in the intervals
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS").append([str(rDay), (cons.TK_DATETIME_START + timedelta(days=rDay-1)).strftime("%A"), False, 0, _NO_TIME_LABEL])
# --------------- GUI control methods --------------- #
def initInternalConfiguration(self):
"""Initialize the internal configuration for admin form"""
self._timekprUserConfigControlElements = [
# combo
"TimekprUserSelectionCB",
# combom refresh
"TimekprUserSelectionRefreshBT",
# control buttons
"TimekprUserConfDaySettingsApplyBT",
"TimekprUserConfTodaySettingsSetAddBT",
"TimekprUserConfTodaySettingsSetSubractBT",
"TimekprUserConfTodaySettingsSetSetBT",
"TimekprUserPlayTimeProcessesAdjustmentAddBT",
"TimekprUserPlayTimeProcessesAdjustmentRemoveBT",
"TimekprUserPlayTimeProcessesApplyBT",
"TimekprUserConfDaySettingsSetDaysIntervalsVerifyBT",
"TimekprUserConfAddOptsApplyBT",
# check box
"TimekprUserConfTodaySettingsTrackInactiveCB",
"TimekprUserConfTodaySettingsHideTrayIconCB",
"TimekprUserPlayTimeEnableCB",
"TimekprUserPlayTimeOverrideEnableCB",
"TimekprUserPlayTimeUnaccountedIntervalsEnabledCB",
# spin buttons for adjustments
"TimekprUserConfTodaySettingsSetMinSB",
"TimekprUserConfTodaySettingsSetHrSB",
"TimekprUserConfAddOptsLockoutTypeSuspendWakeFromSB",
"TimekprUserConfAddOptsLockoutTypeSuspendWakeToSB",
# lists
"TimekprWeekDaysTreeView",
"TimekprHourIntervalsTreeView",
"TimekprUserConfWkMonLimitsTreeView",
"TimekprUserPlayTimeLimitsTreeView",
"TimekprUserPlayTimeProcessesTreeView",
# radio / control groups
"TimekprUserConfDaySettingsSetDaysHeaderControlBX",
"TimekprUserConfDaySettingsSetDaysIntervalsControlBX",
"TimekprUserConfWkMonLimitsAdjustmentsBX",
"TimekprUserConfWkMonLimitsAdjustmentControlButtonsBX",
"TimekprUserPlayTimeLimitsHeaderControlBX",
"TimekprUserConfAddOptsLockoutTypeChoiceBoxBX",
"TimekprUserConfTodaySettingsChoiceBX"
]
self._timekprConfigControlElements = [
# control buttons
"TimekprConfigurationApplyBT",
# check boxes
"TimekprPlayTimeEnableGlobalCB",
"TimekprPlayTimeEnhancedActivityMonitorCB",
# spin buttons for adjustments
"TimekprConfigurationLoglevelSB",
"TimekprConfigurationWarningTimeSB",
"TimekprConfigurationPollIntervalSB",
"TimekprConfigurationSaveTimeSB",
"TimekprConfigurationTerminationTimeSB",
"TimekprConfigurationFinalNotificationSB",
# lists
"TimekprTrackingSessionsTreeView",
"TimekprExcludedSessionsTreeView",
"TimekprExcludedUsersTreeView",
# radio / control groups
"TimekprTrackingSessionsButtonControlBX",
"TimekprExcludedSessionsButtonControlBX",
"TimekprExcludedUsersButtonControlBX"
]
# sets up limit variables for user configuration (internal config to compare to)
self._tkSavedCfg = {}
self._tkSavedCfg["timeTrackInactive"] = False
self._tkSavedCfg["timeHideTrayIcon"] = False
self._tkSavedCfg["timeLockoutType"] = cons.TK_CTRL_RES_T
self._tkSavedCfg["timeWakeInterval"] = "0;23"
self._tkSavedCfg["timeLimitWeek"] = 0
self._tkSavedCfg["timeLimitMonth"] = 0
self._tkSavedCfg["timeLimitDays"] = []
self._tkSavedCfg["timeLimitDaysLimits"] = []
self._tkSavedCfg["timeLimitDaysHoursActual"] = {}
self._tkSavedCfg["timeLimitDaysHoursSaved"] = {}
# initial config
for rDay in range(1, 7+1):
self._tkSavedCfg["timeLimitDaysHoursActual"][str(rDay)] = {}
for rHour in range(0, 23+1):
self._tkSavedCfg["timeLimitDaysHoursActual"][str(rDay)][str(rHour)] = {cons.TK_CTRL_SMIN: 0, cons.TK_CTRL_EMIN: cons.TK_LIMIT_PER_MINUTE, cons.TK_CTRL_UACC: True}
# saved means from server, actual means modified in form
self._tkSavedCfg["timeLimitDaysHoursSaved"] = self._tkSavedCfg["timeLimitDaysHoursActual"].copy()
# ## set up PlayTime variables ##
self._tkSavedCfg["playTimeEnabled"] = False
self._tkSavedCfg["playTimeOverrideEnabled"] = False
self._tkSavedCfg["playTimeUnaccountedIntervalsEnabled"] = False
self._tkSavedCfg["playTimeLimitDays"] = []
self._tkSavedCfg["playTimeLimitDaysLimits"] = []
self._tkSavedCfg["playTimeActivities"] = []
# sets up limit variables for timekpr configuration
self._tkSavedCfg["timekprWarningTime"] = 0
self._tkSavedCfg["timekprNotificationTime"] = 0
self._tkSavedCfg["timekprPollingInterval"] = 0
self._tkSavedCfg["timekprSaveTime"] = 0
self._tkSavedCfg["timekprTerminationTime"] = 0
self._tkSavedCfg["timekprLogLevel"] = 0
self._tkSavedCfg["timekprTrackingSessions"] = []
self._tkSavedCfg["timekprExcludedSessions"] = []
self._tkSavedCfg["timekprExcludedUsers"] = []
self._tkSavedCfg["timekprPlayTimeEnabled"] = False
self._tkSavedCfg["timekprPlayTimeEnhancedActivityMonitorEnabled"] = False
def clearAdminForm(self):
"""Clear and default everything to default values"""
# ## clear form ##
# time limits
for rCtrl in (
"TimekprUserConfTodayInfoSpentTodayLB",
"TimekprUserConfTodayInfoSpentWeekLB",
"TimekprUserConfTodayInfoSpentMonthLB",
"TimekprUserConfTodayInfoLeftTodayLB",
"TimekprUserConfTodayInfoLeftContLB",
"TimekprUserConfTodayInfoInactiveLB"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_text(_NO_TIME_LIMIT_LABEL)
# check / radio boxes
for rCtrl in (
"TimekprUserConfTodaySettingsTrackInactiveCB",
"TimekprUserConfTodaySettingsHideTrayIconCB"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_active(False)
# days / labels
for rDay in self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS"):
# clear list store
rDay[2] = False
rDay[3] = 0
rDay[4] = _NO_TIME_LABEL
# clear day config
for rHour in range(0, 23+1):
self._tkSavedCfg["timeLimitDaysHoursActual"][rDay[0]][str(rHour)] = {cons.TK_CTRL_SMIN: 0, cons.TK_CTRL_EMIN: cons.TK_LIMIT_PER_MINUTE, cons.TK_CTRL_UACC: False}
# clear up the intervals
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").clear()
# week / month limits
for rWkMon in self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsLS"):
# clear list store
rWkMon[2] = 0
rWkMon[3] = _NO_TIME_LIMIT_LABEL
# clear day config
self._tkSavedCfg["timeLimitWeek"] = 0
self._tkSavedCfg["timeLimitMonth"] = 0
# hide lockout intervals
self.controlSelectedLockoutTypeHourIntervals(None)
# reset lockout too
self._tkSavedCfg["timeLockoutType"] = cons.TK_CTRL_RES_T
self._tkSavedCfg["timeWakeInterval"] = "0;23"
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeFromSB").set_value(0)
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeToSB").set_value(23)
# set default lockout type
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeTerminate").set_active(True)
# ## PlayTIme reset ##
# reset times left
for rCtrl in (
"TimekprUserPlayTimeLeftActualLB",
"TimekprUserPlayTimeLeftSavedLB",
"TimekprUserPlayTimeSpentLB"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_text(_NO_TIME_LABEL)
# reset activity count
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeTodaySettingsActivityCntLB").set_text("---")
# reset day limits
for rDay in range(1, 7+1):
# clear list store
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")[rDay-1][2] = False
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")[rDay-1][3] = 0
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")[rDay-1][4] = _NO_TIME_LABEL_SHORT
# clear activities and add one placeholder
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").clear()
# CB not checked
for rCtrl in (
"TimekprUserPlayTimeEnableCB",
"TimekprUserPlayTimeOverrideEnableCB",
"TimekprUserPlayTimeUnaccountedIntervalsEnabledCB"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_active(False)
# color
for rCtrl in (
"TimekprUserConfTodaySettingsSetAddBT",
"TimekprUserConfTodaySettingsSetSubractBT",
"TimekprUserConfTodaySettingsSetSetBT",
"TimekprUserConfTodayLabel",
"TimekprUserConfDaySettingsApplyBT",
"TimekprUserConfDaySettingsSetDaysIntervalsVerifyBT",
"TimekprUserConfDailyLabel",
"TimekprUserPlayTimeProcessesApplyBT",
"TimekprUserPlayTimeLabel",
"TimekprUserConfAddOptsApplyBT",
"TimekprUserConfAddOptsLabel"
):
# reset
self._timekprAdminFormBuilder.get_object(rCtrl).modify_fg(Gtk.StateFlags.NORMAL, None)
# failure
if self._timekprAdminConnector is not None:
# disable all
if not self._timekprAdminConnector.isConnected()[0]:
# reset
for rCtrl in (
"TimekprTrackingSessionsTreeView",
"TimekprTrackingSessionsButtonControlBX",
"TimekprExcludedSessionsTreeView",
"TimekprExcludedSessionsButtonControlBX",
"TimekprExcludedUsersTreeView",
"TimekprExcludedUsersButtonControlBX",
"TimekprPlayTimeEnableGlobalCB",
"TimekprPlayTimeEnhancedActivityMonitorCB"
):
# reset
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(False)
# reset
for rCtrl in (
"TimekprPlayTimeEnableGlobalCB",
"TimekprPlayTimeEnhancedActivityMonitorCB"
):
# reset
self._timekprAdminFormBuilder.get_object(rCtrl).set_active(False)
# reset
for rCtrl in (
"TimekprConfigurationLoglevelSB",
"TimekprConfigurationPollIntervalSB",
"TimekprConfigurationSaveTimeSB",
"TimekprConfigurationTerminationTimeSB",
"TimekprConfigurationWarningTimeSB",
"TimekprConfigurationFinalNotificationSB"
):
# reset
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(False)
self._timekprAdminFormBuilder.get_object(rCtrl).set_value(0)
# reset
for rCtrl in (
"TimekprTrackingSessionsLS",
"TimekprExcludedSessionsLS",
"TimekprExcludedUsersLS"
):
# reset
self._timekprAdminFormBuilder.get_object(rCtrl).clear()
# --------------- DEV test methods --------------- #
def initDEVDefaultConfig(self):
"""Initialize GUI elements for DEV mode"""
# DEV
if cons.TK_DEV_ACTIVE and 1 == 2:
# if there is date, no need to add one
if len(self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")) == 0:
# standard time intervals
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").append([0, "08:00", "13:00", "1", 0, 0, self._ROWCOL_NOK, self._ROWSTYLE_NOK])
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").append([0, "15:00", "18:00", "1", 0, 0, self._ROWCOL_OK, self._ROWSTYLE_OK])
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").append([0, "18:30", "22:00", "1", 0, 0, self._ROWCOL_OK, self._ROWSTYLE_OK])
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").append([0, "22:30", "23:00", "1", 0, 0, self._ROWCOL_NOK, self._ROWSTYLE_NOK])
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").set_sensitive(True)
# if there is date, no need to add one
if len(self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS")) == 0:
# PlayTime activities
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").append(["1", "mask", "Doom Eternal"])
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").append(["2", "mask.*", "The Talos Principle"])
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").append(["3", "mask.*", "Mafia remastered"])
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").append(["4", "csgo_linux", "CS: GO"])
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").append(["5", "kca.*c", "Stupid calculator"])
# enable certain functionality
if 1 == 1:
# enable certain objects (fot testing)
for rO in (
"TimekprUserPlayTimeProcessesAdjustmentAddBT",
"TimekprUserPlayTimeProcessesAdjustmentRemoveBT",
"TimekprUserPlayTimeLimitsTreeView",
"TimekprUserPlayTimeProcessesTreeView"
):
self._timekprAdminFormBuilder.get_object(rO).set_sensitive(True)
# false
return False
# --------------- control / helper methods --------------- #
def getSelectedUserName(self):
"""Get selected username"""
# result
userName = None
# is admin app connected to server
if self._isConnected:
# get object
userCombobox = self._timekprAdminFormBuilder.get_object("TimekprUserSelectionCB")
# get chosen index, model and actual id of the item
userIdx = userCombobox.get_active()
userModel = userCombobox.get_model()
# only if we have selection
if userIdx is not None and userModel is not None:
# only if selected
if userIdx >= 0:
# get username
userName = userModel[userIdx][0]
# result
return userName
def toggleUserConfigControls(self, pEnable=True, pLeaveUserList=False):
"""Enable or disable all user controls for the form"""
# if disable
if not pEnable:
self.clearAdminForm()
# apply settings to all buttons in user configuration
for rObj in self._timekprUserConfigControlElements:
# if we need to leave user selection intact
if not (pLeaveUserList and rObj == "TimekprUserSelectionCB"):
# get the button and set availability
self._timekprAdminFormBuilder.get_object(rObj).set_sensitive(pEnable)
# DEV / samples too
self.initDEVDefaultConfig()
def toggleTimekprConfigControls(self, pEnable=True):
"""Enable or disable all timekpr controls for the form"""
# enable for timekpr main config can be done only in admin mode
enable = pEnable and (os.geteuid() == 0 or cons.TK_DEV_ACTIVE)
# apply settings to all buttons`in user configuration
for rButton in self._timekprConfigControlElements:
# get the button and set availability
self._timekprAdminFormBuilder.get_object(rButton).set_sensitive(enable)
def setTimekprStatus(self, pConnectionStatus, pStatus):
"""Change status of timekpr admin client"""
if pStatus is not None:
# connection
if pConnectionStatus:
# get main status
statusBar = self._timekprAdminFormBuilder.get_object("TimekprConnectionStatusbar")
else:
# get message status
statusBar = self._timekprAdminFormBuilder.get_object("TimekprMessagesStatusbar")
# get context
contextId = statusBar.get_context_id("status")
# pop existing message and add new one
statusBar.remove_all(contextId)
statusBar.push(contextId, pStatus[:80])
def normalizeAllowedDaysAndLimits(self):
"""Method will normalize allowed days and limits, in case user sets them differently"""
# get the least of size
limitLen = min(len(self._tkSavedCfg["timeLimitDays"]), len(self._tkSavedCfg["timeLimitDaysLimits"]))
# remove excess elements
for rElem in (
"timeLimitDays",
"timeLimitDaysLimits"
):
for i in range(limitLen, len(self._tkSavedCfg[rElem])):
self._tkSavedCfg[rElem].pop()
# get the least of size
limitLen = min(len(self._tkSavedCfg["playTimeLimitDays"]), len(self._tkSavedCfg["playTimeLimitDaysLimits"]))
# remove excess elements
for rElem in (
"playTimeLimitDays",
"playTimeLimitDaysLimits"
):
for i in range(limitLen, len(self._tkSavedCfg[rElem])):
self._tkSavedCfg[rElem].pop()
# --------------- format helper methods --------------- #
def formatTimeStr(self, pTotalSeconds, pFormatSecs=False, pFormatDays=False):
"""Format the time intervals as string label"""
# get time out of seconds
time = cons.TK_DATETIME_START + timedelta(seconds=pTotalSeconds)
# day format
isDayFmt = (pTotalSeconds >= cons.TK_LIMIT_PER_DAY and not pFormatDays)
# limit
limitDay = "%s:" % (str((time - cons.TK_DATETIME_START).days).rjust(2, "0")) if pFormatDays else ""
limitHr = "%s" % (str(24 if isDayFmt else time.hour).rjust(2, "0"))
limitMin = ":%s" % (str(0 if isDayFmt else time.minute).rjust(2, "0"))
limitSec = ":%s" % (str(0 if isDayFmt else time.second).rjust(2, "0")) if pFormatSecs else ""
limit = "%s%s%s%s" % (limitDay, limitHr, limitMin, limitSec)
# value
return limit
def getIntervalList(self, pDay):
"""Get intervals for use in GUI"""
# init hours for intervals
timeLimits = []
startTimeStr = None
endTimeStr = None
startSeconds = None
endSeconds = None
uaccValue = None
uaccChanged = False
# loop through all days
for rHour in range(0, 23+1):
# hour in str
hourStr = str(rHour)
# we process only hours that are available
if hourStr in self._tkSavedCfg["timeLimitDaysHoursActual"][pDay]:
# no value (interval was changed)
uaccValue = self._tkSavedCfg["timeLimitDaysHoursActual"][pDay][hourStr][cons.TK_CTRL_UACC] if uaccValue is None else uaccValue
# calc uacc changes
uaccChanged = self._tkSavedCfg["timeLimitDaysHoursActual"][pDay][hourStr][cons.TK_CTRL_UACC] != uaccValue
# if interval is complete and next hour is not available or there is not a continous interval (start != 0 or unaccounted changed, so it's different)
if startTimeStr is not None and endTimeStr is not None:
if (hourStr not in self._tkSavedCfg["timeLimitDaysHoursActual"][pDay]
or (hourStr in self._tkSavedCfg["timeLimitDaysHoursActual"][pDay]
and (self._tkSavedCfg["timeLimitDaysHoursActual"][pDay][hourStr][cons.TK_CTRL_SMIN] != 0
or uaccChanged is True))
):
# add new limit interval
timeLimits.append([startTimeStr, endTimeStr, startSeconds, endSeconds, uaccValue])
# erase values
startTimeStr = None
endTimeStr = None
uaccValue = None
uaccChanged = False
# we process only hours that are available
if hourStr in self._tkSavedCfg["timeLimitDaysHoursActual"][pDay]:
# uacc value
uaccValue = self._tkSavedCfg["timeLimitDaysHoursActual"][pDay][hourStr][cons.TK_CTRL_UACC]
# if start hour is not yet defined
if startTimeStr is None:
# first avaiable hour
startSeconds = rHour * cons.TK_LIMIT_PER_HOUR + self._tkSavedCfg["timeLimitDaysHoursActual"][pDay][hourStr][cons.TK_CTRL_SMIN] * cons.TK_LIMIT_PER_MINUTE
startTimeStr = self.formatTimeStr(startSeconds)
# define end hour
endDate = cons.TK_DATETIME_START + timedelta(hours=rHour, minutes=self._tkSavedCfg["timeLimitDaysHoursActual"][str(pDay)][hourStr][cons.TK_CTRL_EMIN])
endSeconds = int((endDate - cons.TK_DATETIME_START).total_seconds())
endTimeStr = self.formatTimeStr(endSeconds)
# if current interval changes (process end of interval) or this is it, no more hours
if self._tkSavedCfg["timeLimitDaysHoursActual"][pDay][hourStr][cons.TK_CTRL_EMIN] != cons.TK_LIMIT_PER_MINUTE or rHour == 23:
# add new limit interval
timeLimits.append([startTimeStr, endTimeStr, startSeconds, endSeconds, uaccValue])
# erase values
startTimeStr = None
endTimeStr = None
uaccValue = None
uaccChanged = False
# return intervals
return timeLimits
# --------------- field value helper methods --------------- #
def getSelectedDays(self):
"""Get selected day from day list"""
# get selected rows
for i in range(0, 2):
# get selected rows
(tm, paths) = self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").get_selection().get_selected_rows()
# if nothing is selected, set first selected row (if there is nothing, no row is active)
sel = paths is not None
sel = len(paths) > 0 if sel else sel
# nothing selected
if not sel:
# set
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").set_cursor(0)
else:
break
# dict of id and nr of day
days = []
# only if there is smth selected
if paths is not None:
# idx
for path in paths:
# get iter and values
ti = tm.get_iter(path)
days.append({"idx": tm.get_path(ti)[0], "nr": str(tm.get_value(ti, 0))})
# return
return days
def getSelectedHourInterval(self):
"""Get selected hour interval from hour interval list"""
# refresh the child
for i in range(0, 2):
# get selection
(tm, ti) = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").get_selection().get_selected()
# if nothing is selected, get first selected row (if there is nothing, no row is active)
if ti is None:
# set
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").set_cursor(0)
else:
break
# only if there is smth selected
if ti is not None:
# idx
intervalIdx = tm.get_path(ti)[0]
intervalDayNr = str(tm.get_value(ti, 3))
else:
# nothing
intervalIdx = None
intervalDayNr = None
# return
return intervalIdx, intervalDayNr
def getSelectedConfigElement(self, pElementName):
"""Get selected config element"""
# refresh the child
(tm, ti) = self._timekprAdminFormBuilder.get_object(pElementName).get_selection().get_selected()
# return
return tm.get_path(ti)[0] if ti is not None else None
def sortHourIntervals(self):
"""Sort hour intervals for ease of use"""
# sort vairables
hours = {}
rIdx = 0
# prepare sort
for rIt in self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS"):
hours[rIt[4]] = rIdx
# count further
rIdx += 1
# set sort order
sortedHours = []
# set up proper order
for rKey in sorted(hours):
# append to order
sortedHours.append(hours[rKey])
# reorder rows in liststore
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").reorder(sortedHours)
# --------------- additional configuration methods --------------- #
def getSelectedLockoutType(self):
"""Get selected restriction / lockout type"""
# get lockout type
lockoutType = None
lockoutType = cons.TK_CTRL_RES_T if lockoutType is None and self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeTerminate").get_active() else lockoutType
lockoutType = cons.TK_CTRL_RES_K if lockoutType is None and self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeKill").get_active() else lockoutType
lockoutType = cons.TK_CTRL_RES_D if lockoutType is None and self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeShutdown").get_active() else lockoutType
lockoutType = cons.TK_CTRL_RES_S if lockoutType is None and self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspend").get_active() else lockoutType
lockoutType = cons.TK_CTRL_RES_W if lockoutType is None and self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWake").get_active() else lockoutType
lockoutType = cons.TK_CTRL_RES_L if lockoutType is None and self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeLock").get_active() else lockoutType
# result
return lockoutType
def setSelectedLockoutType(self, pLockoutType):
"""Get selected restriction / lockout type"""
# set lockout type
if pLockoutType == cons.TK_CTRL_RES_T:
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeTerminate").set_active(True)
elif pLockoutType == cons.TK_CTRL_RES_K:
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeKill").set_active(True)
elif pLockoutType == cons.TK_CTRL_RES_D:
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeShutdown").set_active(True)
elif pLockoutType == cons.TK_CTRL_RES_S:
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspend").set_active(True)
elif pLockoutType == cons.TK_CTRL_RES_W:
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWake").set_active(True)
elif pLockoutType == cons.TK_CTRL_RES_L:
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeLock").set_active(True)
def controlSelectedLockoutTypeHourIntervals(self, pInterval):
"""Set selected hour intervals"""
# if no interval, just hide them
if pInterval is not None:
# get split interval
hrInterval = pInterval.split(";")
# set values
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeFromSB").set_value(int(hrInterval[0]))
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeToSB").set_value(int(hrInterval[1]))
# set hours visible only when suspendwake
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeWakeupIntervalsLabel").set_visible(pInterval is not None)
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeFromSB").set_visible(pInterval is not None)
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeToSB").set_visible(pInterval is not None)
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeFromSB").set_sensitive(pInterval is not None)
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeToSB").set_sensitive(pInterval is not None)
def enableTimeControlToday(self, pEnable=True):
"""Enable buttons to add time and PlayTime today"""
for rCtrl in (
"TimekprUserConfTodaySettingsSetHrSB",
"TimekprUserConfTodaySettingsSetMinSB",
"TimekprUserConfTodaySettingsChoiceBX"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(pEnable)
# --------------- info retrieval methods --------------- #
def getAdminUserList(self):
"""Get user list via dbus"""
# store
userStore = self._timekprAdminFormBuilder.get_object("TimekprUserSelectionLS")
# clear up
userStore.clear()
userStore.append(["", ""])
# def len
widthInChars = 15
# get list
result, message, userList = self._timekprAdminConnector.getUserList()
# all ok
if result == 0:
# loop and print
for rUser in userList:
# name
userName = "%s (%s)" % (rUser[0], rUser[1]) if (rUser[1] is not None and rUser[1] != "") else rUser[0]
# determine maxlen
widthInChars = max(widthInChars, len(userName) - 3)
# add user
userStore.append([rUser[0], userName])
# status
self.setTimekprStatus(False, "User list retrieved")
# enable
self._timekprAdminFormBuilder.get_object("TimekprUserSelectionCB").set_sensitive(True)
self._timekprAdminFormBuilder.get_object("TimekprUserSelectionRefreshBT").set_sensitive(self._timekprAdminFormBuilder.get_object("TimekprUserSelectionCB").get_sensitive())
# adjust widht
self._timekprAdminFormBuilder.get_object("TimekprUserSelectionCBEntry").set_width_chars(widthInChars)
# init first selection
self._timekprAdminFormBuilder.get_object("TimekprUserSelectionCB").set_active(0)
else:
# status
self.setTimekprStatus(False, message)
# check the connection
self.checkConnection()
def retrieveTimekprConfig(self):
"""Retrieve timekpr configuration"""
# init
timekprConfig = {}
result = 0
message = ""
# get list
result, message, timekprConfig = self._timekprAdminConnector.getTimekprConfiguration()
# all ok
if result == 0:
# loop and print
for rKey, rValue in timekprConfig.items():
# check all by keys
if rKey == "TIMEKPR_LOGLEVEL":
# log level
self._tkSavedCfg["timekprLogLevel"] = int(rValue)
elif rKey == "TIMEKPR_POLLTIME":
# poll time
self._tkSavedCfg["timekprPollingInterval"] = int(rValue)
elif rKey == "TIMEKPR_SAVE_TIME":
# save time
self._tkSavedCfg["timekprSaveTime"] = int(rValue)
elif rKey == "TIMEKPR_TERMINATION_TIME":
# termination time
self._tkSavedCfg["timekprTerminationTime"] = int(rValue)
elif rKey == "TIMEKPR_FINAL_WARNING_TIME":
# final warning time
self._tkSavedCfg["timekprWarningTime"] = int(rValue)
elif rKey == "TIMEKPR_FINAL_NOTIFICATION_TIME":
# final notification time
self._tkSavedCfg["timekprNotificationTime"] = int(rValue)
elif rKey == "TIMEKPR_SESSION_TYPES_CTRL":
# init
self._tkSavedCfg["timekprTrackingSessions"] = []
# loop through available session types
for rSessionType in rValue:
# add config
self._tkSavedCfg["timekprTrackingSessions"].append(str(rSessionType))
elif rKey == "TIMEKPR_SESSION_TYPES_EXCL":
# init
self._tkSavedCfg["timekprExcludedSessions"] = []
# loop through available session types
for rSessionType in rValue:
# add config
self._tkSavedCfg["timekprExcludedSessions"].append(str(rSessionType))
elif rKey == "TIMEKPR_USERS_EXCL":
# init
self._tkSavedCfg["timekprExcludedUsers"] = []
# loop through available users
for rUser in rValue:
# add config
self._tkSavedCfg["timekprExcludedUsers"].append(str(rUser))
elif rKey == "TIMEKPR_PLAYTIME_ENABLED":
# PlayTime enabled
self._tkSavedCfg["timekprPlayTimeEnabled"] = bool(rValue)
elif rKey == "TIMEKPR_PLAYTIME_ENHANCED_ACTIVITY_MONITOR_ENABLED":
# PlayTime enhanced activity monitor enabled
self._tkSavedCfg["timekprPlayTimeEnhancedActivityMonitorEnabled"] = bool(rValue)
# apply config
self.applyTimekprConfig()
# determine control state
self.calculateTimekprConfigControlAvailability()
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_CONFIG_RETRIEVED"))
else:
# disable all but choser
self.toggleUserConfigControls(False, True)
# status
self.setTimekprStatus(False, message)
# check the connection
self.checkConnection()
def retrieveUserInfoAndConfig(self, pUserName, pInfoLvl):
"""Retrieve user configuration"""
# clear before user
if pInfoLvl == cons.TK_CL_INF_FULL:
# reset form
self.clearAdminForm()
# no username passed, we try to find one
userName = self.getSelectedUserName() if pUserName is None else pUserName
# if nothing is passed, nothing is done
if userName is not None and userName != "":
# init
userConfig = {}
# get list
result, message, userConfig = self._timekprAdminConnector.getUserConfigurationAndInformation(userName, pInfoLvl)
# all ok
if result == 0:
# reset if full info or realtime requested
if pInfoLvl in (cons.TK_CL_INF_FULL, cons.TK_CL_INF_RT):
# reset optional information labels
for rCtrl in (
"TimekprUserConfTodayInfoLeftContLB",
"TimekprUserConfTodayInfoInactiveLB"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_text(_NO_TIME_LIMIT_LABEL)
# reset optional information labels for PlayTime
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLeftActualLB").set_text(_NO_TIME_LABEL)
# reset activity count
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeTodaySettingsActivityCntLB").set_text("---")
# loop and print
for rKey, rValue in userConfig.items():
# these ar saved values, refresh of saved or full is asked
if pInfoLvl in (cons.TK_CL_INF_FULL, cons.TK_CL_INF_SAVED):
# this info is refreshed regularly (based on config keys)
if rKey == "TIME_SPENT_DAY":
# spent
timeSpent = cons.TK_DATETIME_START + timedelta(seconds=abs(rValue))
timeSpentStr = "%s:%s:%s:%s" % (str((timeSpent - cons.TK_DATETIME_START).days).rjust(2, "0"), str(timeSpent.hour).rjust(2, "0") , str(timeSpent.minute).rjust(2, "0"), str(timeSpent.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayInfoSpentTodayLB").set_text(timeSpentStr)
elif rKey == "TIME_SPENT_WEEK":
# spent week
timeSpentWeek = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeSpentWeekStr = "%s:%s:%s:%s" % (str((timeSpentWeek - cons.TK_DATETIME_START).days).rjust(2, "0"), str(timeSpentWeek.hour).rjust(2, "0"), str(timeSpentWeek.minute).rjust(2, "0"), str(timeSpentWeek.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayInfoSpentWeekLB").set_text(timeSpentWeekStr)
elif rKey == "TIME_SPENT_MONTH":
# spent month
timeSpentMonth = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeSpentMonthStr = "%s:%s:%s:%s" % (str((timeSpentMonth - cons.TK_DATETIME_START).days).rjust(2, "0"), str(timeSpentMonth.hour).rjust(2, "0"), str(timeSpentMonth.minute).rjust(2, "0"), str(timeSpentMonth.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayInfoSpentMonthLB").set_text(timeSpentMonthStr)
# show balance
elif rKey == "TIME_LEFT_DAY":
# balance
timeLeft = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeLeftStr = "%s:%s:%s:%s" % (str((timeLeft - cons.TK_DATETIME_START).days).rjust(2, "0"), str(timeLeft.hour).rjust(2, "0"), str(timeLeft.minute).rjust(2, "0"), str(timeLeft.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayInfoLeftTodayLB").set_text(timeLeftStr)
# show saved PlayTime left
elif rKey == "PLAYTIME_LEFT_DAY":
# PlayTime left
timeLeft = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeLeftStr = "%s:%s:%s" % (str(timeLeft.hour).rjust(2, "0") , str(timeLeft.minute).rjust(2, "0"), str(timeLeft.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLeftSavedLB").set_text(timeLeftStr)
# show actual PlayTime left
elif rKey == "PLAYTIME_SPENT_DAY":
# PlayTime left
timeLeft = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeLeftStr = "%s:%s:%s" % (str(timeLeft.hour).rjust(2, "0") , str(timeLeft.minute).rjust(2, "0"), str(timeLeft.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeSpentLB").set_text(timeLeftStr)
# refresh only if full or realtime asked
if pInfoLvl in (cons.TK_CL_INF_FULL, cons.TK_CL_INF_RT):
# show actual time left for continous use
if rKey == "ACTUAL_TIME_LEFT_CONTINUOUS":
# total left
timeLeft = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeLeftStr = "%s:%s:%s:%s" % (str((timeLeft - cons.TK_DATETIME_START).days).rjust(2, "0"), str(timeLeft.hour).rjust(2, "0"), str(timeLeft.minute).rjust(2, "0"), str(timeLeft.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayInfoLeftContLB").set_text(timeLeftStr)
# show actual time inactive
elif rKey == "ACTUAL_TIME_INACTIVE_SESSION":
# total left
timeLeft = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeLeftStr = "%s:%s:%s:%s" % (str((timeLeft - cons.TK_DATETIME_START).days).rjust(2, "0"), str(timeLeft.hour).rjust(2, "0"), str(timeLeft.minute).rjust(2, "0"), str(timeLeft.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayInfoInactiveLB").set_text(timeLeftStr)
# show actual PlayTime left
elif rKey == "ACTUAL_PLAYTIME_LEFT_DAY":
# PlayTime left
timeLeft = cons.TK_DATETIME_START + timedelta(seconds=rValue)
timeLeftStr = "%s:%s:%s" % (str(timeLeft.hour).rjust(2, "0") , str(timeLeft.minute).rjust(2, "0"), str(timeLeft.second).rjust(2, "0"))
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLeftActualLB").set_text(timeLeftStr)
# show actual PlayTime count
elif rKey == "ACTUAL_ACTIVE_PLAYTIME_ACTIVITY_COUNT":
# PlayTime count
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeTodaySettingsActivityCntLB").set_text(str(rValue))
# info is needed when full refresh requested
if pInfoLvl == cons.TK_CL_INF_FULL:
if rKey == "TRACK_INACTIVE":
# track inactive
self._tkSavedCfg["timeTrackInactive"] = bool(rValue)
elif rKey == "HIDE_TRAY_ICON":
# hide icon and notif
self._tkSavedCfg["timeHideTrayIcon"] = bool(rValue)
elif rKey == "LOCKOUT_TYPE":
# set lockout type
self._tkSavedCfg["timeLockoutType"] = rValue
elif rKey == "WAKEUP_HOUR_INTERVAL":
# set interval values
self._tkSavedCfg["timeWakeInterval"] = rValue
elif rKey == "ALLOWED_WEEKDAYS":
# empty the values
self._tkSavedCfg["timeLimitDays"] = []
# allowed weekdays
for rDay in rValue:
# set values
self._tkSavedCfg["timeLimitDays"].append(str(rDay))
elif rKey == "LIMITS_PER_WEEKDAYS":
# limits per allowed weekdays
self._tkSavedCfg["timeLimitDaysLimits"] = []
# allowed weekdays
for rDay in range(0, len(rValue)):
# add the value
self._tkSavedCfg["timeLimitDaysLimits"].append(int(rValue[rDay]))
elif rKey == "LIMIT_PER_WEEK":
# value
self._tkSavedCfg["timeLimitWeek"] = int(rValue)
elif rKey == "LIMIT_PER_MONTH":
# value
self._tkSavedCfg["timeLimitMonth"] = int(rValue)
elif "ALLOWED_HOURS_" in rKey:
# determine the day
day = rKey[-1:]
self._tkSavedCfg["timeLimitDaysHoursActual"][day] = {}
# loop through available hours
for rHour, rHourMinutes in rValue.items():
# add config
self._tkSavedCfg["timeLimitDaysHoursActual"][day][str(rHour)] = {cons.TK_CTRL_SMIN: int(rHourMinutes[cons.TK_CTRL_SMIN]), cons.TK_CTRL_EMIN: int(rHourMinutes[cons.TK_CTRL_EMIN]), cons.TK_CTRL_UACC: bool(rHourMinutes[cons.TK_CTRL_UACC])}
# set up saved config as well
self._tkSavedCfg["timeLimitDaysHoursSaved"][day] = self._tkSavedCfg["timeLimitDaysHoursActual"][day].copy()
# ## PlayTime config ##
elif rKey == "PLAYTIME_ENABLED":
# PlayTime enabled
self._tkSavedCfg["playTimeEnabled"] = bool(rValue)
elif rKey == "PLAYTIME_LIMIT_OVERRIDE_ENABLED":
# PlayTime override enabled
self._tkSavedCfg["playTimeOverrideEnabled"] = bool(rValue)
elif rKey == "PLAYTIME_UNACCOUNTED_INTERVALS_ENABLED":
# PlayTime allowed during unaccounted intervals
self._tkSavedCfg["playTimeUnaccountedIntervalsEnabled"] = bool(rValue)
elif rKey == "PLAYTIME_ALLOWED_WEEKDAYS":
# empty the values
self._tkSavedCfg["playTimeLimitDays"] = []
# allowed weekdays
for rDay in rValue:
# set values
self._tkSavedCfg["playTimeLimitDays"].append(str(rDay))
elif rKey == "PLAYTIME_LIMITS_PER_WEEKDAYS":
# limits per allowed weekdays
self._tkSavedCfg["playTimeLimitDaysLimits"] = []
# allowed weekdays
for rDay in range(0, len(rValue)):
# add the value
self._tkSavedCfg["playTimeLimitDaysLimits"].append(int(rValue[rDay]))
elif rKey == "PLAYTIME_ACTIVITIES":
# PlayTime activity list
self._tkSavedCfg["playTimeActivities"] = []
# allowed weekdays
for rDay in range(0, len(rValue)):
# add the value
self._tkSavedCfg["playTimeActivities"].append([rValue[rDay][0], rValue[rDay][1]])
# clean up limits if full refresh requested
if pInfoLvl == cons.TK_CL_INF_FULL:
self.normalizeAllowedDaysAndLimits()
# if PT override is enabled, we do not show time information for PT
if self._tkSavedCfg["playTimeOverrideEnabled"]:
# disable time show
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLeftSavedLB").set_text(_NO_TIME_LABEL)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLeftActualLB").set_text(_NO_TIME_LABEL)
# config was updated only when full
if pInfoLvl == cons.TK_CL_INF_FULL:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_USER_CONFIG_RETRIEVED"))
# apply config
self.applyUserConfig()
# determine control state
self.calculateUserConfigControlAvailability()
self.calculateUserPlayTimeConfigControlAvailability()
self.calculateUserAdditionalConfigControlAvailability()
# enable adding hours as well
self.enableTimeControlToday()
else:
# disable all but choser
self.toggleUserConfigControls(False, True)
# status
self.setTimekprStatus(False, message)
# check the connection
self.checkConnection()
# return
return True
# --------------- retrieved configuration apply methods --------------- #
def applyTimekprConfig(self):
"""Apply user configuration after getting it from server"""
# ## log level ##
self._timekprAdminFormBuilder.get_object("TimekprConfigurationLoglevelSB").set_value(self._tkSavedCfg["timekprLogLevel"])
self._timekprAdminFormBuilder.get_object("TimekprConfigurationLoglevelSB").set_sensitive(True)
# ## poll time ##
self._timekprAdminFormBuilder.get_object("TimekprConfigurationPollIntervalSB").set_value(self._tkSavedCfg["timekprPollingInterval"])
self._timekprAdminFormBuilder.get_object("TimekprConfigurationPollIntervalSB").set_sensitive(True)
# ## save time ##
self._timekprAdminFormBuilder.get_object("TimekprConfigurationSaveTimeSB").set_value(self._tkSavedCfg["timekprSaveTime"])
self._timekprAdminFormBuilder.get_object("TimekprConfigurationSaveTimeSB").set_sensitive(True)
# ## termination time ##
self._timekprAdminFormBuilder.get_object("TimekprConfigurationTerminationTimeSB").set_value(self._tkSavedCfg["timekprTerminationTime"])
self._timekprAdminFormBuilder.get_object("TimekprConfigurationTerminationTimeSB").set_sensitive(True)
# ## final warning time ##
self._timekprAdminFormBuilder.get_object("TimekprConfigurationWarningTimeSB").set_value(self._tkSavedCfg["timekprWarningTime"])
self._timekprAdminFormBuilder.get_object("TimekprConfigurationWarningTimeSB").set_sensitive(True)
# ## final notification time ##
self._timekprAdminFormBuilder.get_object("TimekprConfigurationFinalNotificationSB").set_value(self._tkSavedCfg["timekprNotificationTime"])
self._timekprAdminFormBuilder.get_object("TimekprConfigurationFinalNotificationSB").set_sensitive(True)
# ## tracking session types ###
self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsLS").clear()
for rSessionType in self._tkSavedCfg["timekprTrackingSessions"]:
# add config
self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsLS").append([str(rSessionType)])
# enable editing
for rObj in (
"TimekprTrackingSessionsTreeView",
"TimekprTrackingSessionsButtonControlBX"
):
self._timekprAdminFormBuilder.get_object(rObj).set_sensitive(True)
# select first row
if len(self._tkSavedCfg["timekprTrackingSessions"]) > 0:
self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsTreeView").set_cursor(0)
self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsTreeView").scroll_to_cell(0)
# ## exclusion session types ##
self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsLS").clear()
for rSessionType in self._tkSavedCfg["timekprExcludedSessions"]:
# add config
self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsLS").append([str(rSessionType)])
# enable editing
for rObj in (
"TimekprExcludedSessionsTreeView",
"TimekprExcludedSessionsButtonControlBX"
):
self._timekprAdminFormBuilder.get_object(rObj).set_sensitive(True)
# select first row
if len(self._tkSavedCfg["timekprExcludedSessions"]) > 0:
self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsTreeView").set_cursor(0)
self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsTreeView").scroll_to_cell(0)
# ## excluded users ##
self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersLS").clear()
for rUser in self._tkSavedCfg["timekprExcludedUsers"]:
# add config
self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersLS").append([str(rUser)])
# enable editing
for rObj in (
"TimekprExcludedUsersTreeView",
"TimekprExcludedUsersButtonControlBX"
):
self._timekprAdminFormBuilder.get_object(rObj).set_sensitive(True)
# select first row
if len(self._tkSavedCfg["timekprExcludedUsers"]) > 0:
self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersTreeView").set_cursor(0)
self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersTreeView").scroll_to_cell(0)
# ## PlayTime ##
# global enabled switch
self._timekprAdminFormBuilder.get_object("TimekprPlayTimeEnableGlobalCB").set_active(self._tkSavedCfg["timekprPlayTimeEnabled"])
self._timekprAdminFormBuilder.get_object("TimekprPlayTimeEnableGlobalCB").set_sensitive(True)
# global enhanced activity monitor
self._timekprAdminFormBuilder.get_object("TimekprPlayTimeEnhancedActivityMonitorCB").set_active(self._tkSavedCfg["timekprPlayTimeEnhancedActivityMonitorEnabled"])
self._timekprAdminFormBuilder.get_object("TimekprPlayTimeEnhancedActivityMonitorCB").set_sensitive(True)
# enable / disable controls
self.toggleTimekprConfigControls(True)
def applyUserConfig(self):
"""Apply user configuration after getting it from server"""
# enable refresh
self._timekprAdminFormBuilder.get_object("TimekprUserSelectionRefreshBT").set_sensitive(True)
# ## allowed days ###
for rDay in range(1, 7+1):
# enable certain days
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")[rDay-1][2] = (str(rDay) in self._tkSavedCfg["timeLimitDays"])
# enable editing
for rCtrl in (
"TimekprWeekDaysTreeView",
"TimekprHourIntervalsTreeView",
"TimekprUserConfDaySettingsSetDaysHeaderControlBX"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(True)
# ## limits per allowed days ###
dayLimitIdx = -1
# loop through all days
for rDay in cons.TK_ALLOWED_WEEKDAYS.split(";"):
# day index
dayIdx = int(rDay) - 1
# check whether this day is enabled
if rDay in self._tkSavedCfg["timeLimitDays"]:
# advance index
dayLimitIdx += 1
else:
continue
# calculate time
limit = self.formatTimeStr(self._tkSavedCfg["timeLimitDaysLimits"][dayLimitIdx], True)
# enable certain days
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")[dayIdx][3] = self._tkSavedCfg["timeLimitDaysLimits"][dayLimitIdx]
# set appropriate label as well
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")[dayIdx][4] = limit if rDay in self._tkSavedCfg["timeLimitDays"] else _NO_TIME_LABEL
# ## hour intervals ##
# intervals themselves will be adjusted depending on selected day, we just enable them here
for rCtrl in (
"TimekprHourIntervalsTreeView",
"TimekprUserConfDaySettingsSetDaysIntervalsControlBX"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(True)
# select first row (if there are intervals)
if dayLimitIdx > -1:
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsTreeView").set_cursor(0)
self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsTreeView").scroll_to_cell(0)
# ## limit per week / month ##
for rWkDay in self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsLS"):
# week
if rWkDay[0] == "WK":
limit = self._tkSavedCfg["timeLimitWeek"]
elif rWkDay[0] == "MON":
limit = self._tkSavedCfg["timeLimitMonth"]
rWkDay[2] = limit
rWkDay[3] = self.formatTimeStr(limit, True, True)
# enable editing
for rCtrl in (
"TimekprUserConfWkMonLimitsTreeView",
"TimekprUserConfWkMonLimitsAdjustmentsBX",
"TimekprUserConfWkMonLimitsAdjustmentControlButtonsBX"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(True)
# current day
currDay = datetime.now().isoweekday()-1
# determine curent day and point to it
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").set_cursor(currDay)
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").scroll_to_cell(currDay)
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").get_selection().emit("changed")
# ## PlayTime config ##
# PlayTime and PlayTime options enablement
for rCtrl in (
("TimekprUserPlayTimeEnableCB", "playTimeEnabled"),
("TimekprUserPlayTimeOverrideEnableCB", "playTimeOverrideEnabled"),
("TimekprUserPlayTimeUnaccountedIntervalsEnabledCB", "playTimeUnaccountedIntervalsEnabled")
):
# set value
self._timekprAdminFormBuilder.get_object(rCtrl[0]).set_active(self._tkSavedCfg[rCtrl[1]])
# enable field & set button
self._timekprAdminFormBuilder.get_object(rCtrl[0]).set_sensitive(True)
# ## PlayTime limits per allowed days ###
# loop through all days
for rDay in cons.TK_ALLOWED_WEEKDAYS.split(";"):
# day index
dayIdx = int(rDay) - 1
# check whether this day is enabled
if rDay in self._tkSavedCfg["playTimeLimitDays"]:
# advance index
dayLimitIdx = self._tkSavedCfg["playTimeLimitDays"].index(rDay)
else:
# day not enabled
dayLimitIdx = None
# calculate time
limit = self.formatTimeStr(self._tkSavedCfg["playTimeLimitDaysLimits"][dayLimitIdx], True) if dayLimitIdx is not None else _NO_TIME_LABEL
# enable certain days
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")[dayIdx][2] = dayLimitIdx is not None
# enable time limit
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")[dayIdx][3] = self._tkSavedCfg["playTimeLimitDaysLimits"][dayLimitIdx] if dayLimitIdx is not None else 0
# set appropriate label as well
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")[dayIdx][4] = limit
# determine curent day and point to it
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").set_cursor(currDay)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").scroll_to_cell(currDay)
# enable PlayTime editing
for rCtrl in (
"TimekprUserPlayTimeLimitsTreeView",
"TimekprUserPlayTimeLimitsHeaderControlBX"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(True)
# ## PlayTime activities ###
activityIdx = -1
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").clear()
# check whether this day is enabled
for rAct in self._tkSavedCfg["playTimeActivities"]:
# advance index
activityIdx += 1
# enable certain days
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS").append([str(activityIdx), rAct[0], rAct[1]])
# enable PlayTime editing
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesTreeView").set_sensitive(True)
# if there are activities
if activityIdx > -1:
# select first row
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesTreeView").set_cursor(0)
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesTreeView").scroll_to_cell(0)
# set enablement for PlayTime controls
for rCtrl in (
"TimekprUserPlayTimeProcessesAdjustmentAddBT",
"TimekprUserPlayTimeProcessesAdjustmentRemoveBT",
"TimekprUserPlayTimeLimitsHeaderControlBX"
):
# enable field & set button
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(True)
# ## additional config ##
# set values for track inactive and disable notifications
for rCtrl in (
"TimekprUserConfTodaySettingsTrackInactiveCB",
"TimekprUserConfTodaySettingsHideTrayIconCB"
):
# set value
self._timekprAdminFormBuilder.get_object(rCtrl).set_active(self._tkSavedCfg["timeTrackInactive"] if rCtrl == "TimekprUserConfTodaySettingsTrackInactiveCB" else self._tkSavedCfg["timeHideTrayIcon"])
# enable field & set button
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(True)
# lockout type and intervals
# set option
self.setSelectedLockoutType(self._tkSavedCfg["timeLockoutType"])
# set option
self.controlSelectedLockoutTypeHourIntervals(self._tkSavedCfg["timeWakeInterval"])
# enable editing
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeChoiceBoxBX").set_sensitive(True)
# --------------- change detection and GUI action control methods --------------- #
def calculateTimekprConfigControlAvailability(self, pApplyControls=True):
"""Calculate main control availability"""
# this duplicates diff control as well
changeControl = {}
# perform?
if not self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsTreeView").get_sensitive():
return changeControl
# ## log level ##
control = "TimekprConfigurationLoglevelSB"
value = self._timekprAdminFormBuilder.get_object(control).get_value_as_int()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprLogLevel"], "val": value}
# ## poll time ##
control = "TimekprConfigurationPollIntervalSB"
value = self._timekprAdminFormBuilder.get_object(control).get_value_as_int()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprPollingInterval"], "val": value}
# ## save time ##
control = "TimekprConfigurationSaveTimeSB"
value = self._timekprAdminFormBuilder.get_object(control).get_value_as_int()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprSaveTime"], "val": value}
# ## termination time ##
control = "TimekprConfigurationTerminationTimeSB"
value = self._timekprAdminFormBuilder.get_object(control).get_value_as_int()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprTerminationTime"], "val": value}
# ## final warning time ##
control = "TimekprConfigurationWarningTimeSB"
value = self._timekprAdminFormBuilder.get_object(control).get_value_as_int()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprWarningTime"], "val": value}
# ## final notification ##
control = "TimekprConfigurationFinalNotificationSB"
value = self._timekprAdminFormBuilder.get_object(control).get_value_as_int()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprNotificationTime"], "val": value}
# ## tracking session types ###
tmpArray = [str(rIt[0]) for rIt in self._timekprAdminFormBuilder.get_object("TimekprTrackingSessionsLS") if rIt[0] != ""]
control = "TimekprTrackingSessionsLS"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["timekprTrackingSessions"], "val": tmpArray.copy()}
# ## exclusion session types ##
tmpArray = [str(rIt[0]) for rIt in self._timekprAdminFormBuilder.get_object("TimekprExcludedSessionsLS") if rIt[0] != ""]
control = "TimekprExcludedSessionsLS"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["timekprExcludedSessions"], "val": tmpArray.copy()}
# ## excluded users ##
tmpArray = [str(rIt[0]) for rIt in self._timekprAdminFormBuilder.get_object("TimekprExcludedUsersLS") if rIt[0] != ""]
control = "TimekprExcludedUsersLS"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["timekprExcludedUsers"], "val": tmpArray.copy()}
# ## global PlayTime switch ##
control = "TimekprPlayTimeEnableGlobalCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprPlayTimeEnabled"], "val": value}
# ## global PlayTime switch ##
control = "TimekprPlayTimeEnhancedActivityMonitorCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["timekprPlayTimeEnhancedActivityMonitorEnabled"], "val": value}
# if at least one is changed
enable = False
if pApplyControls:
for rKey, rVal in changeControl.items():
# one thing changed
if rVal["st"]:
# enable
enable = rVal["st"]
# no need to search further
break
# enabled or not
self._timekprAdminFormBuilder.get_object("TimekprConfigurationApplyBT").set_sensitive(enable)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprConfigurationApplyBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enable else None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprConfigurationTabLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enable else None)
# return
return changeControl
def calculateUserTodayControlAvailability(self):
"""Calculate user today config control availability"""
# ## add time today ##
enabled = (self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetHrSB").get_value_as_int() != 0 or self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetMinSB").get_value_as_int() != 0)
for rCtrl in (
"TimekprUserConfTodaySettingsSetAddBT",
"TimekprUserConfTodaySettingsSetSubractBT",
"TimekprUserConfTodaySettingsSetSetBT"
):
self._timekprAdminFormBuilder.get_object(rCtrl).set_sensitive(enabled)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetAddBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enabled else None)
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetSubractBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enabled else None)
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetSetBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enabled else None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodayLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enabled else None)
def calculateUserConfigControlAvailability(self, pApplyControls=True):
"""Calculate user config control availability"""
# this duplicates diff control as well
changeControl = {}
# perform?
if not self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").get_sensitive():
return changeControl
# get stores (for use later)
limitSt = self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")
wkMonSt = self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsLS")
# ## time day config ##
tmpArray = [str(rIt[0]) for rIt in limitSt if rIt[2]]
control = "TimekprUserWeekDaysLSD"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["timeLimitDays"], "val": tmpArray.copy()}
# ## time limits per allowed days ###
tmpArray = [rIt[3] for rIt in limitSt if rIt[2]]
control = "TimekprUserWeekDaysLSL"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["timeLimitDaysLimits"], "val": tmpArray.copy()}
# ## intervals ###
areIntervalsVerified = self.areHoursVerified()
control = "TimekprHourIntervalsLS"
changeControl[control] = {"st": self._tkSavedCfg["timeLimitDaysHoursActual"] != self._tkSavedCfg["timeLimitDaysHoursSaved"], "val": self._tkSavedCfg["timeLimitDaysHoursActual"]}
# ## week / month limits ###
for rIt in wkMonSt:
# week or month?
if rIt[0] == "WK":
control = "TimekprUserConfWkMonLimitsLSWK"
changeControl[control] = {"st": rIt[2] != self._tkSavedCfg["timeLimitWeek"], "val": rIt[2]}
elif rIt[0] == "MON":
control = "TimekprUserConfWkMonLimitsLSMON"
changeControl[control] = {"st": rIt[2] != self._tkSavedCfg["timeLimitMonth"], "val": rIt[2]}
# if at least one is changed
configChanged = False
if pApplyControls:
for rKey, rVal in changeControl.items():
# one thing changed
if rVal["st"]:
# enable
configChanged = rVal["st"]
# no need to search further
break
# enabled or not
self._timekprAdminFormBuilder.get_object("TimekprUserConfDaySettingsApplyBT").set_sensitive(configChanged and areIntervalsVerified)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprUserConfDaySettingsApplyBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if configChanged else None)
# enable / disable verify
self._timekprAdminFormBuilder.get_object("TimekprUserConfDaySettingsSetDaysIntervalsVerifyBT").set_sensitive(not areIntervalsVerified)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprUserConfDaySettingsSetDaysIntervalsVerifyBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if not areIntervalsVerified else None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprUserConfDailyLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if (configChanged or not areIntervalsVerified) else None)
# return
return changeControl
def calculateUserPlayTimeConfigControlAvailability(self, pApplyControls=True):
"""Calculate user PlayTime config control availability"""
# this duplicates diff control as well
changeControl = {}
# perform?
if not self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsTreeView").get_sensitive():
return changeControl
# ## PlayTime enabled ##
control = "TimekprUserPlayTimeEnableCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["playTimeEnabled"], "val": value}
# ## PlayTime override enabled ##
control = "TimekprUserPlayTimeOverrideEnableCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["playTimeOverrideEnabled"], "val": value}
# ## PlayTime allowed during unaccounted intervals ##
control = "TimekprUserPlayTimeUnaccountedIntervalsEnabledCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["playTimeUnaccountedIntervalsEnabled"], "val": value}
# get stores (for use later)
limitSt = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")
actSt = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS")
actStLen = len(actSt)
# ## PlayTime day config ##
tmpArray = [str(rIt[0]) for rIt in limitSt if rIt[2]]
control = "TimekprUserPlayTimeLimitsLSD"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["playTimeLimitDays"], "val": tmpArray.copy()}
# ## PlayTime limits per allowed days ###
tmpArray = [rIt[3] for rIt in limitSt if rIt[2]]
control = "TimekprUserPlayTimeLimitsLSL"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["playTimeLimitDaysLimits"], "val": tmpArray.copy()}
# ## PlayTime activities ###
tmpArray = []
idx = 0
for rIt in actSt:
# increase idx
idx += 1
# do not add, if last line is not filed in properly
if not (idx == actStLen and rIt[1] == ""):
# add mask and description
tmpArray.append([rIt[1], rIt[2]])
control = "TimekprUserPlayTimeProcessesLS"
changeControl[control] = {"st": tmpArray != self._tkSavedCfg["playTimeActivities"], "val": tmpArray.copy()}
# if at least one is changed
enable = False
if pApplyControls:
for rKey, rVal in changeControl.items():
# one thing changed
if rVal["st"]:
# enable
enable = rVal["st"]
# no need to search further
break
# enabled or not
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesApplyBT").set_sensitive(enable)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesApplyBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enable else None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enable else None)
# return
return changeControl
def calculateUserAdditionalConfigControlAvailability(self, pApplyControls=True):
"""Calculate user config control availability"""
# this duplicates diff control as well
changeControl = {}
# ## Track inactive enabled ##
control = "TimekprUserConfTodaySettingsTrackInactiveCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["timeTrackInactive"], "val": value}
# ## Hide try icon enabled ##
control = "TimekprUserConfTodaySettingsHideTrayIconCB"
value = self._timekprAdminFormBuilder.get_object(control).get_active()
changeControl[control] = {"st": value != self._tkSavedCfg["timeHideTrayIcon"], "val": value}
# ## Lockout type / interval ##
control = "TimekprUserConfAddOptsLockoutType"
# get lockout type
lockoutType = self.getSelectedLockoutType()
# intervals
hrFrom = str(self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeFromSB").get_value_as_int()) if lockoutType == cons.TK_CTRL_RES_W else "0"
hrTo = str(self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLockoutTypeSuspendWakeToSB").get_value_as_int()) if lockoutType == cons.TK_CTRL_RES_W else "23"
interval = "%s;%s" % (hrFrom, hrTo)
value = (lockoutType, hrFrom, hrTo)
changeControl[control] = {"st": lockoutType != self._tkSavedCfg["timeLockoutType"] or interval != self._tkSavedCfg["timeWakeInterval"], "val": value}
# interval control
self.controlSelectedLockoutTypeHourIntervals(interval if self.getSelectedLockoutType() == cons.TK_CTRL_RES_W else None)
# if at least one is changed
enable = False
if pApplyControls:
for rKey, rVal in changeControl.items():
# one thing changed
if rVal["st"]:
# enable
enable = rVal["st"]
# no need to search further
break
# enabled or not
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsApplyBT").set_sensitive(enable)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsApplyBT").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enable else None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprUserConfAddOptsLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if enable else None)
# return
return changeControl
# --------------- changed information publish methods --------------- #
def applyTimekprConfigurationChanges(self):
"""Apply configuration changes to server"""
# get what's changed
changeControl = self.calculateTimekprConfigControlAvailability(False)
# initial values
result = 0
message = ""
# loop through all changes
for rKey, rVal in changeControl.items():
# changed
if rVal["st"]:
# check what element we have, depending on that call different interface
# ## poll time ##
if rKey == "TimekprConfigurationLoglevelSB":
# call server
result, message = self._timekprAdminConnector.setTimekprLogLevel(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprLogLevel"] = rVal["val"]
# ## poll time ##
elif rKey == "TimekprConfigurationPollIntervalSB":
# call server
result, message = self._timekprAdminConnector.setTimekprPollTime(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprPollingInterval"] = rVal["val"]
# ## save time ##
elif rKey == "TimekprConfigurationSaveTimeSB":
# call server
result, message = self._timekprAdminConnector.setTimekprSaveTime(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprSaveTime"] = rVal["val"]
# ## termination time ##
elif rKey == "TimekprConfigurationTerminationTimeSB":
# call server
result, message = self._timekprAdminConnector.setTimekprTerminationTime(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprTerminationTime"] = rVal["val"]
# ## final warning time ##
elif rKey == "TimekprConfigurationWarningTimeSB":
# call server
result, message = self._timekprAdminConnector.setTimekprFinalWarningTime(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprWarningTime"] = rVal["val"]
# ## final notification ##
elif rKey == "TimekprConfigurationFinalNotificationSB":
# call server
result, message = self._timekprAdminConnector.setTimekprFinalNotificationTime(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprNotificationTime"] = rVal["val"]
# ## tracking session types ###
elif rKey == "TimekprTrackingSessionsLS":
# call server
result, message = self._timekprAdminConnector.setTimekprSessionsCtrl(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprTrackingSessions"] = rVal["val"].copy()
# ## exclusion session types ##
elif rKey == "TimekprExcludedSessionsLS":
# call server
result, message = self._timekprAdminConnector.setTimekprSessionsExcl(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprExcludedSessions"] = rVal["val"].copy()
# ## excluded users ##
elif rKey == "TimekprExcludedUsersLS":
# call server
result, message = self._timekprAdminConnector.setTimekprUsersExcl(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprExcludedUsers"] = rVal["val"].copy()
# ## PlayTime enabled ##
elif rKey == "TimekprPlayTimeEnableGlobalCB":
# call server
result, message = self._timekprAdminConnector.setTimekprPlayTimeEnabled(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprPlayTimeEnabled"] = rVal["val"]
# ## PlayTime enhanced activity monitor ##
elif rKey == "TimekprPlayTimeEnhancedActivityMonitorCB":
# call server
result, message = self._timekprAdminConnector.setTimekprPlayTimeEnhancedActivityMonitorEnabled(rVal["val"])
# successful call
if result == 0:
# set internal state
self._tkSavedCfg["timekprPlayTimeEnhancedActivityMonitorEnabled"] = rVal["val"]
# if all ok
if result != 0:
# status
self.setTimekprStatus(False, message)
# that's it
break
# fine
if result != 0:
# check the connection
self.checkConnection()
else:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_CONFIGURATION_SAVED"))
# recalc the control state
self.calculateTimekprConfigControlAvailability()
def applyUserTodayConfigurationChanges(self, pType, pOperation):
"""Process actual call to set time for user"""
# get username
userName = self.getSelectedUserName()
# if we have username
if userName is not None:
# initial values
result = 0
message = ""
# regular time or PlayTime
hrSb = "TimekprUserConfTodaySettingsSetHrSB"
minSb = "TimekprUserConfTodaySettingsSetMinSB"
# get time to add
timeToAdjust = self._timekprAdminFormBuilder.get_object(hrSb).get_value_as_int() * cons.TK_LIMIT_PER_HOUR
timeToAdjust += self._timekprAdminFormBuilder.get_object(minSb).get_value_as_int() * cons.TK_LIMIT_PER_MINUTE
if pType == "Time":
# call server
result, message = self._timekprAdminConnector.setTimeLeft(userName, pOperation, timeToAdjust)
elif pType == "PlayTime":
# call server
result, message = self._timekprAdminConnector.setPlayTimeLeft(userName, pOperation, timeToAdjust)
# successful call
if result == 0:
if pType == "Time":
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_ADJUSTTIME_PROCESSED"))
elif pType == "PlayTime":
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_ADJUSTTIME_PROCESSED"))
# reset values to form
for rCtrl in (hrSb, minSb):
# no value to add
self._timekprAdminFormBuilder.get_object(rCtrl).set_value(0)
else:
# status
self.setTimekprStatus(False, message)
# check the connection
self.checkConnection()
# recalc the control state
self.calculateUserTodayControlAvailability()
def applyUserLimitConfigurationChanges(self):
"""Apply configuration changes to server"""
# get what's changed
changeControl = self.calculateUserConfigControlAvailability(False)
# get username
userName = self.getSelectedUserName()
# initial values
result = 0
message = ""
changeCnt = 0
# loop through all changes
for rKey, rVal in changeControl.items():
# changed
if rVal["st"]:
# check what element we have, depending on that call different interface
# ## week limit ##
if rKey == "TimekprUserConfWkMonLimitsLSWK":
# call server
result, message = self._timekprAdminConnector.setTimeLimitForWeek(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeLimitWeek"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_WKMONADJUSTTIME_PROCESSED"))
# ## month limit ##
elif rKey == "TimekprUserConfWkMonLimitsLSMON":
# call server
result, message = self._timekprAdminConnector.setTimeLimitForMonth(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeLimitMonth"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_WKMONADJUSTTIME_PROCESSED"))
# ## day config ##
elif rKey == "TimekprUserWeekDaysLSD":
# call server
result, message = self._timekprAdminConnector.setAllowedDays(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeLimitDays"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_ALLOWEDDAYS_PROCESSED"))
# ## limits per allowed days ###
elif rKey == "TimekprUserWeekDaysLSL":
# call server
result, message = self._timekprAdminConnector.setTimeLimitForDays(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeLimitDaysLimits"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_TIMELIMITS_PROCESSED"))
# ## hour allowance activities ###
elif rKey == "TimekprHourIntervalsLS":
# loop through changed day hours
for rDay in rVal["val"]:
# day changed
hrs = None if self._tkSavedCfg["timeLimitDaysHoursActual"][rDay] == self._tkSavedCfg["timeLimitDaysHoursSaved"][rDay] else self._tkSavedCfg["timeLimitDaysHoursActual"][rDay]
# hours changed
if hrs is not None:
# call server
result, message = self._timekprAdminConnector.setAllowedHours(userName, rDay, hrs)
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeLimitDaysHoursSaved"][rDay] = self._tkSavedCfg["timeLimitDaysHoursActual"][rDay].copy()
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_ALLOWEDHOURS_PROCESSED"))
else:
# finish
break
# if all ok
if result != 0:
# status
self.setTimekprStatus(False, message)
# that's it
break
# fine
if result != 0:
# check the connection
self.checkConnection()
# override messages in case more then one option was processed
elif changeCnt > 1:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_USER_LIMIT_CONFIGURATION_SAVED"))
# recalc the control state
self.calculateUserConfigControlAvailability()
def applyUserPlayTimeConfigurationChanges(self):
"""Apply configuration changes to server"""
# get what's changed
changeControl = self.calculateUserPlayTimeConfigControlAvailability(False)
# get username
userName = self.getSelectedUserName()
# initial values
result = 0
message = ""
changeCnt = 0
# loop through all changes
for rKey, rVal in changeControl.items():
# changed
if rVal["st"]:
# check what element we have, depending on that call different interface
# ## PlayTime enabled ##
if rKey == "TimekprUserPlayTimeEnableCB":
# call server
result, message = self._timekprAdminConnector.setPlayTimeEnabled(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["playTimeEnabled"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_ENABLEMENT_PROCESSED"))
# ## PlayTime override enabled ##
elif rKey == "TimekprUserPlayTimeOverrideEnableCB":
# call server
result, message = self._timekprAdminConnector.setPlayTimeLimitOverride(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["playTimeOverrideEnabled"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_OVERRIDE_PROCESSED"))
# ## PlayTime allowed during unaccounted intervals ##
elif rKey == "TimekprUserPlayTimeUnaccountedIntervalsEnabledCB":
# call server
result, message = self._timekprAdminConnector.setPlayTimeUnaccountedIntervalsEnabled(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["playTimeUnaccountedIntervalsEnabled"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_ALLOWED_UNLIMITED_INTERVALS_PROCESSED"))
# ## PlayTime day config ##
elif rKey == "TimekprUserPlayTimeLimitsLSD":
# call server
result, message = self._timekprAdminConnector.setPlayTimeAllowedDays(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["playTimeLimitDays"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_ALLOWEDDAYS_PROCESSED"))
# ## PlayTime limits per allowed days ###
elif rKey == "TimekprUserPlayTimeLimitsLSL":
# call server
result, message = self._timekprAdminConnector.setPlayTimeLimitsForDays(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["playTimeLimitDaysLimits"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_TIMELIMITS_PROCESSED"))
# ## PlayTime activities ###
elif rKey == "TimekprUserPlayTimeProcessesLS":
# call server
result, message = self._timekprAdminConnector.setPlayTimeActivities(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["playTimeActivities"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_PT_ACTIVITIES_PROCESSED"))
# if all ok
if result != 0:
# status
self.setTimekprStatus(False, message)
# that's it
break
# fine
if result != 0:
# check the connection
self.checkConnection()
# override messages in case more then one option was processed
elif changeCnt > 1:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_USER_PT_LIMIT_CONFIGURATION_SAVED"))
# recalc the control state
self.calculateUserPlayTimeConfigControlAvailability()
def applyUserAdditionalConfigurationChanges(self):
"""Apply configuration changes to server"""
# get what's changed
changeControl = self.calculateUserAdditionalConfigControlAvailability(False)
# get username
userName = self.getSelectedUserName()
# initial values
result = 0
message = ""
changeCnt = 0
# loop through all changes
for rKey, rVal in changeControl.items():
# changed
if rVal["st"]:
# check what element we have, depending on that call different interface
# ## Track inactive enabled ##
if rKey == "TimekprUserConfTodaySettingsTrackInactiveCB":
# call server
result, message = self._timekprAdminConnector.setTrackInactive(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeTrackInactive"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_TRACKINACTIVE_PROCESSED"))
# ## Hide try icon enabled ##
elif rKey == "TimekprUserConfTodaySettingsHideTrayIconCB":
# call server
result, message = self._timekprAdminConnector.setHideTrayIcon(userName, rVal["val"])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeHideTrayIcon"] = rVal["val"]
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_HIDETRAYICON_PROCESSED"))
# ## Lockout type / interval ##
elif rKey == "TimekprUserConfAddOptsLockoutType":
# call server
result, message = self._timekprAdminConnector.setLockoutType(userName, rVal["val"][0], rVal["val"][1], rVal["val"][2])
# successful call
if result == 0:
# cnt
changeCnt += 1
# set internal state
self._tkSavedCfg["timeLockoutType"] = rVal["val"][0]
self._tkSavedCfg["timeWakeInterval"] = "%s;%s" % ( rVal["val"][1], rVal["val"][2])
# print success message
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_LOCKOUTTYPE_PROCESSED"))
# if all ok
if result != 0:
# status
self.setTimekprStatus(False, message)
# that's it
break
# fine
if result != 0:
# check the connection
self.checkConnection()
# override messages in case more then one option was processed
elif changeCnt > 1:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_USER_ADDOPTS_CONFIGURATION_SAVED"))
# recalc the control state
self.calculateUserAdditionalConfigControlAvailability()
# --------------- GTK signal methods --------------- #
# --------------- timekpr configuration GTK signal helper methods --------------- #
def addElementToList(self, pName):
"""Add tracked session"""
lstSt = self._timekprAdminFormBuilder.get_object("%sLS" % (pName))
lstTw = self._timekprAdminFormBuilder.get_object("%sTreeView" % (pName))
lstLen = len(lstSt)
# check if the last one is not empty (no need to add more empty rows)
if lstLen > 0 and lstSt[lstLen-1][0] != "":
# add empty item
lstSt.append([""])
# scroll to end
lstTw.set_cursor(lstLen)
lstTw.scroll_to_cell(lstLen)
# verify control availability
self.calculateTimekprConfigControlAvailability()
def removeElementFromList(self, pName):
"""Remove tracked session"""
# defaults
elemIdx = self.getSelectedConfigElement("%sTreeView" % (pName))
rIdx = 0
# remove selected item
for rIt in self._timekprAdminFormBuilder.get_object("%sLS" % (pName)):
# check what to remove
if elemIdx == rIdx:
# remove
self._timekprAdminFormBuilder.get_object("%sLS" % (pName)).remove(rIt.iter)
# this is it
break
# count further
rIdx += 1
# verify control availability
self.calculateTimekprConfigControlAvailability()
def setTimekprExcludedTrackedValue(self, pPath, pText, pWhat):
"""Set internal representation of in-place edited value"""
# def
lStN = None
if pWhat == "TrackedSessions":
# store
lStN = "TimekprTrackingSessionsLS"
elif pWhat == "ExcludedSessions":
# store
lStN = "TimekprExcludedSessionsLS"
elif pWhat == "ExcludedUsers":
# store
lStN = "TimekprExcludedUsersLS"
# handled type
if lStN is not None:
# get store object
self._timekprAdminFormBuilder.get_object(lStN)[pPath][0] = pText
# calculate control availability
self.calculateTimekprConfigControlAvailability()
# --------------- timekpr configuration GTK signal methods --------------- #
def configControlSwitchesChanged(self, evt):
"""Change any control item (warning, poll, etc.)"""
# verify control availability
self.calculateTimekprConfigControlAvailability()
def trackedSessionsAddClicked(self, evt):
"""Add tracked session"""
self.addElementToList("TimekprTrackingSessions")
def trackedSessionsRemoveClicked(self, evt):
"""Remove tracked session"""
self.removeElementFromList("TimekprTrackingSessions")
def excludedSessionsAddClicked(self, evt):
"""Add excluded session"""
self.addElementToList("TimekprExcludedSessions")
def excludedSessionsRemoveClicked(self, evt):
"""Remove excluded session"""
self.removeElementFromList("TimekprExcludedSessions")
def excludedUsersAddClicked(self, evt):
"""Add excluded session"""
self.addElementToList("TimekprExcludedUsers")
def excludedUsersRemoveClicked(self, evt):
"""Remove excluded user"""
self.removeElementFromList("TimekprExcludedUsers")
def timekprTrackedSessionsEdited(self, widget, path, text):
"""Tracked session values edited"""
self.setTimekprExcludedTrackedValue(path, text, "TrackedSessions")
def timekprExcludedSessionsEdited(self, widget, path, text):
"""Excluded session values edited"""
self.setTimekprExcludedTrackedValue(path, text, "ExcludedSessions")
def timekprExcludedUsersEdited(self, widget, path, text):
"""Excluded user values edited"""
self.setTimekprExcludedTrackedValue(path, text, "ExcludedUsers")
def applyTimekprConfigurationChangesClicked(self, evt):
"""Apply configuration changes"""
# disable button so it cannot be triggered again
self._timekprAdminFormBuilder.get_object("TimekprConfigurationApplyBT").set_sensitive(False)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprConfigurationApplyBT").modify_fg(Gtk.StateFlags.NORMAL, None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprConfigurationTabLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if self._timekprAdminFormBuilder.get_object("TimekprConfigurationApplyBT").get_sensitive() else None)
# process setting
self.applyTimekprConfigurationChanges()
# --------------- user selection GTK signal methods --------------- #
def userSelectionChanged(self, evt, pInfoLvl=None):
"""User selected"""
# get username
userName = self.getSelectedUserName()
# only if connected
if userName is not None and userName != "":
# get user config
self.retrieveUserInfoAndConfig(userName, cons.TK_CL_INF_FULL if pInfoLvl is None else pInfoLvl)
else:
# disable all
self.toggleUserConfigControls(False, True)
def userConfigurationRefreshClicked(self, evt):
"""User requested config restore from server"""
self._timekprAdminFormBuilder.get_object("TimekprUserSelectionCB").emit("changed")
# --------------- today page GTK signal methods --------------- #
def todayAddTimeChanged(self, evt):
"""Call control calculations when time has been added"""
# recalc control availability
self.calculateUserTodayControlAvailability()
def todayAddPlayTimeChanged(self, evt):
"""Call control calculations when time has been added"""
# recalc control availability
self.calculateUserTodayControlAvailability()
def todayAddTimeClicked(self, evt):
"""Add time to user"""
# disable button so it cannot be triggered again
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetAddBT").set_sensitive(False)
# get choice
type = "Time" if self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsChoiceTimeRB").get_active() else "PlayTime"
# process setting
self.applyUserTodayConfigurationChanges(type, "+")
def todaySubtractTimeClicked(self, evt):
"""Subtract time from user"""
# disable button so it cannot be triggered again
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetSubractBT").set_sensitive(False)
# get choice
type = "Time" if self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsChoiceTimeRB").get_active() else "PlayTime"
# process setting
self.applyUserTodayConfigurationChanges(type, "-")
def todaySetTimeClicked(self, evt):
"""Set exact time for user"""
# disable button so it cannot be triggered again
self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsSetSetBT").set_sensitive(False)
# get choice
type = "Time" if self._timekprAdminFormBuilder.get_object("TimekprUserConfTodaySettingsChoiceTimeRB").get_active() else "PlayTime"
# process setting
self.applyUserTodayConfigurationChanges(type, "=")
# --------------- limit configuration GTK signal helper methods --------------- #
def verifyAndSetHourInterval(self, path, text, pIsFrom):
"""Verify and set hour values"""
# store
intervalSt = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")
# value before
secsBefore = intervalSt[path][4 if pIsFrom else 5]
# def
secs = self.verifyAndCalcLimit(text, "h")
# if we could calculate seconds (i.e. entered text is correct)
if secs is not None:
# if values before and after does not change (or initial ""), we do nothing
if secsBefore != secs or (intervalSt[path][0] == -1 and intervalSt[path][1 if pIsFrom else 2] == ""):
# format secs
text = self.formatTimeStr(secs)
# set values
intervalSt[path][1 if pIsFrom else 2] = text
intervalSt[path][4 if pIsFrom else 5] = secs
# reset id
intervalSt[path][0] = -1
# calculate control availability
self.calculateUserConfigControlAvailability()
def verifyAndSetWeeklyLimits(self, path, text):
"""Verify and set weekly values"""
# store
limitsSt = self._timekprAdminFormBuilder.get_object("TimekprUserConfWkMonLimitsLS")
# value before
secsBefore = limitsSt[path][2]
# def
secs = self.verifyAndCalcLimit(text, "w" if limitsSt[path][0] == "WK" else "m")
# if we could calculate seconds (i.e. entered text is correct)
if secs is not None:
# if values before and after does not change, we do nothing
if secsBefore != secs:
# format secs
text = self.formatTimeStr(secs, pFormatSecs=True, pFormatDays=True)
# set values
limitsSt[path][3] = text
limitsSt[path][2] = secs
# calculate control availability
self.calculateUserConfigControlAvailability()
def verifyAndSetDayLimits(self, path, text, pIsPlayTime=False):
"""Verify and set daily values"""
# store
limitsSt = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS" if pIsPlayTime else "TimekprWeekDaysLS")
controlFnc = self.calculateUserPlayTimeConfigControlAvailability if pIsPlayTime else self.calculateUserConfigControlAvailability
# value before
secsBefore = limitsSt[path][3]
# def
secs = self.verifyAndCalcLimit(text, "d")
# if we could calculate seconds (i.e. entered text is correct)
if secs is not None:
# if values before and after does not change, we do nothing
if secsBefore != secs:
# format secs
text = self.formatTimeStr(secs, pFormatSecs=True)
# set values
limitsSt[path][4] = text
limitsSt[path][3] = secs
# calculate control availability
controlFnc()
def areHoursVerified(self):
"""Return whether all hours have been verified"""
# def
result = True
# store
intervalSt = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")
# loop through all
for rInt in intervalSt:
# check whether it has been verified (id = -1 means not verified)
if rInt[0] < 0 and not (rInt[4] == 0 and rInt[4] == rInt[5]):
# not verified
result = False
break
# result
return result
def verifyAndCalcLimit(self, pLimitStr, pLimitType):
"""Parse user entered limit"""
# add limits
def _addLimit(pSecs, pAddSecs):
# add limits
return pAddSecs if pSecs is None else pSecs + pAddSecs
# def
secs = None
# determine interval type and calculate seconds according to it
try:
# days to weeks/month
if pLimitType in ("w", "m") and _DAY_HOUR_MIN_SEC_REGEXP.match(pLimitStr):
# calculate seconds
secs = min(_addLimit(secs, int(_DAY_HOUR_MIN_SEC_REGEXP.sub(r"\4", pLimitStr))), cons.TK_LIMIT_PER_MINUTE)
# days to weeks/month
if pLimitType in ("w", "m", "d") and _DAY_HOUR_MIN_REGEXP.match(pLimitStr):
# calculate minutes
secs = min(_addLimit(secs, int(_DAY_HOUR_MIN_REGEXP.sub(r"\3", pLimitStr)) * (cons.TK_LIMIT_PER_MINUTE if pLimitType != "d" else 1)), cons.TK_LIMIT_PER_HOUR if pLimitType != "d" else cons.TK_LIMIT_PER_MINUTE)
# hours/minutes or days/hours
if _HOUR_MIN_REGEXP.match(pLimitStr):
# calculate seconds
secs = min(_addLimit(secs, int(_HOUR_MIN_REGEXP.sub(r"\2", pLimitStr)) * (cons.TK_LIMIT_PER_MINUTE if pLimitType in ("h", "d") else cons.TK_LIMIT_PER_HOUR)), cons.TK_LIMIT_PER_HOUR if pLimitType in ("h", "d") else cons.TK_LIMIT_PER_DAY)
# hours / days
if _HOUR_REGEXP.match(pLimitStr):
# calculate seconds
secs = min(_addLimit(secs, int(_HOUR_REGEXP.sub(r"\1", pLimitStr)) * (cons.TK_LIMIT_PER_HOUR if pLimitType in ("h", "d") else cons.TK_LIMIT_PER_DAY)), cons.TK_LIMIT_PER_DAY if pLimitType in ("h", "d") else cons.TK_LIMIT_PER_MONTH)
# no error
if secs is not None:
# normalize total seconds
secs = min(secs, cons.TK_LIMIT_PER_MONTH if pLimitType == "m" else cons.TK_LIMIT_PER_WEEK if pLimitType == "w" else cons.TK_LIMIT_PER_DAY)
except:
# we do not care about any errors
secs = None
# return seconds
return secs
# --------------- limit configuration GTK signal methods --------------- #
def dayLimitsIncreaseClicked(self, evt):
"""Increase time limits"""
self.adjustTimeLimits(pType="DailyLimits", pAdd=True)
def dayLimitsDecreaseClicked(self, evt):
"""Decrease time limits"""
self.adjustTimeLimits(pType="DailyLimits", pAdd=False)
def dailyLimitsDaySelectionChanged(self, evt):
"""Set up intervals on GUI day change"""
# refresh the child
days = self.getSelectedDays()
# get current seconds
dt = datetime.now().replace(microsecond=0)
dtd = str(datetime.date(dt).isoweekday())
dts = int((dt - datetime.now().replace(microsecond=0, second=0, minute=0, hour=0)).total_seconds())
selIdx = 0
# only if there is smth selected
if len(days) > 0:
# go to last day (this cannot and should not be calculated for everything)
dayIdx = days[len(days)-1]["idx"]
dayNum = days[len(days)-1]["nr"]
# whether day is enabled
enabled = self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")[dayIdx][2]
limit = self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")[dayIdx][3]
# clear out existing intervals
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").clear()
# fill intervals only if that day exists
if dayNum in self._tkSavedCfg["timeLimitDaysHoursActual"] and enabled and limit > 0:
# idx
idx = 0
found = False
# fill the intervals
for rInterval in self.getIntervalList(dayNum):
# exists
found = True
# determine which is the current hour
selIdx = idx if rInterval[2] <= dts <= rInterval[3] and dtd == dayNum else selIdx
# fill in the intervals
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").append([idx, rInterval[0], rInterval[1], dayNum, rInterval[2], rInterval[3], self._ROWCOL_OK, self._ROWSTYLE_OK, rInterval[4]])
idx += 1
# found
if found:
# set selection to found row
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").set_cursor(selIdx)
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").scroll_to_cell(selIdx)
def dayAvailabilityChanged(self, widget, path):
"""Change minutes depending on day availability"""
# get list store
limitSt = self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")
# flip the checkbox
limitSt[path][2] = not limitSt[path][2]
# if we have a day, restore limits
if limitSt[path][2]:
# if we have limits set in background store, restore them
if limitSt[path][0] in self._tkSavedCfg["timeLimitDays"]:
limitSt[path][3] = self._tkSavedCfg["timeLimitDaysLimits"][self._tkSavedCfg["timeLimitDays"].index(limitSt[path][0])]
else:
limitSt[path][3] = 0
# format string too
limitSt[path][4] = self.formatTimeStr(limitSt[path][3], True)
# restore intervals from saved state
self._tkSavedCfg["timeLimitDaysHoursActual"][limitSt[path][0]] = self._tkSavedCfg["timeLimitDaysHoursSaved"][limitSt[path][0]].copy()
# enable interval refresh
self._timekprAdminFormBuilder.get_object("TimekprWeekDaysTreeView").get_selection().emit("changed")
else:
# reset hours & minutes
limitSt[path][3] = 0
limitSt[path][4] = _NO_TIME_LABEL
# intervals store
intervalsSt = self._timekprAdminFormBuilder.get_object("TimekprWeekDaysLS")
# change interval selection as well
for rHour in range(0, 23+1):
self._tkSavedCfg["timeLimitDaysHoursActual"][intervalsSt[path][0]][str(rHour)] = {cons.TK_CTRL_SMIN: 0, cons.TK_CTRL_EMIN: cons.TK_LIMIT_PER_MINUTE, cons.TK_CTRL_UACC: False}
# clear stuff and disable intervals
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS").clear()
# recalc control availability
self.calculateUserConfigControlAvailability()
def intervalsIncreaseClicked(self, evt):
"""Increase time limits"""
self.adjustTimeLimits(pType="Intervals", pAdd=True)
def intervalsDecreaseClicked(self, evt):
"""Decrease time limits"""
self.adjustTimeLimits(pType="Intervals", pAdd=False)
def userLimitsHourFromEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.verifyAndSetHourInterval(path, text, pIsFrom=True)
def userLimitsHourToEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.verifyAndSetHourInterval(path, text, pIsFrom=False)
def userLimitsWeeklyLimitsEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.verifyAndSetWeeklyLimits(path, text)
def userLimitsDailyLimitsEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.verifyAndSetDayLimits(path, text)
def userLimitsDailyPlayTimeLimitsEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.verifyAndSetDayLimits(path, text, pIsPlayTime=True)
def userLimitsHourUnaccountableToggled(self, widget, path):
"""Set internal representation of in-place edited value"""
# store
intSt = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")
# flip the checkbox
intSt[path][8] = not intSt[path][8]
# we need to rebuild hours
self.rebuildHoursFromIntervals()
# calculate control availability
self.calculateUserConfigControlAvailability()
def addHourIntervalClicked(self, evt):
"""Add PlayTime activity placeholder to the list"""
limitsSt = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")
limitsTw = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView")
limitsLen = len(limitsSt)
# add
addRow = True
# check if the last one is not empty (no need to add more empty rows)
if (limitsLen > 0 and limitsSt[limitsLen-1][4] == 0 and limitsSt[limitsLen-1][5] == 0):
addRow = False
# we can add the row
if addRow:
# get day to which add the interval
days = self.getSelectedDays()
# if it's not selected
if len(days) < 1:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_NODAY_SELECTED"))
else:
# normalize day
calcDay = days[len(days)-1]["nr"]
# add
limitsSt.append([-1, "", "", calcDay, 0, 0, self._ROWCOL_OK, self._ROWSTYLE_OK, False])
# scroll to end
limitsTw.set_cursor(limitsLen)
limitsTw.scroll_to_cell(limitsLen)
# verify control availability
self.calculateUserConfigControlAvailability()
def removeHourIntervalClicked(self, evt):
"""Remove hour interval"""
# defaults
limitsSt = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")
elemIdx = self.getSelectedConfigElement("TimekprHourIntervalsTreeView")
rIdx = 0
# only if something is selected
if elemIdx is not None:
# remove selected item
for rIt in limitsSt:
if elemIdx == rIdx:
# remove
limitsSt.remove(rIt.iter)
break
# count further
rIdx += 1
# verify hours
self.verifyHourIntervals(None)
# verify control availability
self.calculateUserConfigControlAvailability()
# status change
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVAL_REMOVED"))
def wkMonLimitsIncreaseClicked(self, evt):
"""Increase week / month time limits"""
self.adjustTimeLimits(pType="WeekMonthLimits", pAdd=True)
def wkMonLimitsDecreaseClicked(self, evt):
"""Decrease week / month time limits"""
self.adjustTimeLimits(pType="WeekMonthLimits", pAdd=False)
def applyUserLimitConfigurationChangesClicked(self, evt):
"""Call set methods for changes"""
# disable button so it cannot be triggered again
self._timekprAdminFormBuilder.get_object("TimekprUserConfDaySettingsApplyBT").set_sensitive(False)
# process setting
self.applyUserLimitConfigurationChanges()
# --------------- PlayTime limit configuration GTK signal methods --------------- #
def userPlayTimeEnabledChanged(self, evt):
"""PlayTime enablement changed"""
self.calculateUserPlayTimeConfigControlAvailability()
def userPlayTimeOverrideEnabledChanged(self, evt):
"""PlayTime override enablement changed"""
self.calculateUserPlayTimeConfigControlAvailability()
def userPlayTimeUnaccountedIntervalsEnabledChanged(self, evt):
"""PlayTime allowed during unaccounted intervals enablement changed"""
self.calculateUserPlayTimeConfigControlAvailability()
def playTimeLimitsIncreaseClicked(self, evt):
"""Increase PlayTime limits"""
self.adjustTimeLimits(pType="PlayTimeLimits", pAdd=True)
def playTimeLimitsDecreaseClicked(self, evt):
"""Decrease PlayTime limits"""
self.adjustTimeLimits(pType="PlayTimeLimits", pAdd=False)
def dayPlayTimeAvailabilityChanged(self, widget, path):
"""Change PlayTime minutes depending on day availability"""
# get list store
limitSt = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLimitsLS")
# flip the checkbox
limitSt[path][2] = not limitSt[path][2]
# if we have a day, restore limits
if limitSt[path][2]:
# if we have limits set in background store, restore them
if limitSt[path][0] in self._tkSavedCfg["playTimeLimitDays"]:
limitSt[path][3] = self._tkSavedCfg["playTimeLimitDaysLimits"][self._tkSavedCfg["playTimeLimitDays"].index(limitSt[path][0])]
else:
limitSt[path][3] = 0
# format string too
limitSt[path][4] = self.formatTimeStr(limitSt[path][3], True)
else:
# reset hours & minutes
limitSt[path][3] = 0
limitSt[path][4] = _NO_TIME_LABEL
# recalc control availability
self.calculateUserPlayTimeConfigControlAvailability()
def addPlayTimeActivityClicked(self, evt):
"""Add PlayTime activity placeholder to the list"""
limitsSt = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS")
limitsTw = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesTreeView")
PTActivityLen = len(limitsSt)
# add
addRow = True
# check if the last one is not empty (no need to add more empty rows)
if (PTActivityLen > 0 and limitsSt[PTActivityLen-1][1] == ""):
addRow = False
# we can add the row
if addRow:
# get last index
PTActivityIdx = str(int(limitsSt[PTActivityLen-1][0]) + 1 if PTActivityLen > 0 else 1)
# add
limitsSt.append([PTActivityIdx, "", ""])
# scroll to end
limitsTw.set_cursor(PTActivityLen)
limitsTw.scroll_to_cell(PTActivityLen)
limitsTw.get_selection().emit("changed")
def removePlayTimeActivityClicked(self, evt):
"""Remove excluded user"""
# defaults
limitsSt = self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS")
elemIdx = self.getSelectedConfigElement("TimekprUserPlayTimeProcessesTreeView")
rIdx = 0
# only if something is selected
if elemIdx is not None:
# remove selected item
for rIt in limitsSt:
if elemIdx == rIdx:
# remove
limitsSt.remove(rIt.iter)
elif elemIdx < rIdx:
# adjust next element index
limitsSt[rIdx-1][0] = str(rIdx)
# count further
rIdx += 1
# verify control availability
self.calculateUserPlayTimeConfigControlAvailability()
def playTimeActivityMaskEntryEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
# store value
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS")[path][1] = text
# recalc control availability
self.calculateUserPlayTimeConfigControlAvailability()
def playTimeActivityDescriptionEntryEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
# store value
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesLS")[path][2] = text
# recalc control availability
self.calculateUserPlayTimeConfigControlAvailability()
def applyUserPlayTimeConfigurationChangesClicked(self, evt):
"""Apply PlayTime configuration changes"""
# disable button so it cannot be triggered again
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesApplyBT").set_sensitive(False)
# color the buttons for ppl to see them better
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesApplyBT").modify_fg(Gtk.StateFlags.NORMAL, None)
# tab color
self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeLabel").modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red") if self._timekprAdminFormBuilder.get_object("TimekprUserPlayTimeProcessesApplyBT").get_sensitive() else None)
# process setting
self.applyUserPlayTimeConfigurationChanges()
# --------------- additional page configuration GTK signal methods --------------- #
def trackInactiveChanged(self, evt):
"""Call control calculations when inactive flag has been changed"""
# recalc control availability
self.calculateUserAdditionalConfigControlAvailability()
def hideTrayIconChanged(self, evt):
"""Call control calculations when hide icon has been changed"""
# recalc control availability
self.calculateUserAdditionalConfigControlAvailability()
def lockoutTypeGroupChanged(self, evt):
"""Call control calculations when restriction / lockout type has been changed"""
# recalc control availability
self.calculateUserAdditionalConfigControlAvailability()
def wakeUpIntervalChanged(self, evt):
"""Call control calculations when restriction / lockout wake up hours have been changed"""
# recalc control availability
self.calculateUserAdditionalConfigControlAvailability()
def applyUserAdditionalConfigurationChangesClicked(self, evt):
"""Apply additional configuration changes"""
# process setting
self.applyUserAdditionalConfigurationChanges()
def navigateTreeView(self, treeview, event):
# get key name
keyname = Gdk.keyval_name(event.keyval)
path, col = treeview.get_cursor()
# if there are no cols
if path is None:
return
# only visible columns
columns = [c for c in treeview.get_columns() if c.get_visible()]
colnum = columns.index(col)
next_column = None
# check key
if keyname == "Tab" or keyname == "Esc":
# wrap
if colnum + 1 < len(columns):
# choose next col
next_column = columns[colnum + 1]
else:
# get model
tmodel = treeview.get_model()
# model exists
if tmodel is not None:
titer = tmodel.iter_next(tmodel.get_iter(path))
# there are cols
if titer is None:
titer = tmodel.get_iter_first()
# next
path = tmodel.get_path(titer)
next_column = columns[0]
# key handling
if next_column is not None:
if keyname == 'Tab':
GLib.timeout_add(1, treeview.set_cursor, path, next_column, True)
elif keyname == 'Escape':
pass
# --------------- helper methods for signal methods --------------- #
def adjustTimeLimits(self, pType, pAdd):
"""Recalc total seconds"""
# get objects depending on type
# rb format:
# array of: checkbutton, seconds to add, check limit, seconds in liststore, string secs in liststore, control to execute, format seconds, format days
if pType == "PlayTimeLimits":
tw = "TimekprUserPlayTimeLimitsTreeView"
ls = "TimekprUserPlayTimeLimitsLS"
rb = [["TimekprUserPlayTimeLimitsHrRB", cons.TK_LIMIT_PER_HOUR, cons.TK_LIMIT_PER_DAY, 3, 4, self.calculateUserPlayTimeConfigControlAvailability, True, False],
["TimekprUserPlayTimeLimitsMinRB", cons.TK_LIMIT_PER_MINUTE, cons.TK_LIMIT_PER_DAY, 3, 4, self.calculateUserPlayTimeConfigControlAvailability, True, False]]
elif pType == "DailyLimits":
tw = "TimekprWeekDaysTreeView"
ls = "TimekprWeekDaysLS"
rb = [["TimekprUserTimeLimitsHrRB", cons.TK_LIMIT_PER_HOUR, cons.TK_LIMIT_PER_DAY, 3, 4, self.calculateUserConfigControlAvailability, True, False],
["TimekprUserTimeLimitsMinRB", cons.TK_LIMIT_PER_MINUTE, cons.TK_LIMIT_PER_DAY, 3, 4, self.calculateUserConfigControlAvailability, True, False]]
elif pType == "WeekMonthLimits":
tw = "TimekprUserConfWkMonLimitsTreeView"
ls = "TimekprUserConfWkMonLimitsLS"
rb = [["TimekprUserConfWkMonLimitsAdjustmentDayRB", cons.TK_LIMIT_PER_DAY, None, 2, 3, self.calculateUserConfigControlAvailability, True, True],
["TimekprUserConfWkMonLimitsAdjustmentHrRB", cons.TK_LIMIT_PER_HOUR, None, 2, 3, self.calculateUserConfigControlAvailability, True, True],
["TimekprUserConfWkMonLimitsAdjustmentMinRB", cons.TK_LIMIT_PER_MINUTE, None, 2, 3, self.calculateUserConfigControlAvailability, True, True]]
elif pType == "Intervals":
tw = "TimekprHourIntervalsTreeView"
ls = "TimekprHourIntervalsLS"
rb = [["TimekprUserConfDaySettingsSetDaysIntervalsHrRB", cons.TK_LIMIT_PER_HOUR, cons.TK_LIMIT_PER_DAY, None, None, self.calculateUserConfigControlAvailability, False, False],
["TimekprUserConfDaySettingsSetDaysIntervalsMinRB", cons.TK_LIMIT_PER_MINUTE, cons.TK_LIMIT_PER_DAY, None, None, self.calculateUserConfigControlAvailability, False, False]]
# depending on selected items
isFrom = self._timekprAdminFormBuilder.get_object("TimekprUserConfDaySettingsSetDaysIntervalsFromRB").get_active()
# set seconds and display idx
for rIdx in range(0, len(rb)):
# depending whether start or end is selected we change that
rb[rIdx][3] = 4 if isFrom else 5 # seconds
rb[rIdx][4] = 1 if isFrom else 2 # time (seconds) to display
# opposite indexes (special case)
rb[rIdx].append(4 if not isFrom else 5)
rb[rIdx].append(1 if not isFrom else 2)
# get selected rows
(tm, paths) = self._timekprAdminFormBuilder.get_object(tw).get_selection().get_selected_rows()
# if rows were selected
if paths is not None:
# def
adj = None
# determine adjustment amount
for rRb in (rRb for rRb in rb if self._timekprAdminFormBuilder.get_object(rRb[0]).get_active()):
# if checked
adj = rRb
# check type found
if adj is not None:
# limits store
limitsSt = self._timekprAdminFormBuilder.get_object(ls)
# idx
for path in paths:
# get idx
idx = tm.get_path(tm.get_iter(path))[0]
# for DailyLimits and PlayTimeLimits we do not need to adjust inactive rows
if pType in ("DailyLimits", "PlayTimeLimits"):
# check if day is active
if not limitsSt[idx][2]:
# we do not process disabled days
continue
# for week / month a row that is selected makes limits different (two rows in LS)
elif pType == "WeekMonthLimits":
if limitsSt[idx][0] == "WK":
adj[2] = cons.TK_LIMIT_PER_WEEK
elif limitsSt[idx][0] == "MON":
adj[2] = cons.TK_LIMIT_PER_MONTH
# adjust value
secs = int(limitsSt[idx][adj[3]]) + adj[1] * (1 if pAdd else -1)
secs = min(adj[2], max(0, secs))
# set up new value
limitsSt[idx][adj[3]] = secs
limitsSt[idx][adj[4]] = self.formatTimeStr(secs, adj[6], adj[7])
# in case of intervals, we need to manage the end / start of it too
if pType == "Intervals":
# now check the other end whether start is later than end (from both sides)
if (isFrom and limitsSt[idx][adj[3]] > limitsSt[idx][adj[8]]) or (not isFrom and limitsSt[idx][adj[3]] < limitsSt[idx][adj[8]]):
# equalize
limitsSt[idx][adj[8]] = limitsSt[idx][adj[3]]
# format string
limitsSt[idx][adj[9]] = self.formatTimeStr(limitsSt[idx][adj[8]], adj[6], adj[7])
# verify control availability by calling configured method (check setup)
adj[5]()
else:
# status
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_NODAY_SELECTED"))
def verifyHourIntervals(self, evt):
"""Verify hour intervals"""
# limits store
intervalsSt = self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS")
intervalIdx = -1
result = False
# loop through all entered intervals
for rInt in intervalsSt:
# get interval seconds
intervalIdx += 1
secondsFrom = intervalsSt[intervalIdx][4]
secondsTo = intervalsSt[intervalIdx][5]
# do not check empty intervals
if secondsFrom == 0 == secondsTo and intervalsSt[intervalIdx][0] < 0:
continue
# len
intervalsLen = len(intervalsSt)
# whether interval is valid
intervalOverlaps = False
intervalHourConflictStart = False
intervalHourConflictEnd = False
intervalDuplicate = False
intervalSameStartEnd = False
intervalStartEndMismatch = False
# check intervals
for rIdx in range(0, intervalsLen):
# interval boundaries
fromSecs = intervalsSt[rIdx][4]
toSecs = intervalsSt[rIdx][5]
result = False
errIdx = None
# do not check empty intervals
if fromSecs == 0 == toSecs and intervalsSt[rIdx][0] < 0:
continue
# start is the same as end
if secondsFrom == secondsTo:
# this is it
intervalSameStartEnd = True
elif secondsFrom > secondsTo:
# this is it
intervalStartEndMismatch = True
# these are for all hours
if intervalIdx != rIdx and not (intervalSameStartEnd or intervalStartEndMismatch):
# check whether user tries to insert duplicate iterval
if fromSecs == secondsFrom or toSecs == secondsTo:
# this is it
intervalDuplicate = True
# check whether start is betwen existing interval
elif secondsFrom < fromSecs < secondsTo or secondsFrom < toSecs < secondsTo:
# this is it
intervalOverlaps = True
# check whether start is betwen existing interval
elif fromSecs < secondsFrom < toSecs or fromSecs < secondsTo < toSecs:
# this is it
intervalOverlaps = True
# check whether user tries to insert iterval that doesn'y overlaps with start / end hours from other intervals, but are on the same day
elif int(fromSecs/cons.TK_LIMIT_PER_HOUR) <= int(secondsFrom/cons.TK_LIMIT_PER_HOUR) <= int(toSecs/cons.TK_LIMIT_PER_HOUR) and int(secondsFrom/cons.TK_LIMIT_PER_HOUR) * cons.TK_LIMIT_PER_HOUR not in (secondsTo, toSecs):
# this is it
intervalHourConflictStart = True
# check whether user tries to insert iterval that doesn'y overlaps with start / end hours from other intervals, but are on the same day
elif int(fromSecs/cons.TK_LIMIT_PER_HOUR) <= int(secondsTo/cons.TK_LIMIT_PER_HOUR) <= int(toSecs/cons.TK_LIMIT_PER_HOUR) and int(secondsTo/cons.TK_LIMIT_PER_HOUR) * cons.TK_LIMIT_PER_HOUR not in (secondsTo, toSecs):
# this is it
intervalHourConflictEnd = True
# get final result whether intervals are ok
result = (intervalOverlaps or intervalHourConflictStart or intervalHourConflictEnd or intervalDuplicate or intervalSameStartEnd or intervalStartEndMismatch)
# if we have errors
if intervalsSt[rIdx][7] != result or result:
# mark offending row
intervalsSt[rIdx][6] = self._ROWCOL_NOK if result else self._ROWCOL_OK
intervalsSt[rIdx][7] = self._ROWSTYLE_NOK if result else self._ROWSTYLE_OK
# mark checked row too
intervalsSt[intervalIdx][6] = self._ROWCOL_NOK if result else self._ROWCOL_OK
intervalsSt[intervalIdx][7] = self._ROWSTYLE_NOK if result else self._ROWSTYLE_OK
# we have a problem
if result:
# set ofending row and finish up
errIdx = rIdx
# interval is not ok, remove id
intervalsSt[rIdx][0] = -1
# exit on first error
break
else:
# assingn an id
intervalsSt[intervalIdx][0] = intervalIdx
# scroll to first error
if result:
# set status message if fail
if intervalOverlaps:
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVAL_OVERLAP_DETECTED"))
elif intervalHourConflictStart:
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVALSTART_CONFLICT_DETECTED"))
elif intervalHourConflictEnd:
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVALEND_CONFLICT_DETECTED"))
elif intervalDuplicate:
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVAL_DUPLICATE_DETECTED"))
elif intervalSameStartEnd:
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVAL_STARTENDEQUAL_DETECTED"))
elif intervalStartEndMismatch:
self.setTimekprStatus(False, msg.getTranslation("TK_MSG_STATUS_INTERVAL_ENDLESSTHANSTART_DETECTED"))
else:
# sort
self.sortHourIntervals()
self.setTimekprStatus(False, "")
# scroll
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").set_cursor(errIdx)
self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsTreeView").scroll_to_cell(errIdx)
# fin
break
if not result:
# sort intervals
self.sortHourIntervals()
# rebuild hour intervals
self.rebuildHoursFromIntervals()
# calculate control availability
self.calculateUserConfigControlAvailability()
def rebuildHoursFromIntervals(self):
"""Rebuild hours from intervals in GUI, representation to user is different than actual config"""
# get days
days = self.getSelectedDays()
# day is here
if len(days) > 0:
# first day
day = days[0]["nr"]
# clear internal hour representation
self._tkSavedCfg["timeLimitDaysHoursActual"][day] = {}
# remove selected item
for rIt in self._timekprAdminFormBuilder.get_object("TimekprHourIntervalsLS"):
# start time
calcTime = cons.TK_DATETIME_START + timedelta(seconds=rIt[4])
# total seconds
totalSeconds = rIt[5] - rIt[4]
# failover
maxIt = 30
# now loop through time in interval
while totalSeconds > 0 and maxIt > 0:
# failover
maxIt -= 1
# hour
calcHour = str(calcTime.hour)
# build up hour
self._tkSavedCfg["timeLimitDaysHoursActual"][day][calcHour] = {cons.TK_CTRL_SMIN: calcTime.minute, cons.TK_CTRL_EMIN: None, cons.TK_CTRL_UACC: rIt[8]}
# calc end of the hour
timeToSubtract = min(cons.TK_LIMIT_PER_HOUR - calcTime.minute * cons.TK_LIMIT_PER_MINUTE, totalSeconds)
# adjust time
calcTime += timedelta(seconds=timeToSubtract)
# subtract hour
totalSeconds -= timeToSubtract
# add end hour
self._tkSavedCfg["timeLimitDaysHoursActual"][day][calcHour][cons.TK_CTRL_EMIN] = cons.TK_LIMIT_PER_MINUTE if calcTime.minute == 0 else calcTime.minute
# set this up to all selected rows
for rDay in days:
# set to all days (except the one we modified)
if rDay["nr"] != day:
# copy config
self._tkSavedCfg["timeLimitDaysHoursActual"][rDay["nr"]] = self._tkSavedCfg["timeLimitDaysHoursActual"][day].copy()
# --------------- additional and misc methods --------------- #
def timekprLogoClicked(self, evt, smth):
"""Open link to development support page, disabled, and maybe never will be enabled :)"""
if 1 == 1:
pass
elif os.geteuid() == 0:
# copy to clipboard and show message
Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD).set_text(cons.TK_DEV_SUPPORT_PAGE, -1)
tkrMsg = Gtk.MessageDialog(parent=self._timekprAdminForm, flags=Gtk.DialogFlags.MODAL, type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, message_format="\nDonations link copied to clipbard!\nPlease paste the address in internet browser.\nThanks for your support!")
tkrMsg.run()
tkrMsg.destroy()
else:
# open link
webbrowser.open(cons.TK_DEV_SUPPORT_PAGE, new=2, autoraise=True)
def closePropertiesSignal(self, evt, smth):
"""Close the config form"""
# close
self._mainLoop.quit()
# flush log
log.autoFlushLogFile(True)
timekpr-next/client/gui/__init__.py 000664 001750 001750 00000000000 13476006650 021345 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/gui/clientgui.py 000644 001750 001750 00000113476 15122253261 021605 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
import gi
import os
import re
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from datetime import datetime, timedelta
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.common.constants import messages as msg
# constant
_NO_TIME_LABEL = "--:--:--"
_NO_TIME_LABEL_SHORT = "--:--"
_NO_TIME_LIMIT_LABEL = "--:--:--:--"
_HOUR_REGEXP = re.compile("^([0-9]{1,2})$")
_HOUR_MIN_REGEXP = re.compile("^([0-9]{1,2}):([0-9]{1,2})$")
class timekprGUI(object):
"""Main class for supporting timekpr forms"""
def __init__(self, pTimekprVersion, pTimekprClientConfig, pUsername, pUserNameFull):
"""Initialize gui"""
# set up base variables
self._userName = pUsername
self._timekprVersion = pTimekprVersion
self._timekprClientConfig = pTimekprClientConfig
self._timekprPTPageNr = 2
# sets up limit variables
self._timeSpent = None
self._timeSpentWeek = None
self._timeSpentMonth = None
self._timeInactive = None
self._timeLeftToday = None
self._timeLeftContinous = None
self._timeTrackInactive = True
self._timeTimeLimitOverridePT = False
self._timeUnaccountedIntervalsFlagPT = False
self._timeSpentPT = None
self._timeLeftPT = None
self._timePTActivityCntStr = "0"
self._limitConfig = {}
# change tracking
self._configChanged = False
# ## forms builders ##
# init about builder
self._timekprAboutDialogBuilder = Gtk.Builder()
# get our dialog
self._timekprAboutDialogBuilder.add_from_file(os.path.join(self._timekprClientConfig.getTimekprSharedDir(), "client/forms", "about.glade"))
# get main form (to set various runtime things)
self._timekprAboutDialog = self._timekprAboutDialogBuilder.get_object("timekprAboutDialog")
# init config builder
self._timekprConfigDialogBuilder = Gtk.Builder()
# get our dialog
self._timekprConfigDialogBuilder.add_from_file(os.path.join(self._timekprClientConfig.getTimekprSharedDir(), "client/forms", "client.glade"))
# get main form (to set various runtime things)
self._timekprConfigDialog = self._timekprConfigDialogBuilder.get_object("timekprConfigDialog")
self._timekprAboutDialogBuilder.connect_signals(self)
self._timekprConfigDialogBuilder.connect_signals(self)
# set up username (this does not change)
self._timekprConfigDialogBuilder.get_object("timekprUsernameLB").set_text("%s (%s)" % (self._userName, pUserNameFull) if pUserNameFull != "" else self._userName)
# this sets up columns for days config
col = Gtk.TreeViewColumn("Day", Gtk.CellRendererText(), text=1)
col.set_min_width(100)
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysTreeview").append_column(col)
col = Gtk.TreeViewColumn("Limit", Gtk.CellRendererText(), text=2)
col.set_min_width(60)
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysTreeview").append_column(col)
# this sets up columns for interval list
# interval string
col = Gtk.TreeViewColumn("Interval", Gtk.CellRendererText(), text=0)
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsTreeview").append_column(col)
# unaccountable interval column
col = Gtk.TreeViewColumn("Unaccounted", Gtk.CellRendererText(), text=1)
col.set_min_width(10)
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsTreeview").append_column(col)
# PlayTime
# this sets up columns for limits list
col = Gtk.TreeViewColumn("Day", Gtk.CellRendererText(), text=1)
col.set_min_width(100)
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsDaysTreeview").append_column(col)
col = Gtk.TreeViewColumn("Limit", Gtk.CellRendererText(), text=2)
col.set_min_width(60)
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsDaysTreeview").append_column(col)
# this sets up columns for process list
col = Gtk.TreeViewColumn("Day", Gtk.CellRendererText(), text=0)
col.set_min_width(140)
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsApplsTreeview").append_column(col)
# hide PT page by default
self._timekprConfigDialogBuilder.get_object("timekprConfigNotebook").get_nth_page(self._timekprPTPageNr).set_visible(False)
# hide PT config as well
self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigPlayTimeGrid").set_visible(False)
# initial config (everything is to the max)
for i in range(0, 7):
# set up default limits
self._limitConfig[str(i+1)] = {cons.TK_CTRL_LIMITD: None, cons.TK_CTRL_INT: [[None, None, False]]}
# initialize week and month limits
self._limitConfig[cons.TK_CTRL_LIMITW] = {cons.TK_CTRL_LIMITW: None}
self._limitConfig[cons.TK_CTRL_LIMITM] = {cons.TK_CTRL_LIMITM: None}
# ## notification configuration ##
# Less than
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_NOTIF_CONFIG_TIME_PHLD_LABEL"))
rend.connect("edited", self.userTimeEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_NOTIF_CONFIG_TIME_LABEL"), rend, text=1)
col.set_min_width(90)
self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigTreeView").append_column(col)
# importance
rend = Gtk.CellRendererCombo()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_NOTIF_CONFIG_IMPORTANCE_PHLD_LABEL"))
rend.set_property("model", self._timekprConfigDialogBuilder.get_object("TimekprNotificationPrioritiesLS"))
rend.set_property("text-column", 1)
rend.set_property("has-entry", False)
rend.connect("edited", self.userPriorityEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_NOTIF_CONFIG_IMPORTANCE_LABEL"), rend, text=3)
col.set_min_width(120)
self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigTreeView").append_column(col)
# clear
self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS").clear()
# ## PlayTime notification configuration ##
# Less than
rend = Gtk.CellRendererText()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_NOTIF_CONFIG_TIME_PHLD_LABEL"))
rend.connect("edited", self.userPlayTimeEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_NOTIF_CONFIG_TIME_LABEL"), rend, text=1)
col.set_min_width(90)
self._timekprConfigDialogBuilder.get_object("TimekprUserPlayTimeNotificationConfigTreeView").append_column(col)
# importance
rend = Gtk.CellRendererCombo()
rend.set_property("editable", True)
rend.set_property("placeholder-text", msg.getTranslation("TK_MSG_NOTIF_CONFIG_IMPORTANCE_PHLD_LABEL"))
rend.set_property("model", self._timekprConfigDialogBuilder.get_object("TimekprNotificationPrioritiesLS"))
rend.set_property("text-column", 1)
rend.set_property("has-entry", False)
rend.connect("edited", self.userPlayTimePriorityEdited)
col = Gtk.TreeViewColumn(msg.getTranslation("TK_MSG_NOTIF_CONFIG_IMPORTANCE_LABEL"), rend, text=3)
col.set_min_width(120)
self._timekprConfigDialogBuilder.get_object("TimekprUserPlayTimeNotificationConfigTreeView").append_column(col)
# clear
self._timekprConfigDialogBuilder.get_object("TimekprUserPlayTimeNotificationConfigLS").clear()
# status
self.setStatus(msg.getTranslation("TK_MSG_STATUS_STARTED"))
# --------------- TMP (move to proper places later) --------------- #
def userTimeEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.setTimeValue(path, text, pConfType="Time")
def userPlayTimeEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.setTimeValue(path, text, pConfType="PlayTime")
def userPriorityEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.setPriorityValue(path, text, "Time")
def userPlayTimePriorityEdited(self, widget, path, text):
"""Set internal representation of in-place edited value"""
self.setPriorityValue(path, text, "PlayTime")
def setTimeValue(self, path, text, pConfType):
"""Verify and set time string values"""
# element
prioLs = "TimekprUserNotificationConfigLS" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigLS"
# store
timelSt = self._timekprConfigDialogBuilder.get_object(prioLs)
# value before
secsBefore = timelSt[path][0]
secs = None
# verify values
if _HOUR_REGEXP.match(text):
# calculate seconds
secs = min(int(_HOUR_REGEXP.sub(r"\1", text)) * cons.TK_LIMIT_PER_HOUR, cons.TK_LIMIT_PER_DAY)
elif _HOUR_MIN_REGEXP.match(text):
# calculate seconds
secs = min(int(_HOUR_MIN_REGEXP.sub(r"\1", text)) * cons.TK_LIMIT_PER_HOUR + int(_HOUR_MIN_REGEXP.sub(r"\2", text)) * cons.TK_LIMIT_PER_MINUTE, cons.TK_LIMIT_PER_DAY)
# if we could calculate seconds (i.e. entered text is correct)
if secs is not None:
# only if changed
if secsBefore != secs:
# check if we have this interval already
dupl = [rPrio for rPrio in timelSt if rPrio[0] == secs]
# we can not allow duplicates
if not len(dupl) > 0:
# format secs
textStr = self.formatTimeStr(cons.TK_DATETIME_START + timedelta(seconds=secs), "s")
# set values
timelSt[path][0] = secs
timelSt[path][1] = textStr
# sort
self.sortNotificationConfig(pConfType)
# verify controls too
self.processConfigChanged()
def setPriorityValue(self, path, text, pConfType):
"""Verify and set time string values"""
# element
prioLs = "TimekprUserNotificationConfigLS" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigLS"
# store
priolSt = self._timekprConfigDialogBuilder.get_object(prioLs)
# value before
prioBefore = priolSt[path][3]
# only if priority actuall changed
if prioBefore != text:
# find selected value
val = [(rVal[0], rVal[1]) for rVal in self._timekprConfigDialogBuilder.get_object("TimekprNotificationPrioritiesLS") if rVal[1] == text]
# set values
priolSt[path][2] = val[0][0]
priolSt[path][3] = val[0][1]
# verify controls too
self.processConfigChanged()
def addNotificationConfigClicked(self, evt):
"""Add notification interval to the list"""
self.addNotificationConf("Time")
def addPlayTimeNotificationConfigClicked(self, evt):
"""Add notification interval to the list"""
self.addNotificationConf("PlayTime")
def removeNotificationConfigClicked(self, evt):
"""Remove notification interval"""
self.removeNotificationConf("Time")
def removePlayTimeNotificationConfigClicked(self, evt):
"""Remove notification interval"""
self.removeNotificationConf("PlayTime")
def addNotificationConf(self, pConfType):
"""Add notification interval to the list"""
prioSt = self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigLS")
prioTw = self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigTreeView" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigTreeView")
prioLen = len(prioSt)
# add
addRow = True
# check if the last one is not empty (no need to add more empty rows)
if (prioLen > 0 and prioSt[prioLen-1][0] < 0):
addRow = False
# we can add the row
if addRow:
# add
prioSt.append([-1, "", "", ""])
# scroll to end
prioTw.set_cursor(prioLen)
prioTw.scroll_to_cell(prioLen)
def removeNotificationConf(self, pConfType):
"""Remove notification interval"""
# defaults
prioSt = self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigLS")
# refresh the child
(tm, ti) = self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigTreeView" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigTreeView").get_selection().get_selected()
# only if there is smth selected
elemIdx = tm.get_path(ti)[0] if ti is not None else None
# only if something is selected
if elemIdx is not None:
rIdx = 0
# remove selected item
for rIt in prioSt:
if elemIdx == rIdx:
# remove
prioSt.remove(rIt.iter)
# count further
rIdx += 1
# verify controls too
self.processConfigChanged()
def sortNotificationConfig(self, pConfType):
"""Sort notification config for ease of use"""
# element
prioSt = self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS" if pConfType == "Time" else "TimekprUserPlayTimeNotificationConfigLS")
# sort vairables
prio = {}
rIdx = 0
# prepare sort
for rIt in prioSt:
prio[rIt[0]] = rIdx
# count further
rIdx += 1
# set sort order
sortedPrio = []
# set up proper order
for rKey in sorted(prio, reverse=True):
# append to order
sortedPrio.append(prio[rKey])
# reorder rows in liststore
prioSt.reorder(sortedPrio)
# --------------- helper methods --------------- #
def formatTimeStr(self, pTime, pFormatType="f"):
"""Format time for output on form"""
# f - full, s - short, t - time
# final result
timeStr = None
if pTime is None:
timeStr = _NO_TIME_LABEL_SHORT if pFormatType == "s" else _NO_TIME_LABEL if pFormatType == "t" else _NO_TIME_LIMIT_LABEL
else:
# calculate days
days = (pTime - cons.TK_DATETIME_START).days
# calculate hours and mins
hrMin = "%s:%s" % (("24" if pFormatType != "f" and days >= 1 else str(pTime.hour)).rjust(2, "0"), str(pTime.minute).rjust(2, "0"))
# calculate secs
secs = str(pTime.second).rjust(2, "0")
# final composition
# for limit time (h:m:s)
if pFormatType == "t":
timeStr = "%s:%s" % (hrMin, secs)
# for full time (d:h:m:s)
else:
timeStr = "%s:%s:%s" % (str(days).rjust(2, "0"), hrMin, secs) if pFormatType != "s" else hrMin
# return
return timeStr
def renewUserConfiguration(self):
"""Update configuration options"""
# if speech is not supported, we disable and uncheck the box
self._timekprConfigDialogBuilder.get_object("timekprUseSpeechNotifCB").set_sensitive(self._timekprClientConfig.getIsNotificationSpeechSupported())
# if sound is not supported by libnotify implementation, we disable and uncheck the box
self._timekprConfigDialogBuilder.get_object("timekprUseNotificationSoundCB").set_sensitive(self._timekprClientConfig.getIsNotificationSoundSupported())
# user config
self._timekprConfigDialogBuilder.get_object("timekprLimitChangeNotifCB").set_active(self._timekprClientConfig.getClientShowLimitNotifications())
self._timekprConfigDialogBuilder.get_object("timekprShowAllNotifCB").set_active(self._timekprClientConfig.getClientShowAllNotifications())
self._timekprConfigDialogBuilder.get_object("timekprUseSpeechNotifCB").set_active(self._timekprClientConfig.getClientUseSpeechNotifications())
self._timekprConfigDialogBuilder.get_object("timekprShowSecondsCB").set_active(self._timekprClientConfig.getClientShowSeconds())
self._timekprConfigDialogBuilder.get_object("timekprUseNotificationSoundCB").set_active(self._timekprClientConfig.getClientUseNotificationSound())
self._timekprConfigDialogBuilder.get_object("timekprNotificationTimeoutSB").set_value(self._timekprClientConfig.getClientNotificationTimeout())
self._timekprConfigDialogBuilder.get_object("timekprNotificationTimeoutCriticalSB").set_value(self._timekprClientConfig.getClientNotificationTimeoutCritical())
self._timekprConfigDialogBuilder.get_object("timekprLogLevelSB").set_value(self._timekprClientConfig.getClientLogLevel())
# priority config
prioConfSt = self._timekprConfigDialogBuilder.get_object("TimekprNotificationPrioritiesLS")
# load notification priorities
prioSt = self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS")
prioSt.clear()
for rPrio in self._timekprClientConfig.getClientNotificationLevels():
# append intervals
val = [(rVal[0], rVal[1]) for rVal in prioConfSt if rVal[0] == cons.TK_PRIO_LVL_MAP[rPrio[1]]]
prioSt.append([rPrio[0], self.formatTimeStr(cons.TK_DATETIME_START + timedelta(seconds=rPrio[0]), "s"), val[0][0], val[0][1]])
# sort configd
self.sortNotificationConfig("Time")
# load PlayTime notification priorities
prioSt = self._timekprConfigDialogBuilder.get_object("TimekprUserPlayTimeNotificationConfigLS")
prioSt.clear()
for rPrio in self._timekprClientConfig.getClientPlayTimeNotificationLevels():
# append intervals
val = [(rVal[0], rVal[1]) for rVal in prioConfSt if rVal[0] == cons.TK_PRIO_LVL_MAP[rPrio[1]]]
prioSt.append([rPrio[0], self.formatTimeStr(cons.TK_DATETIME_START + timedelta(seconds=rPrio[0]), "s"), val[0][0], val[0][1]])
# sort config
self.sortNotificationConfig("PlayTime")
# verify controls too
self.processConfigChanged()
def renewLimits(self, pTimeInformation=None):
"""Renew information to be show for user in GUI"""
# sets time left
if pTimeInformation is not None:
# limits
self._timeSpent = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_SPENT])
self._timeSpentWeek = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_SPENTW])
self._timeSpentMonth = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_SPENTM])
self._timeInactive = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_SLEEP])
self._timeLeftToday = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_LEFTD])
self._timeLeftContinous = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_LEFT])
self._timeTrackInactive = True if pTimeInformation[cons.TK_CTRL_TRACK] else False
self._timeTimeLimitOverridePT = bool(pTimeInformation[cons.TK_CTRL_PTTLO]) if cons.TK_CTRL_PTTLO in pTimeInformation else False
self._timeUnaccountedIntervalsFlagPT = bool(pTimeInformation[cons.TK_CTRL_PTAUH]) if cons.TK_CTRL_PTAUH in pTimeInformation else False
self._timeSpentPT = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_PTSPD]) if cons.TK_CTRL_PTSPD in pTimeInformation else None
self._timeLeftPT = cons.TK_DATETIME_START + timedelta(seconds=pTimeInformation[cons.TK_CTRL_PTLPD]) if cons.TK_CTRL_PTLPD in pTimeInformation else None
self._timePTActivityCntStr = str(pTimeInformation[cons.TK_CTRL_PTLSTC] if cons.TK_CTRL_PTLSTC in pTimeInformation else 0)
# calculate strings to show (and show only those, which have data)
timeSpentStr = self.formatTimeStr(self._timeSpent)
timeSpentWeekStr = self.formatTimeStr(self._timeSpentWeek)
timeSpentMonthStr = self.formatTimeStr(self._timeSpentMonth)
timeSleepStr = self.formatTimeStr(self._timeInactive)
timeLeftTodayStr = self.formatTimeStr(self._timeLeftToday)
timeLeftTotalStr = self.formatTimeStr(self._timeLeftContinous)
timeSpentPTStr = self.formatTimeStr(self._timeSpentPT)
timeLeftPTStr = self.formatTimeStr(self._timeLeftPT if self._timeLeftPT is None else min(self._timeLeftPT, self._timeLeftToday)) if not self._timeTimeLimitOverridePT else _NO_TIME_LABEL
# sets up stuff
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoTimeSpeneLB").set_text(timeSpentStr)
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoTimeSpentWeeeLB").set_text(timeSpentWeekStr)
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoTimeSpentMonteLB").set_text(timeSpentMonthStr)
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoTimeInactiveLB").set_text(timeSleepStr)
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoTimeLeftTodaeLB").set_text(timeLeftTodayStr)
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoContTimeLefeLB").set_text(timeLeftTotalStr)
self._timekprConfigDialogBuilder.get_object("timekprLimitInfoTrackInactiveCB").set_active(self._timeTrackInactive)
self._timekprConfigDialogBuilder.get_object("timekprPTLimitInfoTimeLimitOverrideLB").set_active(self._timeTimeLimitOverridePT)
self._timekprConfigDialogBuilder.get_object("timekprPTLimitInfoUnaccountedIntervalsFlagLB").set_active(self._timeUnaccountedIntervalsFlagPT)
self._timekprConfigDialogBuilder.get_object("timekprPTLimitInfoTimeSpentTodayLB").set_text(timeSpentPTStr)
self._timekprConfigDialogBuilder.get_object("timekprPTLimitInfoTimeLeftTodayLB").set_text(timeLeftPTStr)
self._timekprConfigDialogBuilder.get_object("timekprPTLimitInfoActivityCountLB").set_text(self._timePTActivityCntStr)
def setStatus(self, pStatus):
"""Change status of timekpr"""
if pStatus is not None:
# get main status
statusBar = self._timekprConfigDialogBuilder.get_object("timekprStatusBar")
contextId = statusBar.get_context_id("status")
# pop existing message and add new one
statusBar.remove_all(contextId)
statusBar.push(contextId, pStatus)
def renewLimitConfiguration(self, pLimits=None):
"""Renew information to be show for user"""
# if there is smth
if pLimits is not None:
# new limits appeared
self._limitConfig = pLimits
# hide PT page by default
enablePT = False
# clear out days / limits / processes
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysLS").clear()
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsDaysLS").clear()
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsActsLS").clear()
# go in sorted order
for rKey in sorted(self._limitConfig):
# some of configuration needs different approach
if rKey in (cons.TK_CTRL_LIMITW, cons.TK_CTRL_LIMITM):
# set locally
if self._limitConfig[rKey][rKey] is not None:
# limit
timeLimitWKMON = cons.TK_DATETIME_START + timedelta(seconds=self._limitConfig[rKey][rKey])
# limit
timeLimitWKMONStr = self.formatTimeStr(timeLimitWKMON)
else:
timeLimitWKMONStr = _NO_TIME_LIMIT_LABEL
# set week limit
if rKey == cons.TK_CTRL_LIMITW:
# set up limits
self._timekprConfigDialogBuilder.get_object("timekprLimitForWeeeLB").set_text(timeLimitWKMONStr)
elif rKey == cons.TK_CTRL_LIMITM:
# set up limits
self._timekprConfigDialogBuilder.get_object("timekprLimitForMonteLB").set_text(timeLimitWKMONStr)
# check for override
elif rKey == cons.TK_CTRL_PTTLO:
# if enabled
self._timeTimeLimitOverridePT = True if bool(self._limitConfig[rKey][rKey]) else False
# check for allowed during unaccounted intervals
elif rKey == cons.TK_CTRL_PTAUH:
# if enabled
self._timeUnaccountedIntervalsFlagPT = True if bool(self._limitConfig[rKey][rKey]) else False
# for the days limits
elif rKey in ("1", "2", "3", "4", "5", "6", "7"):
# get time limit string
timeLimitStr = self.formatTimeStr(cons.TK_DATETIME_START + timedelta(seconds=self._limitConfig[rKey][cons.TK_CTRL_LIMITD]) if self._limitConfig[rKey][cons.TK_CTRL_LIMITD] is not None else None, "t")
# add limit to the list
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysLS").append([rKey, (cons.TK_DATETIME_START + timedelta(days=int(rKey)-1)).strftime("%A"), "%s" % (timeLimitStr)])
# current day
currDay = datetime.now().isoweekday()-1
# calculate day index for scrolling
dayIdx = 0
finalDayIdx = None
# PT limits are processed separately due to override detection
if cons.TK_CTRL_PTLMT in self._limitConfig and cons.TK_CTRL_PTLST in self._limitConfig and cons.TK_CTRL_PTTLE in self._limitConfig:
# PlayTime
for rKey in (cons.TK_CTRL_PTLMT, cons.TK_CTRL_PTLST, cons.TK_CTRL_PTTLE):
# PT enable
if rKey == cons.TK_CTRL_PTTLE:
# enable PT
enablePT = bool(self._limitConfig[rKey][cons.TK_CTRL_PTTLE])
# PT limits
elif rKey == cons.TK_CTRL_PTLMT:
# for all days
for rDay in self._limitConfig[rKey][cons.TK_CTRL_PTLMT]:
# count
dayIdx += 1
# if override enabled, we do not show limits because that's not meaningful
timeLimitStr = self.formatTimeStr(cons.TK_DATETIME_START + timedelta(seconds=int(rDay[1])) if not self._timeTimeLimitOverridePT else None, "t")
# add to the list
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsDaysLS").append([str(rDay[0]), (cons.TK_DATETIME_START + timedelta(days=int(rDay[0])-1)).strftime("%A"), "%s" % (timeLimitStr)])
# if alllowed list has current day
if currDay == int(rDay[0]) + 1:
# index
finalDayIdx = dayIdx
# PT process list
elif rKey == cons.TK_CTRL_PTLST:
# all activities (source array format: 0 - friendly name, 1 - process name)
for rAppl in self._limitConfig[rKey][cons.TK_CTRL_PTLST]:
# add process to the list
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsActsLS").append(["%s" % (rAppl[1] if rAppl[1] != "" else rAppl[0])])
# determine curent day and point to it
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysTreeview").set_cursor(currDay)
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysTreeview").scroll_to_cell(currDay)
# do the same for PT
if finalDayIdx is not None:
# scroll to current day
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsDaysTreeview").set_cursor(currDay)
self._timekprConfigDialogBuilder.get_object("timekprPTAllowedDaysLimitsDaysTreeview").scroll_to_cell(currDay)
def processConfigChanged(self):
"""Determine whether config has been changed and enable / disable apply"""
# initial
configChanged = False
# determine what's changed
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprLimitChangeNotifCB").get_active() != self._timekprClientConfig.getClientShowLimitNotifications()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprShowAllNotifCB").get_active() != self._timekprClientConfig.getClientShowAllNotifications()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprUseSpeechNotifCB").get_active() != self._timekprClientConfig.getClientUseSpeechNotifications()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprShowSecondsCB").get_active() != self._timekprClientConfig.getClientShowSeconds()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprUseNotificationSoundCB").get_active() != self._timekprClientConfig.getClientUseNotificationSound()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprNotificationTimeoutSB").get_value_as_int() != self._timekprClientConfig.getClientNotificationTimeout()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprNotificationTimeoutCriticalSB").get_value_as_int() != self._timekprClientConfig.getClientNotificationTimeoutCritical()
configChanged = configChanged or self._timekprConfigDialogBuilder.get_object("timekprLogLevelSB").get_value_as_int() != self._timekprClientConfig.getClientLogLevel()
# interval changes
tmpVal = [[rVal[0], cons.TK_PRIO_LVL_MAP[rVal[2]]] for rVal in self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS") if rVal[2] in cons.TK_PRIO_LVL_MAP and rVal[0] > 0]
configChanged = configChanged or self._timekprClientConfig.getClientNotificationLevels() != tmpVal
# interval changes
tmpVal = [[rVal[0], cons.TK_PRIO_LVL_MAP[rVal[2]]] for rVal in self._timekprConfigDialogBuilder.get_object("TimekprUserPlayTimeNotificationConfigLS") if rVal[2] in cons.TK_PRIO_LVL_MAP and rVal[0] > 0]
configChanged = configChanged or self._timekprClientConfig.getClientPlayTimeNotificationLevels() != tmpVal
# this is it
self._timekprConfigDialogBuilder.get_object("timekprSaveBT").set_sensitive(configChanged)
# --------------- init methods --------------- #
def initAboutForm(self):
"""Initialize about form"""
# version
self._timekprAboutDialog.set_version(self._timekprVersion)
# comment
self._timekprAboutDialog.set_comments(msg.getTranslation("TK_MSG_LOGO_LABEL"))
# show up all
self._timekprAboutDialog.show()
self._timekprAboutDialog.run()
# hide for later use
self._timekprAboutDialog.hide()
def initConfigForm(self):
"""Initialize config form"""
# refresh info
self.renewUserConfiguration()
self.renewLimits()
self.renewLimitConfiguration()
self.configPageSwitchSignal()
# show up all
self._timekprConfigDialog.show()
self._timekprConfigDialog.run()
# hide for later use
self._timekprConfigDialog.hide()
# --------------- user clicked methods --------------- #
def clientConfigChangedSignal(self, evt):
"""Process config changed signal"""
self.processConfigChanged()
def daysChangedSignal(self, evt):
"""Refresh intervals when days change"""
# refresh the child
(tm, ti) = self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysDaysTreeview").get_selection().get_selected()
# only if there is smth selected
if ti is not None:
# get current seconds
dt = datetime.now().replace(microsecond=0)
dtd = str(datetime.date(dt).isoweekday())
dts = int((dt - datetime.now().replace(microsecond=0, second=0, minute=0, hour=0)).total_seconds())
idx = 0
selIdx = 0
# clear out existing intervals
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsLS").clear()
# fill intervals only if that day exists
if tm.get_value(ti, 0) in self._limitConfig:
# if no intervals
if not self._limitConfig[tm.get_value(ti, 0)][cons.TK_CTRL_INT]:
# fill in the intervals with empty values
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsLS").append([("%s - %s") % (_NO_TIME_LABEL_SHORT, _NO_TIME_LABEL_SHORT), ""])
else:
# fill the intervals
for r in self._limitConfig[tm.get_value(ti, 0)][cons.TK_CTRL_INT]:
# determine which is the current hour
selIdx = idx if r[0] is not None and r[0] <= dts <= r[1] and dtd == tm.get_value(ti, 0) else selIdx
# if we have no data, we fill this up with nothing
if r[0] is None or (r[0] == 0 and r[1] == 0):
# fill in the intervals with empty values
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsLS").append([("%s - %s") % (_NO_TIME_LABEL_SHORT, _NO_TIME_LABEL_SHORT), ""])
else:
start = (cons.TK_DATETIME_START + timedelta(seconds=r[0]))
end = (cons.TK_DATETIME_START + timedelta(seconds=r[1]))
uacc = "∞" if r[2] else ""
# fill in the intervals
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsLS").append([("%s:%s - %s:%s") % (str(start.hour).rjust(2, "0"), str(start.minute).rjust(2, "0"), str(end.hour).rjust(2, "0") if r[1] < cons.TK_LIMIT_PER_DAY else "24", str(end.minute).rjust(2, "0")), uacc])
# count
idx += 1
# set selection to found row
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsTreeview").set_cursor(selIdx)
self._timekprConfigDialogBuilder.get_object("timekprAllowedDaysIntervalsTreeview").scroll_to_cell(selIdx)
def configPageSwitchSignal(self, nb=None, pg=None, pgn=None):
"""Enable or disable apply on page change"""
# nothing here
pass
def saveUserConfigSignal(self, evt):
"""Save the configuration using config file manager"""
# get config, set config to manager and save it
self._timekprClientConfig.setClientShowLimitNotifications(self._timekprConfigDialogBuilder.get_object("timekprLimitChangeNotifCB").get_active())
self._timekprClientConfig.setClientShowAllNotifications(self._timekprConfigDialogBuilder.get_object("timekprShowAllNotifCB").get_active())
self._timekprClientConfig.setClientUseSpeechNotifications(self._timekprConfigDialogBuilder.get_object("timekprUseSpeechNotifCB").get_active())
self._timekprClientConfig.setClientShowSeconds(self._timekprConfigDialogBuilder.get_object("timekprShowSecondsCB").get_active())
self._timekprClientConfig.setClientUseNotificationSound(self._timekprConfigDialogBuilder.get_object("timekprUseNotificationSoundCB").get_active())
self._timekprClientConfig.setClientNotificationTimeout(self._timekprConfigDialogBuilder.get_object("timekprNotificationTimeoutSB").get_value_as_int())
self._timekprClientConfig.setClientNotificationTimeoutCritical(self._timekprConfigDialogBuilder.get_object("timekprNotificationTimeoutCriticalSB").get_value_as_int())
self._timekprClientConfig.setClientLogLevel(self._timekprConfigDialogBuilder.get_object("timekprLogLevelSB").get_value_as_int())
# save notification priorities
tmpVal = [[rVal[0], cons.TK_PRIO_LVL_MAP[rVal[2]]] for rVal in self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigLS") if rVal[2] in cons.TK_PRIO_LVL_MAP and rVal[0] > 0]
self._timekprClientConfig.setClientNotificationLevels(tmpVal)
# save PlayTime notification priorities
tmpVal = [[rVal[0], cons.TK_PRIO_LVL_MAP[rVal[2]]] for rVal in self._timekprConfigDialogBuilder.get_object("TimekprUserPlayTimeNotificationConfigLS") if rVal[2] in cons.TK_PRIO_LVL_MAP and rVal[0] > 0]
self._timekprClientConfig.setClientPlayTimeNotificationLevels(tmpVal)
# save config
self._timekprClientConfig.saveClientConfig()
# disable apply for now
self._timekprConfigDialogBuilder.get_object("timekprSaveBT").set_sensitive(False)
# enable as well
log.setLogLevel(self._timekprClientConfig.getClientLogLevel())
def closePropertiesSignal(self, evt):
"""Close the config form"""
# close
self._timekprConfigDialog.hide()
def preventDestroyingDialogSignal(self, evt, bs):
"""Prevent destroying the dialog"""
return False
# --------------- helper methods --------------- #
def isPlayTimeAccountingInfoEnabled(self):
"""Whether PlayTime controls are enabled"""
return self._timekprConfigDialogBuilder.get_object("timekprConfigNotebook").get_nth_page(self._timekprPTPageNr).get_visible()
def setPlayTimeAccountingInfoEnabled(self, pState):
"""Whether PlayTime controls are enabled"""
# enable page
self._timekprConfigDialogBuilder.get_object("timekprConfigNotebook").get_nth_page(self._timekprPTPageNr).set_visible(pState)
# enable config
self._timekprConfigDialogBuilder.get_object("TimekprUserNotificationConfigPlayTimeGrid").set_visible(pState)
timekpr-next/client/timekprc.py 000600 001750 001750 00000001570 14575617135 020652 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# imports
import os
import getpass
import sys
import signal
# set up our python path
if "/usr/lib/python3/dist-packages" not in sys.path:
sys.path.append("/usr/lib/python3/dist-packages")
# timekpr imports
from timekpr.client.interface.dbus.daemon import timekprClient
from timekpr.common.utils import misc
# main start
if __name__ == "__main__":
# simple self-running check
if misc.checkAndSetRunning(os.path.splitext(os.path.basename(__file__))[0], getpass.getuser()):
# get out
sys.exit(0)
# get our client
_timekprClient = timekprClient()
# this is needed for appindicator to react to ctrl+c
signal.signal(signal.SIGINT, _timekprClient.finishTimekpr)
signal.signal(signal.SIGTERM, _timekprClient.finishTimekpr)
# start up timekpr client
_timekprClient.startTimekprClient()
timekpr-next/client/__init__.py 000664 001750 001750 00000000000 13476006650 020561 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/timekpra.py 000600 001750 001750 00000001676 14575617135 020657 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Jan 4, 2019
@author: mjasnik
"""
# imports
import os
import getpass
import sys
import signal
# set up our python path
if "/usr/lib/python3/dist-packages" not in sys.path:
sys.path.append("/usr/lib/python3/dist-packages")
# timekpr imports
from timekpr.client.admin.adminprocessor import timekprAdminClient
from timekpr.common.utils import misc
# main start
if __name__ == "__main__":
# simple self-running check
if misc.checkAndSetRunning(os.path.splitext(os.path.basename(__file__))[0], getpass.getuser()):
# get out
sys.exit(0)
# get our admin client
_timekprAdminClient = timekprAdminClient()
# this is needed for admin application to react to ctrl+c gracefully
signal.signal(signal.SIGINT, _timekprAdminClient.finishTimekpr)
signal.signal(signal.SIGTERM, _timekprAdminClient.finishTimekpr)
# start up timekpr admin client
_timekprAdminClient.startTimekprAdminClient(*sys.argv)
timekpr-next/client/admin/ 000775 001750 001750 00000000000 15122253261 017542 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/admin/adminprocessor.py 000644 001750 001750 00000077230 15122253261 023153 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# imports
import os
import getpass
from os import geteuid
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.client.interface.dbus.administration import timekprAdminConnector
from timekpr.common.utils.config import timekprConfig
from timekpr.common.constants import messages as msg
from timekpr.common.utils.misc import findHourStartEndMinutes as findHourStartEndMinutes
from timekpr.common.utils.misc import splitConfigValueNameParam as splitConfigValueNameParam
from timekpr.common.utils.misc import getSecondsFromTimeStr
from timekpr.common.utils.misc import getTimeStrFromSeconds
class timekprAdminClient(object):
"""Main class for holding all client logic (including dbus)"""
# --------------- initialization / control methods --------------- #
def __init__(self):
"""Initialize admin client"""
# get our connector
self._timekprAdminConnector = timekprAdminConnector()
# main object for GUI
self._adminGUI = None
def startTimekprAdminClient(self, *args):
"""Start up timekpr admin (choose gui or cli and start this up)"""
# check whether we need CLI or GUI
lastParam = args[len(args)-1]
timekprForceCLI = False
# configuration init
_timekprConfig = timekprConfig()
# load config
_timekprConfig.loadMainConfiguration()
# init logging
log.setLogging(_timekprConfig.getTimekprLogLevel(), cons.TK_LOG_TEMP_DIR, (cons.TK_LOG_OWNER_ADMIN_SU if geteuid() == 0 else cons.TK_LOG_OWNER_ADMIN), getpass.getuser())
# check for script
if ("/timekpra" in lastParam or "timekpra.py" in lastParam):
# whether we have X running or wayland?
timekprX11Available = os.getenv("DISPLAY") is not None
timekprWaylandAvailable = os.getenv("WAYLAND_DISPLAY") is not None
timekprMirAvailable = os.getenv("MIR_SOCKET") is not None
# if we are required to run graphical thing
if (timekprX11Available or timekprWaylandAvailable or timekprMirAvailable):
# resource dir
_resourcePathGUI = os.path.join(_timekprConfig.getTimekprSharedDir(), "client/forms")
# use GUI
from timekpr.client.gui.admingui import timekprAdminGUI
# load GUI and process from there
self._adminGUI = timekprAdminGUI(cons.TK_VERSION, _resourcePathGUI, getpass.getuser())
# start GUI
self._adminGUI.startAdminGUI()
# nor X nor wayland are available
else:
# print to console
log.consoleOut("%s\n" % (msg.getTranslation("TK_MSG_CONSOLE_GUI_NOT_AVAILABLE")))
# forced CLI"
timekprForceCLI = True
else:
# CLI
timekprForceCLI = True
# for CLI connections
if timekprForceCLI:
# connect
self._timekprAdminConnector.initTimekprConnection(True, False, timekprForceCLI)
# connected?
if self._timekprAdminConnector.isConnected()[1]:
# use CLI
# validate possible parameters and their values, when fine - execute them as well
self.checkAndExecuteAdminCommands(*args)
log.autoFlushLogFile(True)
# --------------- initialization / helper methods --------------- #
def finishTimekpr(self, signal=None, frame=None):
"""Exit timekpr admin GUI gracefully"""
if self._adminGUI is not None:
# finish main thread on GUI`
self._adminGUI.finishTimekpr(signal, frame)
# --------------- parameter validation methods --------------- #
def checkAndExecuteAdminCommands(self, *args):
"""Init connection to timekpr dbus server"""
# initial param len
paramIdx = 0
paramLen = len(args)
adminCmdIncorrect = False
tmpIdx = 0
# determine parameter offset
for rArg in args:
# count offset
tmpIdx += 1
# check for script
if "/timekpra" in rArg or "timekpra.py" in rArg:
paramIdx = tmpIdx
# this gets the command itself (args[0] is the script name)
adminCmd = args[paramIdx] if paramLen > paramIdx else "timekpra"
# now based on params check them out
# this gets saved user list from the server
if adminCmd == "--help":
# fine
pass
# this gets saved user list from the server
elif adminCmd in ("--getuserlist", "--userlist"):
# check param len
if paramLen != paramIdx + 1:
# fail
adminCmdIncorrect = True
else:
# get list
result, message, userList = self._timekprAdminConnector.getUserList()
# process
if result == 0:
# process
self.printUserList(userList)
else:
# log error
log.consoleOut(message)
# this gets user configuration from the server
elif adminCmd in ("--getuserinfo", "--userinfo"):
# check param len
if not (paramLen >= paramIdx + 2 and paramLen <= paramIdx + 3):
# fail
adminCmdIncorrect = True
else:
# get user config
result, message, userConfig = self._timekprAdminConnector.getUserConfigurationAndInformation(args[paramIdx + 1], cons.TK_CL_INF_FULL)
# process
if result == 0:
# process
self.printUserConfig(args[paramIdx + 1], userConfig, True if paramLen > paramIdx + 2 and args[paramIdx + 2] == "-h" else False)
else:
# log error
log.consoleOut(message)
# this gets user configuration from the server
elif adminCmd in ("--getuserinfort", "--userinfort"):
# check param len
if not (paramLen >= paramIdx + 2 and paramLen <= paramIdx + 3):
# fail
adminCmdIncorrect = True
else:
# get user config
result, message, userConfig = self._timekprAdminConnector.getUserConfigurationAndInformation(args[paramIdx + 1], cons.TK_CL_INF_RT)
# process
if result == 0:
# process
self.printUserConfig(args[paramIdx + 1], userConfig, True if paramLen > paramIdx + 2 and args[paramIdx + 2] == "-h" else False)
else:
# log error
log.consoleOut(message)
# this sets allowed days for the user
elif adminCmd == "--setalloweddays":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetAllowedDays(args[paramIdx + 1], args[paramIdx + 2])
# this sets allowed hours per specified day or ALL for every day
elif adminCmd == "--setallowedhours":
# check param len
if paramLen != paramIdx + 4:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetAllowedHours(args[paramIdx + 1], args[paramIdx + 2], args[paramIdx + 3])
# this sets time limits per allowed days
elif adminCmd == "--settimelimits":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetTimeLimits(args[paramIdx + 1], args[paramIdx + 2])
# this sets time limits per week
elif adminCmd == "--settimelimitweek":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetTimeLimitWeek(args[paramIdx + 1], args[paramIdx + 2])
# this sets time limits per month
elif adminCmd == "--settimelimitmonth":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetTimeLimitMonth(args[paramIdx + 1], args[paramIdx + 2])
# this sets whether to track inactive user sessions
elif adminCmd == "--settrackinactive":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetTrackInactive(args[paramIdx + 1], args[paramIdx + 2])
# this sets whether to show tray icon
elif adminCmd == "--sethidetrayicon":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetHideTrayIcon(args[paramIdx + 1], args[paramIdx + 2])
# this sets lockout type for the user
elif adminCmd == "--setlockouttype":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetLockoutType(args[paramIdx + 1], args[paramIdx + 2])
# this sets whether PlayTime is enabled for user
elif adminCmd == "--setplaytimeenabled":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeEnabled(args[paramIdx + 1], args[paramIdx + 2])
# this sets playtime override for user
elif adminCmd == "--setplaytimelimitoverride":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeLimitOverride(args[paramIdx + 1], args[paramIdx + 2])
# this sets playtime allowed during unaccounted intervals for user
elif adminCmd == "--setplaytimeunaccountedintervalsflag":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeUnaccountedIntervalsEnabled(args[paramIdx + 1], args[paramIdx + 2])
# this sets allowed days for PlayTime for the user
elif adminCmd == "--setplaytimealloweddays":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeAllowedDays(args[paramIdx + 1], args[paramIdx + 2])
# this sets PlayTime limits for allowed days for the user
elif adminCmd == "--setplaytimelimits":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeLimits(args[paramIdx + 1], args[paramIdx + 2])
# this sets PlayTime activities for the user
elif adminCmd == "--setplaytimeactivities":
# check param len
if paramLen != paramIdx + 3:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeActivities(args[paramIdx + 1], args[paramIdx + 2])
# this sets time left for the user at current moment
elif adminCmd == "--settimeleft":
# check param len
if paramLen != paramIdx + 4:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetTimeLeft(args[paramIdx + 1], args[paramIdx + 2], args[paramIdx + 3])
# this sets time left for the user at current moment
elif adminCmd == "--setplaytimeleft":
# check param len
if paramLen != paramIdx + 4:
# fail
adminCmdIncorrect = True
else:
# set days
self.processSetPlayTimeLeft(args[paramIdx + 1], args[paramIdx + 2], args[paramIdx + 3])
else:
# out
adminCmdIncorrect = True
# initial order as well as sibling cmds
cmds = ["--help", "--getuserlist", "--userlist", "--getuserinfo", "--userinfo", "--getuserinfort", "--userinfort"]
# check whether command is supported
if ((adminCmd not in cons.TK_USER_ADMIN_COMMANDS and adminCmd not in cons.TK_ADMIN_COMMANDS and adminCmd not in cmds)
or adminCmd == "--help"
or adminCmdIncorrect
):
# fail
if adminCmdIncorrect:
# fail msg
log.consoleOut(msg.getTranslation("TK_MSG_CONSOLE_COMMAND_INCORRECT"), *args, "\n")
# log notice
log.consoleOut("%s\n*) %s\n*) %s\n*) %s\n" % (
msg.getTranslation("TK_MSG_CONSOLE_USAGE_NOTICE_HEAD"),
msg.getTranslation("TK_MSG_CONSOLE_USAGE_NOTICE_TIME"),
msg.getTranslation("TK_MSG_CONSOLE_USAGE_NOTICE_HOURS"),
msg.getTranslation("TK_MSG_CONSOLE_USAGE_NOTICE_DAYS"))
)
# log usage notes text
log.consoleOut("%s\n" % (msg.getTranslation("TK_MSG_CONSOLE_USAGE_NOTES")))
# print initial commands as first
for rCmd in cmds:
# if exists (this needs to be enabled if there are multiple cmds to do the same thing)
if rCmd in cons.TK_USER_ADMIN_COMMANDS:
# log on screen
log.consoleOut(" ", rCmd, cons.TK_USER_ADMIN_COMMANDS[rCmd], "\n")
# print help
for rCmd, rCmdDesc in cons.TK_USER_ADMIN_COMMANDS.items():
# do not print already known commands
if rCmd not in cmds:
# log on screen
log.consoleOut(" ", rCmd, rCmdDesc, "\n")
# --------------- parameter execution methods --------------- #
def printUserList(self, pUserList):
"""Format and print userlist"""
# print to console
log.consoleOut(msg.getTranslation("TK_MSG_CONSOLE_USERS_TOTAL", len(pUserList)))
# loop and print
for rUser in pUserList:
log.consoleOut(rUser[0])
def printUserConfig(self, pUserName, pPrintUserConfig, pEasyTime=False):
"""Format and print user config"""
# print to console
log.consoleOut("# %s" % (msg.getTranslation("TK_MSG_CONSOLE_CONFIG_FOR") % (pUserName)))
# loop and print the same format as ppl will use to set that
for rUserKey, rUserConfig in pPrintUserConfig.items():
# final value
result = ""
# join the lists for days
if rUserKey in ("ALLOWED_WEEKDAYS", "PLAYTIME_ALLOWED_WEEKDAYS"):
# get final value
result = ";".join(str(rVal) for rVal in rUserConfig)
# join the lists for limits
elif rUserKey in ("LIMITS_PER_WEEKDAYS", "PLAYTIME_LIMITS_PER_WEEKDAYS"):
# get final value
result = ";".join(getTimeStrFromSeconds(int(rVal)) if pEasyTime else str(rVal) for rVal in rUserConfig)
# join the lists for days / hours
elif "ALLOWED_HOURS_" in rUserKey:
# print join
if len(rUserConfig) > 0:
# process hours
for rUserHour in sorted(list(map(int, rUserConfig))):
# unaccounted hour
uacc = "!" if rUserConfig[str(rUserHour)][cons.TK_CTRL_UACC] else ""
# get config per hr
hr = "%s" % (rUserHour) if rUserConfig[str(rUserHour)][cons.TK_CTRL_SMIN] <= 0 and rUserConfig[str(rUserHour)][cons.TK_CTRL_EMIN] >= 60 else "%s[%s-%s]" % (rUserHour, rUserConfig[str(rUserHour)][cons.TK_CTRL_SMIN], rUserConfig[str(rUserHour)][cons.TK_CTRL_EMIN])
# empty
result = "%s%s" % (uacc, hr) if result == "" else "%s;%s%s" % (result, uacc, hr)
# bools
elif rUserKey in ("TRACK_INACTIVE", "HIDE_TRAY_ICON", "PLAYTIME_ENABLED", "PLAYTIME_LIMIT_OVERRIDE_ENABLED", "PLAYTIME_UNACCOUNTED_INTERVALS_ENABLED"):
# bools
result = True if str(rUserConfig).lower() == "true" else False
# strings
elif rUserKey in ("PLAYTIME_ACTIVITIES"):
# loop thorhough activities
for rActArr in rUserConfig:
# activity
act = "%s[%s]" % (rActArr[0], rActArr[1]) if rActArr[1] != "" else "%s" % (rActArr[0])
# gather activities
result = "%s" % (act) if result == "" else "%s;%s" % (result, act)
# limits
elif rUserKey in ("LIMIT_PER_WEEK" ,"LIMIT_PER_MONTH" ,"TIME_SPENT_BALANCE" ,"TIME_SPENT_DAY" ,"TIME_SPENT_WEEK" ,"TIME_SPENT_MONTH" ,"TIME_LEFT_DAY" ,"PLAYTIME_LEFT_DAY" ,"PLAYTIME_SPENT_DAY" ,"ACTUAL_TIME_SPENT_SESSION" ,"ACTUAL_TIME_INACTIVE_SESSION" ,"ACTUAL_TIME_SPENT_BALANCE " ,"ACTUAL_TIME_SPENT_DAY" ,"ACTUAL_TIME_LEFT_DAY" ,"ACTUAL_TIME_LEFT_CONTINUOUS" ,"ACTUAL_PLAYTIME_LEFT_DAY"):
# final
result = getTimeStrFromSeconds(int(rUserConfig)) if pEasyTime else str(rUserConfig)
# the rest
else:
# final val
result = str(rUserConfig)
# print final
log.consoleOut("%s: %s" % (rUserKey, result))
def processSetAllowedDays(self, pUserName, pDayList):
"""Process allowed days"""
# defaults
dayMap = []
result = 0
# day map
try:
# try to parse parameters
dayMap = str(pDayList).split(";")
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setAllowedDays(pUserName, dayMap)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetAllowedHours(self, pUserName, pDayNumber, pHourList):
"""Process allowed hours"""
# this is the dict for hour config
allowedHours = {}
result = 0
# allowed hours
try:
# check hours
for rHour in str(pHourList).split(";"):
# get hours and minutes
hour, sMin, eMin, uacc = findHourStartEndMinutes(rHour)
# raise any error in case we can not get parsing right
if hour is None:
# raise
raise ValueError("this does not compute")
# set hours
allowedHours[str(hour)] = {cons.TK_CTRL_SMIN: sMin, cons.TK_CTRL_EMIN: eMin, cons.TK_CTRL_UACC: uacc}
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setAllowedHours(pUserName, pDayNumber, allowedHours)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetTimeLimits(self, pUserName, pDayLimits):
"""Process time limits for days"""
# defaults
dayLimits = []
result = 0
# day limits
try:
# allow empty limits too
if str(pDayLimits) != "":
# convert limits
for rDl in pDayLimits.split(";"):
# add limit
dayLimits += [getSecondsFromTimeStr(rDl)]
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setTimeLimitForDays(pUserName, dayLimits)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetTimeLimitWeek(self, pUserName, pTimeLimitWeek):
"""Process time limits for week"""
# defaults
weekLimit = 0
result = 0
# week limit
try:
# try to parse parameters
weekLimit = int(getSecondsFromTimeStr(pTimeLimitWeek))
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setTimeLimitForWeek(pUserName, weekLimit)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetTimeLimitMonth(self, pUserName, pTimeLimitMonth):
"""Process time limits for month"""
# defaults
monthLimit = 0
result = 0
# week limit
try:
# try to parse parameters
monthLimit = int(getSecondsFromTimeStr(pTimeLimitMonth))
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setTimeLimitForMonth(pUserName, monthLimit)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetTrackInactive(self, pUserName, pTrackInactive):
"""Process track inactive"""
# defaults
trackInactive = None
result = 0
# check
if str(pTrackInactive).lower() not in ("true", "false"):
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % ("please specify true or false")
else:
trackInactive = True if str(pTrackInactive).lower() == "true" else False
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setTrackInactive(pUserName, trackInactive)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetHideTrayIcon(self, pUserName, pHideTrayIcon):
"""Process hide tray icon"""
# defaults
hideTrayIcon = None
result = 0
# check
if str(pHideTrayIcon).lower() not in ("true", "false"):
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % ("please specify true or false")
else:
hideTrayIcon = True if str(pHideTrayIcon).lower() == "true" else False
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setHideTrayIcon(pUserName, hideTrayIcon)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetLockoutType(self, pUserName, pLockoutType):
"""Process lockout type"""
# defaults
result = 0
# parse lockout
lockout = str(pLockoutType).split(";")
lockoutType = lockout[0]
lockoutWakeFrom = lockout[1] if len(lockout) == 3 else '0'
lockoutWakeTo = lockout[2] if len(lockout) == 3 else '23'
# check
if lockoutType not in (cons.TK_CTRL_RES_L, cons.TK_CTRL_RES_S, cons.TK_CTRL_RES_W, cons.TK_CTRL_RES_T, cons.TK_CTRL_RES_K, cons.TK_CTRL_RES_D):
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % ("please specify one of these: %s, %s, %s, %s, %s, %s" % (cons.TK_CTRL_RES_L, cons.TK_CTRL_RES_S, cons.TK_CTRL_RES_W, cons.TK_CTRL_RES_T, cons.TK_CTRL_RES_K, cons.TK_CTRL_RES_D))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setLockoutType(pUserName, lockoutType, lockoutWakeFrom, lockoutWakeTo)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetTimeLeft(self, pUserName, pOperation, pLimit):
"""Process time left"""
# defaults
limit = 0
result = 0
# limit
try:
# try to parse parameters
limit = int(getSecondsFromTimeStr(pLimit))
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setTimeLeft(pUserName, pOperation, limit)
# process
if result != 0:
# log error
log.consoleOut(message)
# --------------- parameter execution methods for PlayTime --------------- #
def processSetPlayTimeEnabled(self, pUserName, pPlayTimeEnabled):
"""Process PlayTime enabled flag"""
# defaults
isPlayTimeEnabled = None
result = 0
# check
if str(pPlayTimeEnabled).lower() not in ("true", "false"):
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % ("please specify true or false")
else:
isPlayTimeEnabled = True if str(pPlayTimeEnabled).lower() == "true" else False
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeEnabled(pUserName, isPlayTimeEnabled)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetPlayTimeLimitOverride(self, pUserName, pPlayTimeLimitOverride):
"""Process PlayTime override flag"""
# defaults
isPlayTimeLimitOverride = None
result = 0
# check
if str(pPlayTimeLimitOverride).lower() not in ("true", "false"):
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % ("please specify true or false")
else:
isPlayTimeLimitOverride = True if str(pPlayTimeLimitOverride).lower() == "true" else False
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeLimitOverride(pUserName, isPlayTimeLimitOverride)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetPlayTimeUnaccountedIntervalsEnabled(self, pUserName, pPlayTimeUnaccountedIntervalsEnabled):
"""Process PlayTime allowed during unaccounted intervals flag"""
# defaults
isPlayTimeUnaccountedIntervalsEnabled = None
result = 0
# check
if str(pPlayTimeUnaccountedIntervalsEnabled).lower() not in ("true", "false"):
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % ("please specify true or false")
else:
isPlayTimeUnaccountedIntervalsEnabled = True if str(pPlayTimeUnaccountedIntervalsEnabled).lower() == "true" else False
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeUnaccountedIntervalsEnabled(pUserName, isPlayTimeUnaccountedIntervalsEnabled)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetPlayTimeAllowedDays(self, pUserName, pPlayTimeDayList):
"""Process allowed days for PlayTime"""
# defaults
dayMap = []
result = 0
# day map
try:
# try to parse parameters
dayMap = str(pPlayTimeDayList).split(";")
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeAllowedDays(pUserName, dayMap)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetPlayTimeLimits(self, pUserName, pPlayTimeDayLimits):
"""Process time limits for allowed days for PlayTime"""
# defaults
dayLimits = []
result = 0
# day limists
try:
# allow empty limits too
if str(pPlayTimeDayLimits) != "":
# convert limits
for rDl in str(pPlayTimeDayLimits).split(";"):
# add limit
dayLimits += [getSecondsFromTimeStr(rDl)]
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeLimitsForDays(pUserName, dayLimits)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetPlayTimeActivities(self, pUserName, pPlayTimeActivities):
"""Process PlayTime activities"""
# defaults
playTimeActivities = []
result = 0
# day limists
try:
# ## try to parse parameters ##
if str(pPlayTimeActivities) != "":
# split activities
for rAct in str(pPlayTimeActivities).split(";"):
# try parsing the names
mask, description = splitConfigValueNameParam(rAct)
# raise any error in case we can not get parsing right
if mask is None:
# raise
raise ValueError("this does not compute")
# set up activity list
playTimeActivities.append([mask, description])
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeActivities(pUserName, playTimeActivities)
# process
if result != 0:
# log error
log.consoleOut(message)
def processSetPlayTimeLeft(self, pUserName, pOperation, pLimit):
"""Process time left"""
# defaults
limit = 0
result = 0
# limit
try:
# try to parse parameters
limit = int(getSecondsFromTimeStr(pLimit))
except Exception as ex:
# fail
result = -1
message = msg.getTranslation("TK_MSG_PARSE_ERROR") % (str(ex))
# preprocess successful
if result == 0:
# invoke
result, message = self._timekprAdminConnector.setPlayTimeLeft(pUserName, pOperation, limit)
# process
if result != 0:
# log error
log.consoleOut(message)
timekpr-next/client/interface/ 000775 001750 001750 00000000000 13476006650 020422 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/speech/ 000775 001750 001750 00000000000 15122253204 021656 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/speech/espeak.py 000644 001750 001750 00000004235 15122253204 023502 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
import locale
import time
# init speech
try:
from espeak import espeak as espeak
_USE_SPEECH = True
except (ImportError, ValueError):
_USE_SPEECH = False
# init speech-ng
if not _USE_SPEECH:
try:
from espeakng import ESpeakNG as espeakng
_USE_SPEECH_NG = True
except (ImportError, ValueError):
_USE_SPEECH_NG = False
pass
pass
def isSupported():
"""Return whether speech can be used"""
# result
return (_USE_SPEECH or _USE_SPEECH_NG)
class timekprSpeech(object):
"""Class will provide speech synth functionality"""
def __init__(self):
"""Initialize config"""
def initSpeech(self):
"""Initialize speech"""
# set up speech synth
if _USE_SPEECH:
# espeak
espeak.set_voice(self.getDefaultSpeechLanguage())
espeak.set_parameter(espeak.Parameter.Pitch, 1)
espeak.set_parameter(espeak.Parameter.Rate, 135)
espeak.set_parameter(espeak.Parameter.Range, 600)
elif _USE_SPEECH_NG:
# ng espeak
self.espeak = espeakng()
self.espeak.voice = self.getDefaultSpeechLanguage();
self.espeak.pitch = 1
self.espeak.speed = 135
self.espeak.range = 600
def getDefaultSpeechLanguage(self):
"""Get default language"""
# no lang
lang = "en"
try:
if _USE_SPEECH:
lang = locale.getlocale()[0].split("_")[0]
elif _USE_SPEECH_NG:
lang = locale.getlocale()[0].replace("_", "-").lower()
except Exception:
pass
# result
return lang
def saySmth(self, pMsg):
"""Say something"""
# if supported
if self.isSpeechSupported():
if _USE_SPEECH:
# synth the speech
espeak.synth(pMsg)
elif _USE_SPEECH_NG:
# say
self.espeak.say(pMsg)
def isSpeechSupported(self):
"""Return whether speech can be used"""
# result
return isSupported()
timekpr-next/client/interface/speech/__init__.py 000664 001750 001750 00000000000 13476006650 023770 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/__init__.py 000664 001750 001750 00000000000 13476006650 022521 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/ui/ 000775 001750 001750 00000000000 14575617250 021044 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/ui/appindicator.py 000600 001750 001750 00000011221 14575617134 024057 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# import
import gi
import os
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.client.interface.ui.notificationarea import timekprNotificationArea
from timekpr.common.constants import messages as msg
# indicator stuff
try:
# try to load ayatanaappindicator (fully compatible with appindicator, I hope it stays that way)
gi.require_version("AyatanaAppIndicator3", "0.1")
from gi.repository import AyatanaAppIndicator3 as AppIndicator
# if successful, mark it so
_USE_INDICATOR = True
except (ImportError, ValueError):
try:
# try to load appindicator
gi.require_version("AppIndicator3", "0.1")
from gi.repository import AppIndicator3 as AppIndicator
# if successful, mark it so
_USE_INDICATOR = True
except (ImportError, ValueError):
# no indictor
_USE_INDICATOR = False
pass
class timekprIndicator(timekprNotificationArea):
"""Support appindicator"""
def __init__(self, pUserName, pUserNameFull, pTimekprClientConfig):
"""Init all required stuff for indicator"""
log.log(cons.TK_LOG_LEVEL_INFO, "start initTimekprIndicator")
# only if this is supported
if self.isSupported():
# init parent as well
super().__init__(pUserName, pUserNameFull, pTimekprClientConfig)
# this is our icon
self._indicator = None
log.log(cons.TK_LOG_LEVEL_INFO, "finish initTimekprIndicator")
def isSupported(self):
"""Get whether appindicator is supported"""
global _USE_INDICATOR
# returns whether we can use appindicator
return _USE_INDICATOR
def initTimekprIcon(self):
"""Initialize timekpr indicator"""
log.log(cons.TK_LOG_LEVEL_INFO, "start initTimekprIndicatorIcon")
# init indicator itself (icon will be set later)
self._indicator = AppIndicator.Indicator.new("indicator-timekpr", os.path.join(self._timekprClientConfig.getTimekprSharedDir(), "icons", cons.TK_PRIO_CONF["client-logo"][cons.TK_ICON_STAT]), AppIndicator.IndicatorCategory.APPLICATION_STATUS)
self._indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE)
# define empty menu
self._timekprMenu = Gtk.Menu()
# add menu items
self._timekprMenuItemTimeLeft = Gtk.MenuItem(msg.getTranslation("TK_MSG_MENU_TIME_LEFT"))
self._timekprMenu.append(self._timekprMenuItemTimeLeft)
self._timekprMenu.append(Gtk.SeparatorMenuItem())
self._timekprMenuItemProperties = Gtk.MenuItem(msg.getTranslation("TK_MSG_MENU_CONFIGURATION"))
self._timekprMenu.append(self._timekprMenuItemProperties)
self._timekprMenu.append(Gtk.SeparatorMenuItem())
self._timekprMenuItemAbout = Gtk.MenuItem(msg.getTranslation("TK_MSG_MENU_ABOUT"))
self._timekprMenu.append(self._timekprMenuItemAbout)
# enable all
self._timekprMenu.show_all()
# connect signal to code
self._timekprMenuItemTimeLeft.connect("activate", super().invokeTimekprTimeLeft)
self._timekprMenuItemProperties.connect("activate", super().invokeTimekprUserProperties)
self._timekprMenuItemAbout.connect("activate", super().invokeTimekprAbout)
# set menu to indicator
self._indicator.set_menu(self._timekprMenu)
# initial config
self.setTimeLeft("", None, 0)
log.log(cons.TK_LOG_LEVEL_INFO, "finish initTimekprIndicatorIcon")
def setTimeLeft(self, pPriority, pTimeLeft, pTimeNotLimited, pPlayTimeLeft=None):
"""Set time left in the indicator"""
# make strings to set
timeLeftStr, icon = super().formatTimeLeft(pPriority, pTimeLeft, pTimeNotLimited, pPlayTimeLeft)
# if we have smth to set
if timeLeftStr is not None:
# set time left (this works with indicator in unity and gnome)
self._indicator.set_label(timeLeftStr, "")
# set time left (this works with indicator in kde5)
self._indicator.set_title(timeLeftStr)
# if we have smth to set
if icon is not None:
# set up the icon
self._indicator.set_icon(icon)
def getTrayIconEnabled(self):
"""Get whether tray icon is enabled"""
return self._indicator.get_status() == AppIndicator.IndicatorStatus.ACTIVE
def setTrayIconEnabled(self, pEnabled):
"""Set whether tray icon is enabled"""
self._indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE if pEnabled else AppIndicator.IndicatorStatus.PASSIVE)
timekpr-next/client/interface/ui/__init__.py 000664 001750 001750 00000000000 13476006650 023136 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/ui/statusicon.py 000600 001750 001750 00000010133 14575617135 023600 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# import
import os
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.client.interface.ui.notificationarea import timekprNotificationArea
from timekpr.common.constants import messages as msg
# status icon stuff
_USE_STATUSICON = True
class timekprIndicator(timekprNotificationArea):
"""Support appindicator"""
def __init__(self, pUserName, pUserNameFull, pTimekprClientConfig):
"""Init all required stuff for indicator"""
log.log(cons.TK_LOG_LEVEL_INFO, "start initTimekprSystrayIcon")
# only if this is supported
if self.isSupported():
# init parent as well
super().__init__(pUserName, pUserNameFull, pTimekprClientConfig)
# this is our icon
self._tray = None
log.log(cons.TK_LOG_LEVEL_INFO, "finish initTimekprSystrayIcon")
def isSupported(self):
"""Get whether appindicator is supported"""
global _USE_STATUSICON
# returns whether we can use appindicator
return _USE_STATUSICON
def initTimekprIcon(self):
"""Initialize timekpr indicator"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "start initTimekprStatusIcon")
# define our popupmenu
timekprMenu = """
"""
#
#
# set up tray
self._tray = Gtk.StatusIcon()
self._tray.set_visible(True)
# connect to methods
self._tray.connect("activate", super().invokeTimekprTimeLeft)
self._tray.connect("popup-menu", self.onTimekprMenu)
# build up menu actiongroups
timekprActionGroup = Gtk.ActionGroup("timekprActions")
timekprActionGroup.add_actions([
("TimeLeft", Gtk.STOCK_INFO, msg.getTranslation("TK_MSG_MENU_TIME_LEFT"), None, None, super().invokeTimekprTimeLeft),
("Limits & configuration", Gtk.STOCK_PROPERTIES, msg.getTranslation("TK_MSG_MENU_CONFIGURATION"), None, None, super().invokeTimekprUserProperties),
("About", Gtk.STOCK_ABOUT, msg.getTranslation("TK_MSG_MENU_ABOUT"), None, None, super().invokeTimekprAbout)
])
# build up menu
timekprUIManager = Gtk.UIManager()
timekprUIManager.add_ui_from_string(timekprMenu)
timekprUIManager.insert_action_group(timekprActionGroup)
self._popup = timekprUIManager.get_widget("/timekprPopupMenu")
# initial config
self._tray.set_from_file(os.path.join(self._timekprClientConfig.getTimekprSharedDir(), "icons", cons.TK_PRIO_CONF["client-logo"][cons.TK_ICON_STAT]))
self.setTimeLeft("", None, 0)
log.log(cons.TK_LOG_LEVEL_DEBUG, "finish initTimekprStatusIcon")
def setTimeLeft(self, pPriority, pTimeLeft, pTimeNotLimited, pPlayTimeLeft=None):
"""Set time left in the indicator"""
# make strings to set
timeLeftStr, icon = super().formatTimeLeft(pPriority, pTimeLeft, pTimeNotLimited, pPlayTimeLeft)
# if we have smth to set
if timeLeftStr is not None:
# set time left
self._tray.set_tooltip_text(timeLeftStr)
self._tray.set_title(timeLeftStr)
# if we have smth to set
if icon is not None:
# set up the icon
self._tray.set_from_file(icon)
def onTimekprMenu(self, status, button, time):
"""Show popup menu for tray"""
self._popup.popup(None, None, None, None, 0, time)
def getTrayIconEnabled(self):
"""Get whether tray icon is enabled"""
return self._tray.get_visible()
def setTrayIconEnabled(self, pEnabled):
"""Set whether tray icon is enabled"""
self._tray.set_visible(pEnabled)
timekpr-next/client/interface/ui/notificationarea.py 000600 001750 001750 00000030224 14575617250 024724 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# import
from datetime import timedelta
import os
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.client.interface.dbus.notifications import timekprNotifications
from timekpr.client.gui.clientgui import timekprGUI
class timekprNotificationArea(object):
"""Support appindicator or other means of showing icon on the screen (this class is a parent for classes like indicator or staticon)"""
def __init__(self, pUserName, pUserNameFull, pTimekprClientConfig):
"""Init all required stuff for indicator"""
log.log(cons.TK_LOG_LEVEL_INFO, "start init timekpr indicator")
# configuration
self._timekprClientConfig = pTimekprClientConfig
# set version
self._timekprVersion = "-.-.-"
# set username
self._userName = pUserName
# initialize priority
self._lastUsedPriority = self._lastUsedServerPriority = ""
# priority level
self._lastUsedPriorityLvl = -99
# PlayTime priority level
self._lastUsedPTPriorityLvl = -99
# initialize time left
self._timeLeftTotal = None
# initialize PlayTime left
self._playTimeLeftTotal = None
# initialize time limit
self._timeNotLimited = 0
# init notificaction stuff
self._timekprNotifications = timekprNotifications(self._userName, self._timekprClientConfig)
# dbus
self._timekprBus = None
self._notifyObject = None
self._notifyInterface = None
# gui forms
self._timekprGUI = timekprGUI(cons.TK_VERSION, self._timekprClientConfig, self._userName, pUserNameFull)
log.log(cons.TK_LOG_LEVEL_INFO, "finish init timekpr indicator")
def initClientConnections(self):
"""Proxy method for initialization"""
# initalize DBUS connections to every additional module
self._timekprNotifications.initClientConnections()
def isTimekprConnected(self):
"""Proxy method for initialization status"""
# check if main connection to timekpr is up
return self._timekprNotifications.isTimekprConnected()
def verifySessionAttributes(self, pWhat, pKey):
"""Proxy method for receive the signal and process the data"""
self._timekprNotifications.verifySessionAttributes(pWhat, pKey)
def requestTimeLimits(self):
"""Proxy method for request time limits from server"""
self._timekprNotifications.requestTimeLimits()
def requestTimeLeft(self):
"""Proxy method for request time left from server"""
self._timekprNotifications.requestTimeLeft()
def _determinePriority(self, pType, pPriority, pTimeLeft):
"""Determine priority based on client config"""
# def
finalPrio = pPriority
finalLimitSecs = -1
# keep in mind that this applies to timeLeft only and critical notifications can STILL be pushed from server
if pTimeLeft is not None:
# calculate
for rPrio in self._timekprClientConfig.getClientNotificationLevels() if pType == "Time" else self._timekprClientConfig.getClientPlayTimeNotificationLevels():
# determine which is the earliest priority level we need to use
# it is determined as time left is less then this interval
if rPrio[0] >= pTimeLeft and (finalLimitSecs > rPrio[0] or finalLimitSecs < 0):
# determine if this is the gratest level that is lower than limit
finalLimitSecs = rPrio[0]
finalPrio = cons.TK_PRIO_LVL_MAP[rPrio[1]]
# final priority
return finalPrio, finalLimitSecs
def formatTimeLeft(self, pPriority, pTimeLeft, pTimeNotLimited, pPlayTimeLeft=None):
"""Set time left in the indicator"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "start formatTimeLeft")
# prio
prio = pPriority
timekprIcon = None
timeLeftStr = None
isTimeChanged = self._timeLeftTotal != pTimeLeft
isPlayTimeChanged = self._playTimeLeftTotal != pPlayTimeLeft
# determine hours and minutes for PlayTime (if there is such time)
if (isTimeChanged or isPlayTimeChanged) and pPlayTimeLeft is not None and pTimeLeft is not None:
# get the smallest one
timeLeftPT = min(pPlayTimeLeft, pTimeLeft)
# determine hours and minutes
timeLeftStrPT = str((timeLeftPT - cons.TK_DATETIME_START).days * 24 + timeLeftPT.hour).rjust(2, "0")
timeLeftStrPT += ":" + str(timeLeftPT.minute).rjust(2, "0")
timeLeftStrPT += ((":" + str(timeLeftPT.second).rjust(2, "0")) if self._timekprClientConfig.getClientShowSeconds() else "")
# execute time and icon changes + notifications only when there are changes
if isTimeChanged or isPlayTimeChanged or pTimeLeft is None or self._lastUsedServerPriority != pPriority:
# if there is no time left set yet, show --
if pTimeLeft is None:
# determine hours and minutes
timeLeftStr = "--:--" + (":--" if self._timekprClientConfig.getClientShowSeconds() else "")
else:
# update time
self._timeLeftTotal = pTimeLeft
self._playTimeLeftTotal = pPlayTimeLeft
self._timeNotLimited = pTimeNotLimited
# unlimited has special icon and text (if it's not anymore, these will change)
if self._timeNotLimited > 0:
# unlimited!
timeLeftStr = "∞"
prio = "unlimited"
else:
# determine hours and minutes
timeLeftStr = str((self._timeLeftTotal - cons.TK_DATETIME_START).days * 24 + self._timeLeftTotal.hour).rjust(2, "0")
timeLeftStr += ":" + str(self._timeLeftTotal.minute).rjust(2, "0")
timeLeftStr += ((":" + str(self._timeLeftTotal.second).rjust(2, "0")) if self._timekprClientConfig.getClientShowSeconds() else "")
# notifications and icons only when time has changed
if isTimeChanged:
# get user configured level and priority
prio, finLvl = (pPriority, -1) if pPriority == cons.TK_PRIO_UACC else self._determinePriority("Time", pPriority, (pTimeLeft - cons.TK_DATETIME_START).total_seconds())
# if level actually changed
if self._lastUsedPriorityLvl != finLvl:
# do not notify if this is the first invocation, because initial limits are already asked from server
# do not notify user in case icon is hidden and no notifications should be shown
if self._lastUsedPriorityLvl > 0 and self.getTrayIconEnabled():
# emit notification
self.notifyUser(cons.TK_MSG_CODE_TIMELEFT, None, prio, pTimeLeft, None)
# level this up
self._lastUsedPriorityLvl = finLvl
# determine hours and minutes for PlayTime (if there is such time)
if pPlayTimeLeft is not None:
# format final time string
timeLeftStr = "%s / %s" % (timeLeftStr, timeLeftStrPT)
# now, if priority changes, set up icon as well
if isTimeChanged and self._lastUsedPriority != prio:
# log
log.log(cons.TK_LOG_LEVEL_DEBUG, "changing icon for level, old: %s, new: %s" % (self._lastUsedPriority, prio))
# set up last used prio
self._lastUsedPriority = prio
# get status icon
timekprIcon = os.path.join(self._timekprClientConfig.getTimekprSharedDir(), "icons", cons.TK_PRIO_CONF[cons.getNotificationPrioriy(self._lastUsedPriority)][cons.TK_ICON_STAT])
# adjust server priority: server sends all time left messages with low priority, except when there is no time left, then priority is critical
self._lastUsedServerPriority = pPriority
log.log(cons.TK_LOG_LEVEL_DEBUG, "finish formatTimeLeft")
# return time left and icon (if changed), so implementations can use it
return timeLeftStr, timekprIcon
def processPlayTimeNotifications(self, pTimeLimits):
"""Process PlayTime notifications (if there is PT info in limits)"""
isPTInfoEnabled = self._timekprGUI.isPlayTimeAccountingInfoEnabled()
# determine whether we actually need to process PlayTime
if cons.TK_CTRL_PTLSTC in pTimeLimits and cons.TK_CTRL_PTLPD in pTimeLimits and cons.TK_CTRL_PTTLO in pTimeLimits:
# only of not enabled
if not isPTInfoEnabled:
self._timekprGUI.setPlayTimeAccountingInfoEnabled(True)
# get user configured level and priority
prio, finLvl = self._determinePriority("PlayTime", cons.TK_PRIO_LOW, pTimeLimits[cons.TK_CTRL_PTLPD])
# log
log.log(cons.TK_LOG_LEVEL_DEBUG, "process PT notif, prio: %s, prevLVL: %i, lvl: %i, icoena: %s" % (prio, self._lastUsedPTPriorityLvl, finLvl, self.getTrayIconEnabled()))
# if any priority is effective, determine whether we need to inform user
if (finLvl > 0 or self._lastUsedPTPriorityLvl < -1) and self._lastUsedPTPriorityLvl != finLvl and self.isTimekprConnected():
# adjust level too
self._lastUsedPTPriorityLvl = finLvl
# if icon is hidden, do not show any notifications
if self.getTrayIconEnabled():
# notify user
self._timekprNotifications.notifyUser(cons.TK_MSG_CODE_TIMELEFT, "PlayTime", prio, cons.TK_DATETIME_START + timedelta(seconds=min(pTimeLimits[cons.TK_CTRL_PTLPD], pTimeLimits[cons.TK_CTRL_LEFTD])), None)
elif isPTInfoEnabled:
# disable info (if it was enabled)
log.log(cons.TK_LOG_LEVEL_DEBUG, "disable PT info tab")
self._timekprGUI.setPlayTimeAccountingInfoEnabled(False)
def notifyUser(self, pMsgCode, pMsgType, pPriority, pTimeLeft=None, pAdditionalMessage=None):
"""Notify user (a wrapper call)"""
# prio
prio = pPriority
timeLeft = cons.TK_DATETIME_START if pTimeLeft is None else pTimeLeft
# for time left, we need to determine final priority accoriding to user defined priority (if not defined, that will come from server)
if pMsgCode == cons.TK_MSG_CODE_TIMELEFT:
# get user configured level and priority
prio, finLvl = self._determinePriority("Time", pPriority, (timeLeft - cons.TK_DATETIME_START).total_seconds())
# notify user
self._timekprNotifications.notifyUser(pMsgCode, pMsgType, prio, timeLeft, pAdditionalMessage)
def setStatus(self, pStatus):
"""Change status of timekpr"""
return self._timekprGUI.setStatus(pStatus)
# --------------- user clicked methods --------------- #
def invokeTimekprTimeLeft(self, pEvent):
"""Inform user about (almost) exact time left"""
# inform user about precise time
self.notifyUser((cons.TK_MSG_CODE_TIMEUNLIMITED if self._timeNotLimited > 0 else cons.TK_MSG_CODE_TIMELEFT), None, self._lastUsedPriority, self._timeLeftTotal)
def invokeTimekprUserProperties(self, pEvent):
"""Bring up a window for property editing"""
# show limits and config
self._timekprGUI.initConfigForm()
def invokeTimekprAbout(self, pEvent):
"""Bring up a window for timekpr configration (this needs elevated privileges to do anything)"""
# show about
self._timekprGUI.initAboutForm()
# --------------- configuration update methods --------------- #
def renewUserLimits(self, pTimeInformation):
"""Call an update to renew time left"""
# pass this to actual gui storage
self._timekprGUI.renewLimits(pTimeInformation)
def renewLimitConfiguration(self, pLimits):
"""Call an update on actual limits"""
# pass this to actual gui storage
self._timekprGUI.renewLimitConfiguration(pLimits)
timekpr-next/client/interface/dbus/ 000775 001750 001750 00000000000 15122253261 021347 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/dbus/daemon.py 000644 001750 001750 00000035104 15122253261 023165 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# imports
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from datetime import timedelta
import os
import dbus
from gi.repository import GLib
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.common.utils import misc
from timekpr.common.utils.config import timekprClientConfig
from timekpr.client.interface.ui.appindicator import timekprIndicator as appind_timekprIndicator
from timekpr.client.interface.ui.statusicon import timekprIndicator as statico_timekprIndicator
from timekpr.common.constants import messages as msg
class timekprClient(object):
"""Main class for holding all client logic (including dbus)"""
# --------------- initialization / control methods --------------- #
def __init__(self):
"""Initialize client"""
# set username , etc.
self._userName, self._userNameFull = misc.getNormalizedUserNames(pUID=os.getuid())
self._userNameDBUS = self._userName.replace(".", "").replace("-", "")
# get our bus
self._timekprBus = (dbus.SessionBus() if (cons.TK_DEV_ACTIVE and cons.TK_DEV_BUS == "ses") else dbus.SystemBus())
# loop
self._mainLoop = GLib.MainLoop()
# init logging (load config which has all the necessarry bits)
self._timekprClientConfig = timekprClientConfig()
self._timekprClientConfig.loadClientConfiguration()
# init logging
log.setLogging(self._timekprClientConfig.getClientLogLevel(), cons.TK_LOG_TEMP_DIR, cons.TK_LOG_OWNER_CLIENT, self._userName)
def startTimekprClient(self):
"""Start up timekpr (choose appropriate gui and start this up)"""
log.log(cons.TK_LOG_LEVEL_INFO, "starting up timekpr client")
# check if appind is supported
self._timekprClientIndicator = appind_timekprIndicator(self._userName, self._userNameFull, self._timekprClientConfig)
# if not supported fall back to statico
if not self._timekprClientIndicator.isSupported():
# check if appind is supported
self._timekprClientIndicator = statico_timekprIndicator(self._userName, self._userNameFull, self._timekprClientConfig)
# this will check whether we have an icon, if not, the rest goes through timekprClient anyway
if self._timekprClientIndicator.isSupported():
# init timekpr
self._timekprClientIndicator.initTimekprIcon()
else:
# process time left notification (notifications should be available in any of the icons, even of not supported)
self._timekprClientIndicator.notifyUser(cons.TK_MSG_CODE_ICON_INIT_ERROR, None, cons.TK_PRIO_CRITICAL, None, "cannot initialize the icon in any way")
# connect to timekpr etc.
self.connectTimekprSignalsDBUS()
# init startup notification at default interval
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.requestInitialTimeValues)
# periodic log flusher
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.autoFlushLogFile)
# start main loop
self._mainLoop.run()
def autoFlushLogFile(self):
"""Periodically save file"""
log.autoFlushLogFile()
return True
def finishTimekpr(self, signal=None, frame=None):
"""Exit timekpr gracefully"""
log.log(cons.TK_LOG_LEVEL_INFO, "Finishing up")
# exit main loop
self._mainLoop.quit()
log.log(cons.TK_LOG_LEVEL_INFO, "Finished")
log.autoFlushLogFile(True)
def requestInitialTimeValues(self):
"""Request initial config from server"""
# whether to process again
result = False
# check if connected
if self._notificationFromDBUS is not None:
# connect to DBUS for the rest of modules
self._timekprClientIndicator.initClientConnections()
# request values if connections are made successfully
result = self._timekprClientIndicator.isTimekprConnected()
# connected?
if result:
# get limits
self._timekprClientIndicator.requestTimeLimits()
# get left
self._timekprClientIndicator.requestTimeLeft()
# continue execution while not connected (this is called from glib exec)
return not result
# --------------- DBUS / communication methods --------------- #
def connectTimekprSignalsDBUS(self):
"""Init connections to dbus provided by server"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "start connectTimekprSignalsDBUS")
# trying to connect
self._timekprClientIndicator.setStatus(msg.getTranslation("TK_MSG_STATUS_CONNECTING"))
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# get dbus object
self._notificationFromDBUS = self._timekprBus.get_object(cons.TK_DBUS_BUS_NAME, cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS)
# connect to signal
self._sessionAttributeVerificationSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveSessionAttributeVerificationRequest,
dbus_interface = cons.TK_DBUS_USER_SESSION_ATTRIBUTE_INTERFACE,
signal_name = "sessionAttributeVerification")
# connect to signal
self._timeLeftSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeLeft,
dbus_interface = cons.TK_DBUS_USER_LIMITS_INTERFACE,
signal_name = "timeLeft")
# connect to signal
self._timeLimitsSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeLimits,
dbus_interface = cons.TK_DBUS_USER_LIMITS_INTERFACE,
signal_name = "timeLimits")
# connect to signal
self._timeLeftNotificatonSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeLeftNotification,
dbus_interface = cons.TK_DBUS_USER_NOTIF_INTERFACE,
signal_name = "timeLeftNotification")
# connect to signal
self._timeCriticalNotificatonSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeCriticalNotification,
dbus_interface = cons.TK_DBUS_USER_NOTIF_INTERFACE,
signal_name = "timeCriticalNotification")
# connect to signal
self._timeNoLimitNotificationSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeNoLimitNotification,
dbus_interface = cons.TK_DBUS_USER_NOTIF_INTERFACE,
signal_name = "timeNoLimitNotification")
# connect to signal
self._timeLeftChangedNotificationSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeLeftChangedNotification,
dbus_interface = cons.TK_DBUS_USER_NOTIF_INTERFACE,
signal_name = "timeLeftChangedNotification")
# connect to signal
self._timeConfigurationChangedNotificationSignal = self._timekprBus.add_signal_receiver(
path = cons.TK_DBUS_USER_NOTIF_PATH_PREFIX + self._userNameDBUS,
handler_function = self.receiveTimeConfigurationChangedNotification,
dbus_interface = cons.TK_DBUS_USER_NOTIF_INTERFACE,
signal_name = "timeConfigurationChangedNotification")
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pDbusIFName=cons.TK_DBUS_BUS_NAME)
# set status
self._timekprClientIndicator.setStatus(msg.getTranslation("TK_MSG_STATUS_CONNECTED"))
log.log(cons.TK_LOG_LEVEL_DEBUG, "main DBUS signals connected")
except Exception as dbusEx:
# logging
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR (DBUS): \"%s\" in \"%s.%s\"" % (str(dbusEx), __name__, self.connectTimekprSignalsDBUS.__name__))
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR: failed to connect to timekpr dbus, trying again...")
# did not connect (set connection to None) and schedule for reconnect at default interval
self._notificationFromDBUS = None
# connect until successful
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.connectTimekprSignalsDBUS)
log.log(cons.TK_LOG_LEVEL_DEBUG, "finish connectTimekprSignalsDBUS")
# finish
return False
# --------------- admininstration / verification methods (from dbus) --------------- #
def receiveSessionAttributeVerificationRequest(self, pWhat, pKey):
"""Receive the signal and process the data"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive verification request: %s, %s" % (pWhat, "key"))
# resend stuff to server
self._timekprClientIndicator.verifySessionAttributes(pWhat, pKey)
def processShowClientIcon(self, pTimeInformation):
"""Check wheter to show or hide tray icon"""
# do we have information about show or hide icon
if cons.TK_CTRL_HIDEI in pTimeInformation:
# enable?
iconStatus = (not bool(pTimeInformation[cons.TK_CTRL_HIDEI]))
# check if those differ
if self._timekprClientIndicator.getTrayIconEnabled() != iconStatus:
# set it
self._timekprClientIndicator.setTrayIconEnabled(iconStatus)
# --------------- worker methods (from dbus) --------------- #
def receiveTimeLeft(self, pPriority, pTimeInformation):
"""Receive the signal and process the data to user"""
# check which options are available
timeLeft = (pTimeInformation[cons.TK_CTRL_LEFT] if cons.TK_CTRL_LEFT in pTimeInformation else 0)
playTimeLeft = (pTimeInformation[cons.TK_CTRL_PTLPD] if cons.TK_CTRL_PTLSTC in pTimeInformation and cons.TK_CTRL_PTLPD in pTimeInformation and cons.TK_CTRL_PTTLO in pTimeInformation else None)
isTimeNotLimited = (pTimeInformation[cons.TK_CTRL_TNL] if cons.TK_CTRL_TNL in pTimeInformation else 0)
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive timeleft, prio: %s, tl: %i, ptl: %s, nolim: %i" % (pPriority, timeLeft, str(playTimeLeft), isTimeNotLimited))
# process show / hide icon
self.processShowClientIcon(pTimeInformation)
# process time left
self._timekprClientIndicator.setTimeLeft(pPriority, cons.TK_DATETIME_START + timedelta(seconds=timeLeft), isTimeNotLimited, cons.TK_DATETIME_START + timedelta(seconds=playTimeLeft) if playTimeLeft is not None else playTimeLeft)
# renew limits in GUI
self._timekprClientIndicator.renewUserLimits(pTimeInformation)
# process PlayTime notifications as well
self._timekprClientIndicator.processPlayTimeNotifications(pTimeInformation)
def receiveTimeLimits(self, pPriority, pTimeLimits):
"""Receive the signal and process the data to user"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive timelimits: %s" % (pPriority))
# renew limits in GUI
self._timekprClientIndicator.renewLimitConfiguration(pTimeLimits)
# --------------- notification methods (from dbus) --------------- #
def receiveTimeLeftNotification(self, pPriority, pTimeLeftTotal, pTimeLeftToday, pTimeLimitToday):
"""Receive time left and update GUI"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive tl notif: %s, %i" % (pPriority, pTimeLeftTotal))
# if notifications are turned on
if (self._timekprClientConfig.getClientShowAllNotifications() and self._timekprClientIndicator.getTrayIconEnabled()) or pPriority == cons.TK_PRIO_CRITICAL:
# process time left notification
self._timekprClientIndicator.notifyUser(cons.TK_MSG_CODE_TIMELEFT, None, pPriority, cons.TK_DATETIME_START + timedelta(seconds=pTimeLeftTotal))
def receiveTimeCriticalNotification(self, pFinalNotificationType, pPriority, pSecondsLeft):
"""Receive critical time left and show that to user"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive crit notif: %s, %i" % (pFinalNotificationType, pSecondsLeft))
# process time left (this shows in any case)
self._timekprClientIndicator.notifyUser(cons.TK_MSG_CODE_TIMECRITICAL, pFinalNotificationType, pPriority, cons.TK_DATETIME_START + timedelta(seconds=pSecondsLeft))
def receiveTimeNoLimitNotification(self, pPriority):
"""Receive no limit notificaton and show that to user"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive nl notif")
# if notifications are turned on
if self._timekprClientConfig.getClientShowAllNotifications() and self._timekprClientIndicator.getTrayIconEnabled():
# process time left
self._timekprClientIndicator.notifyUser(cons.TK_MSG_CODE_TIMEUNLIMITED, None, pPriority)
def receiveTimeLeftChangedNotification(self, pPriority):
"""Receive time left notification and show it to user"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive time left changed notif")
# if notifications are turned on
if self._timekprClientConfig.getClientShowLimitNotifications() and self._timekprClientIndicator.getTrayIconEnabled():
# limits have changed and applied
self._timekprClientIndicator.notifyUser(cons.TK_MSG_CODE_TIMELEFTCHANGED, None, pPriority)
def receiveTimeConfigurationChangedNotification(self, pPriority):
"""Receive notification about config change and show it to user"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive config changed notif")
# if notifications are turned on
if self._timekprClientConfig.getClientShowLimitNotifications() and self._timekprClientIndicator.getTrayIconEnabled():
# configuration has changed, new limits may have been applied
self._timekprClientIndicator.notifyUser(cons.TK_MSG_CODE_TIMECONFIGCHANGED, None, pPriority)
timekpr-next/client/interface/dbus/notifications.py 000644 001750 001750 00000076200 15122253261 024575 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# import
import dbus
import os
from gi.repository import GLib
from dbus.mainloop.glib import DBusGMainLoop
from datetime import datetime
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.common.utils import misc
from timekpr.client.interface.speech.espeak import timekprSpeech
from timekpr.common.constants import messages as msg
# default loop
DBusGMainLoop(set_as_default=True)
class timekprNotifications(object):
"""Main class for supporting indicator notifications, connect to request methods for timekpr and connections to other DBUS modules"""
def __init__(self, pUserName, pTimekprClientConfig):
"""Initialize notifications"""
log.log(cons.TK_LOG_LEVEL_INFO, "start init timekpr notifications")
# uname
self._userName = pUserName
self._timekprClientConfig = pTimekprClientConfig
# notification (to replace itself in case they are incoming fast)
self._lastNotifId = 0
self._lastNotifDT = datetime.now()
self._lastPTNotifId = 0
self._lastPTNotifDT = datetime.now()
# session bus
self._userSessionBus = dbus.SessionBus()
# timekpr bus
self._timekprBus = (dbus.SessionBus() if (cons.TK_DEV_ACTIVE and cons.TK_DEV_BUS == "ses") else dbus.SystemBus())
# DBUS client connections
# connection types
self.CL_CONN_TK = "timekpr"
self.CL_CONN_NOTIF = "notifications"
self.CL_CONN_SCR = "screensaver"
# constants
self.CL_IF = "primary interface"
self.CL_IFA = "attributes interface"
self.CL_SI = "signal"
self.CL_CNT = "retry_count"
self.CL_DEL = "delay_times"
# object collection for DBUS connections
self._dbusConnections = {
self.CL_CONN_TK: {self.CL_IF: None, self.CL_IFA: None, self.CL_SI: None, self.CL_CNT: 999, self.CL_DEL: 0},
self.CL_CONN_NOTIF: {self.CL_IF: None, self.CL_IFA: None, self.CL_SI: None, self.CL_CNT: 99, self.CL_DEL: 0},
self.CL_CONN_SCR: {self.CL_IF: None, self.CL_IFA: None, self.CL_SI: None, self.CL_CNT: 5, self.CL_DEL: 2}
}
# WORKAROUNDS section start
# adjust even bigger delay with unity + 18.04 + HDD
if "UNITY" in os.getenv("XDG_CURRENT_DESKTOP", "N/A").upper():
# more delay, race condition with screensaver?
self._dbusConnections[self.CL_CONN_SCR][self.CL_DEL] += cons.TK_POLLTIME - 1
# WORKAROUNDS section end
# speech init
self._timekprSpeechManager = None
log.log(cons.TK_LOG_LEVEL_INFO, "finish init timekpr notifications")
def initClientConnections(self):
"""Init dbus (connect to session bus for notification)"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "start initClientConnections")
# speech
if self._timekprSpeechManager is None:
# initialize
self._timekprSpeechManager = timekprSpeech()
# check if supported, if it is, initialize
if self._timekprSpeechManager.isSpeechSupported():
# initialize if supported
self._timekprSpeechManager.initSpeech()
# only if notifications are not ok
if self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF] is None and self._dbusConnections[self.CL_CONN_NOTIF][self.CL_CNT] > 0 and not self._dbusConnections[self.CL_CONN_NOTIF][self.CL_DEL] > 0:
# define inames (I hope "revolutionary company" won't sue me for using i in front of variable names)
iNames = ["org.freedesktop.Notifications"]
iPaths = ["/org/freedesktop/Notifications"]
# go through inames
for idx in range(0, len(iNames)):
# go through all possible interfaces
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# getting interface
self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF] = dbus.Interface(self._userSessionBus.get_object(iNames[idx], iPaths[idx]), iNames[idx])
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pDbusIFName=iNames[idx])
# first sucess is enough
log.log(cons.TK_LOG_LEVEL_DEBUG, "CONNECTED to DBUS %s interface" % (self.CL_CONN_NOTIF))
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: connected to notification service through \"%s\"" % (iNames[idx]))
# check capabilities
if "sound" not in self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF].GetCapabilities():
# notifications w/ sound are not available
self._timekprClientConfig.setIsNotificationSoundSupported(False)
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: notification sound is not supported")
# add a connection to signal
self._dbusConnections[self.CL_CONN_NOTIF][self.CL_SI] = self._userSessionBus.add_signal_receiver(
path = iPaths[idx],
handler_function = self.receiveNotificationClosed,
dbus_interface = iNames[idx],
signal_name = "NotificationClosed")
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: connected to notification closing callback service through \"%s\"" % (iNames[idx]))
# finish
break
except Exception as dbusEx:
self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF] = None
# logging
log.log(cons.TK_LOG_LEVEL_WARN, "WARNING: initiating dbus connection (\"%s.%s\", %s, %s), error: %s" % (__name__, self.initClientConnections.__name__, self.CL_CONN_NOTIF, iNames[idx], str(dbusEx)))
# only if screensaver is not ok
if self._dbusConnections[self.CL_CONN_SCR][self.CL_IF] is None and self._dbusConnections[self.CL_CONN_SCR][self.CL_CNT] > 0 and not self._dbusConnections[self.CL_CONN_SCR][self.CL_DEL] > 0:
# define inames (I hope "revolutionary company" won't sue me for using i in front of variable names :) )
iNames = []
iPaths = []
chosenIdx = None
currentDE = None
isGnomeScrUsed = False
isFDScrUsed = False
# THIS WHOLE SECTION IS WORKAROUNDS FOR MULTIPLE VARIETIES OF SCREENSAVER IMPLEMENTATIONS - START
# they must be compatible to freedekstop standard, e.g. have corect naming and at least GetActive method as well as ActiveChanged signal
# get current DE
currentDE = os.getenv("XDG_CURRENT_DESKTOP", "N/A")
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: current desktop environment \"%s\"" % (currentDE))
# transform
currentDE = currentDE.lower().replace("x-", "")
# workarounds per desktop
for rIdx in range(0, len(cons.TK_SCR_XDGCD_OVERRIDE)):
# check desktops
if cons.TK_SCR_XDGCD_OVERRIDE[rIdx][0] in currentDE:
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: using \"%s\" screensaver dbus interface as a workaround" % (cons.TK_SCR_XDGCD_OVERRIDE[rIdx][1]))
# use gnome stuff
iNames.extend(["org.%s.ScreenSaver" % (cons.TK_SCR_XDGCD_OVERRIDE[rIdx][1])])
iPaths.extend(["/org/%s/ScreenSaver" % (cons.TK_SCR_XDGCD_OVERRIDE[rIdx][1])])
# check if gnome screensaver is used (can it be used as failover?)
isGnomeScrUsed = ((cons.TK_SCR_XDGCD_OVERRIDE[rIdx][1] == "gnome") if not isGnomeScrUsed else isGnomeScrUsed)
# check if freedesktop screensaver is used (can it be used as failover?)
isFDScrUsed = ((cons.TK_SCR_XDGCD_OVERRIDE[rIdx][1] == "freedesktop") if not isFDScrUsed else isFDScrUsed)
# first match is enough
break
# in case overrides were found, do not try anything else
if len(iNames) < 1:
# try parsing DE to get the most accurate DE name (there are desktop names, like, KDE, X-Cinnamon, XFCE and ubuntu:GNOME, ...)
if ":" in currentDE:
# get desktop so we check for second option after ":"
currentDE = currentDE.split(":")[1]
# add to the list
if currentDE is not None and currentDE != "":
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: trying to use \"%s\" as screensaver dbus object" % (currentDE))
# add
iNames.extend(["org.%s.ScreenSaver" % (currentDE)])
iPaths.extend(["/org/%s/ScreenSaver" % (currentDE)])
# check if gnome screensaver is used (can it be used as failover?)
isGnomeScrUsed = ((currentDE == "gnome") if not isGnomeScrUsed else isGnomeScrUsed)
# add gnome (most popular)
if not isGnomeScrUsed:
# add default section
iNames.extend(["org.gnome.ScreenSaver"])
iPaths.extend(["/org/gnome/ScreenSaver"])
# add freedesktop (almost no one uses this, except KDE and maybe some other, the rest return "method not implemented" error)
if not isFDScrUsed:
# add default section with the actual standard
iNames.extend(["org.freedesktop.ScreenSaver"])
iPaths.extend(["/org/freedesktop/ScreenSaver"])
# THIS WHOLE SECTION IS WORKAROUNDS FOR MULTIPLE VARIETIES OF SCREENSAVER IMPLEMENTATIONS - END
# go through inames
for idx in range(0, len(iNames)):
# go through all possible interfaces
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# getting interface
self._dbusConnections[self.CL_CONN_SCR][self.CL_IF] = dbus.Interface(self._userSessionBus.get_object(iNames[idx], iPaths[idx]), iNames[idx])
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: connected to screensaver service through \"%s\"" % (iNames[idx]))
# verification (Gnome has not implemented freedesktop methods, we need to verify this actually works)
self._dbusConnections[self.CL_CONN_SCR][self.CL_IF].GetActive()
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pDbusIFName=iNames[idx])
# first sucess is enough
chosenIdx = idx
# finish
break
except Exception as dbusEx:
del self._dbusConnections[self.CL_CONN_SCR][self.CL_IF]
self._dbusConnections[self.CL_CONN_SCR][self.CL_IF] = None
# logging
log.log(cons.TK_LOG_LEVEL_WARN, "WARNING: initiating dbus connection (\"%s.%s\", %s, %s), error: %s" % (__name__, self.initClientConnections.__name__, self.CL_CONN_SCR, iNames[idx], str(dbusEx)))
# connection successful
if self._dbusConnections[self.CL_CONN_SCR][self.CL_IF] is not None:
# log
log.log(cons.TK_LOG_LEVEL_DEBUG, "CONNECTED to DBUS %s (%s) interface" % (self.CL_CONN_SCR, iNames[chosenIdx]))
# add a connection to signal
self._dbusConnections[self.CL_CONN_SCR][self.CL_SI] = self._userSessionBus.add_signal_receiver(
path = iPaths[chosenIdx],
handler_function = self.receiveScreenSaverActivityChange,
dbus_interface = iNames[chosenIdx],
signal_name = "ActiveChanged")
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: connected to screensaver active callback signal through \"%s\"" % (iNames[chosenIdx]))
# only if screensaver is not ok
if self._dbusConnections[self.CL_CONN_TK][self.CL_IF] is None and self._dbusConnections[self.CL_CONN_TK][self.CL_CNT] > 0 and not self._dbusConnections[self.CL_CONN_TK][self.CL_DEL] > 0:
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# getting interface
self._dbusConnections[self.CL_CONN_TK][self.CL_IF] = dbus.Interface(self._timekprBus.get_object(cons.TK_DBUS_BUS_NAME, cons.TK_DBUS_SERVER_PATH), cons.TK_DBUS_USER_LIMITS_INTERFACE)
# log
log.log(cons.TK_LOG_LEVEL_DEBUG, "CONNECTED to %s DBUS %s interface" % (self.CL_CONN_TK, self.CL_IF))
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: connected to timekpr limits service through \"%s\"" % (cons.TK_DBUS_USER_LIMITS_INTERFACE))
# getting interface
self._dbusConnections[self.CL_CONN_TK][self.CL_IFA] = dbus.Interface(self._timekprBus.get_object(cons.TK_DBUS_BUS_NAME, cons.TK_DBUS_SERVER_PATH), cons.TK_DBUS_USER_SESSION_ATTRIBUTE_INTERFACE)
# log
log.log(cons.TK_LOG_LEVEL_DEBUG, "CONNECTED to %s DBUS %s interface" % (self.CL_CONN_TK, self.CL_IFA))
# log
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: connected to timekpr session attributes service through \"%s\"" % (cons.TK_DBUS_USER_SESSION_ATTRIBUTE_INTERFACE))
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pDbusIFName=cons.TK_DBUS_USER_LIMITS_INTERFACE)
except Exception as dbusEx:
# reset
self._dbusConnections[self.CL_CONN_TK][self.CL_IF] = None
self._dbusConnections[self.CL_CONN_TK][self.CL_IFA] = None
# logging
log.log(cons.TK_LOG_LEVEL_INFO, "WARNING: initiating dbus connection (\"%s.%s\", %s, %s), error: %s" % (__name__, self.initClientConnections.__name__, self.CL_CONN_TK, cons.TK_DBUS_BUS_NAME, str(dbusEx)))
# retry?
doRetry = False
# all variants
for rConn in (self.CL_CONN_TK, self.CL_CONN_NOTIF, self.CL_CONN_SCR):
# if either of this fails, we keep trying to connect
if self._dbusConnections[rConn][self.CL_IF] is None:
# max retries
if self._dbusConnections[rConn][self.CL_CNT] > 0:
# only if delay is ended
if not self._dbusConnections[rConn][self.CL_DEL] > 0:
# decrease retries
self._dbusConnections[rConn][self.CL_CNT] -= 1
# continue if more retries available
if self._dbusConnections[rConn][self.CL_CNT] > 0:
# retry
doRetry = True
# do not take into account delay
if self._dbusConnections[rConn][self.CL_DEL] > 0:
# connection delayed
log.log(cons.TK_LOG_LEVEL_INFO, "INFO: dbus connection to %s delayed for %d more times" % (rConn, self._dbusConnections[rConn][self.CL_DEL]))
# decrease delay
self._dbusConnections[rConn][self.CL_DEL] -= 1
else:
# logging
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR: failed to connect to %s dbus, trying again..." % (rConn))
else:
# connection aborted
log.log(cons.TK_LOG_LEVEL_WARN, "WARNING: dbus connection to %s failed, some functionality will not be available" % (rConn))
# retry
if doRetry:
# if either of this fails, we keep trying to connect
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.initClientConnections)
# prepare notifications in case smth is not ok
else:
# let's inform user in case screensaver is not connected
if self._dbusConnections[self.CL_CONN_SCR][self.CL_IF] is None and self._timekprClientConfig.getClientShowAllNotifications():
# prepare notification
self.notifyUser(cons.TK_MSG_CODE_FEATURE_SCR_NOT_AVAILABLE_ERROR, None, cons.TK_PRIO_WARNING, pAdditionalMessage=self.CL_CONN_SCR)
log.log(cons.TK_LOG_LEVEL_DEBUG, "finish initClientConnections")
# finish
return False
def isTimekprConnected(self):
"""Return status of timekpr connection (nothing else, just timekpr itself)"""
return self._dbusConnections[self.CL_CONN_TK][self.CL_IF] is not None
def _prepareNotification(self, pMsgCode, pMsgType, pPriority, pTimeLeft=None, pAdditionalMessage=None):
"""Prepare the message to be sent to dbus notifications"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "start prepareNotification")
# determine icon to use
timekprIcon = cons.TK_PRIO_CONF[cons.getNotificationPrioriy(pPriority)][cons.TK_ICON_NOTIF]
timekprPrio = cons.TK_PRIO_CONF[cons.getNotificationPrioriy(pPriority)][cons.TK_DBUS_PRIO]
# calculate hours in advance
if pTimeLeft is not None:
timeLeftHours = (pTimeLeft - cons.TK_DATETIME_START).days * 24 + pTimeLeft.hour
# determine the message to pass
if pMsgCode == cons.TK_MSG_CODE_TIMEUNLIMITED:
# no limit
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_NOT_LIMITED")
elif pMsgCode == cons.TK_MSG_CODE_TIMELEFT:
# msg
msgStr = " ".join((msg.getTranslation("TK_MSG_NOTIFICATION_TIME_LEFT_1", timeLeftHours), msg.getTranslation("TK_MSG_NOTIFICATION_TIME_LEFT_2", pTimeLeft.minute), msg.getTranslation("TK_MSG_NOTIFICATION_PLAYTIME_LEFT_3" if pMsgType == "PlayTime" else "TK_MSG_NOTIFICATION_TIME_LEFT_3", pTimeLeft.second)))
elif pMsgCode == cons.TK_MSG_CODE_TIMECRITICAL:
# depending on type
if pMsgType == cons.TK_CTRL_RES_L:
msgCode = "TK_MSG_NOTIFICATION_TIME_IS_UP_1L"
elif pMsgType in (cons.TK_CTRL_RES_S, cons.TK_CTRL_RES_W):
msgCode = "TK_MSG_NOTIFICATION_TIME_IS_UP_1S"
elif pMsgType == cons.TK_CTRL_RES_D:
msgCode = "TK_MSG_NOTIFICATION_TIME_IS_UP_1D"
else:
msgCode = "TK_MSG_NOTIFICATION_TIME_IS_UP_1T"
# msg
msgStr = " ".join((msg.getTranslation(msgCode), msg.getTranslation("TK_MSG_NOTIFICATION_TIME_IS_UP_2", pTimeLeft.second)))
elif pMsgCode == cons.TK_MSG_CODE_TIMELEFTCHANGED:
# msg
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_ALLOWANCE_CHANGED")
elif pMsgCode == cons.TK_MSG_CODE_TIMECONFIGCHANGED:
# msg
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_CONFIGURATION_CHANGED")
elif pMsgCode == cons.TK_MSG_CODE_REMOTE_COMMUNICATION_ERROR:
# msg
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_CANNOT_CONNECT") % (pAdditionalMessage)
elif pMsgCode == cons.TK_MSG_CODE_REMOTE_INVOCATION_ERROR:
# msg
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_CANNOT_COMMUNICATE") % (pAdditionalMessage)
elif pMsgCode == cons.TK_MSG_CODE_ICON_INIT_ERROR:
# msg
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_CANNOT_INIT_ICON") % (pAdditionalMessage)
elif pMsgCode == cons.TK_MSG_CODE_FEATURE_SCR_NOT_AVAILABLE_ERROR:
# msg
msgStr = msg.getTranslation("TK_MSG_NOTIFICATION_SCR_FEATURE_NOT_AVAILABLE") % (pAdditionalMessage)
log.log(cons.TK_LOG_LEVEL_DEBUG, "finish prepareNotification")
# pass this back
return timekprIcon, msgStr, timekprPrio
def notifyUser(self, pMsgCode, pMsgType, pPriority, pTimeLeft=None, pAdditionalMessage=None):
"""Notify the user."""
# if we have dbus connection, let"s do so
if self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF] is None:
# init
self.initClientConnections()
# if not then return
if not self.isTimekprConnected():
return
# can we notify user
if self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF] is not None:
# prepare notification
timekprIcon, msgStr, timekprPrio = self._prepareNotification(pMsgCode, pMsgType, pPriority, pTimeLeft, pAdditionalMessage)
# defaults
hints = {"urgency": timekprPrio}
# notification params based on criticality
if pPriority in (cons.TK_PRIO_CRITICAL, cons.TK_PRIO_IMPORTANT):
# timeout
notificationTimeout = self._timekprClientConfig.getClientNotificationTimeoutCritical()
# sound
if self._timekprClientConfig.getIsNotificationSoundSupported() and self._timekprClientConfig.getClientUseNotificationSound():
# add sound hint
if cons.TK_CL_NOTIF_SND_TYPE == "sound-name":
hints["sound-name"] = cons.TK_CL_NOTIF_SND_NAME_IMPORTANT
else:
hints["sound-file"] = cons.TK_CL_NOTIF_SND_FILE_CRITICAL
else:
# timeout
notificationTimeout = self._timekprClientConfig.getClientNotificationTimeout()
# sound
if self._timekprClientConfig.getIsNotificationSoundSupported() and self._timekprClientConfig.getClientUseNotificationSound():
# add sound hint
if cons.TK_CL_NOTIF_SND_TYPE == "sound-name":
hints["sound-name"] = cons.TK_CL_NOTIF_SND_NAME_WARNING
else:
hints["sound-file"] = cons.TK_CL_NOTIF_SND_FILE_WARN
# calculate last time notification is shown (if this is too recent - replace, otherwise add new notification)
if pMsgType == "PlayTime" and self._lastPTNotifId != 0 and abs((datetime.now() - self._lastPTNotifDT).total_seconds()) >= (notificationTimeout if notificationTimeout > 0 else abs((datetime.now() - self._lastPTNotifDT).total_seconds()) + 1):
self._lastPTNotifId = 0
elif pMsgType != "PlayTime" and self._lastNotifId != 0 and abs((datetime.now() - self._lastNotifDT).total_seconds()) >= (notificationTimeout if notificationTimeout > 0 else abs((datetime.now() - self._lastNotifDT).total_seconds()) + 1):
self._lastNotifId = 0
# calculate notification values
notificationTimeout = min(cons.TK_CL_NOTIF_MAX, max(0, notificationTimeout)) * 1000
# notification value of 0 means "forever"
actions = ["OK", "OK"] if notificationTimeout == 0 else []
log.log(cons.TK_LOG_LEVEL_DEBUG, "preshow: %s, %s, %i" % (msg.getTranslation("TK_MSG_NOTIFICATION_PLAYTIME_TITLE" if pMsgType == "PlayTime" else "TK_MSG_NOTIFICATION_TITLE"), msgStr, notificationTimeout))
# notify through dbus
try:
# before
notifId = 0
# call dbus method
notifId = self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF].Notify(
"Timekpr"
,self._lastPTNotifId if pMsgType == "PlayTime" else self._lastNotifId
,timekprIcon
,msg.getTranslation("TK_MSG_NOTIFICATION_PLAYTIME_TITLE" if pMsgType == "PlayTime" else "TK_MSG_NOTIFICATION_TITLE")
,msgStr
,actions
,hints
,notificationTimeout
)
except Exception as dbusEx:
# do not need to reconnect when too many notifs
if "ExcessNotificationGeneration" not in str(dbusEx):
# we cannot send notif through dbus
self._dbusConnections[self.CL_CONN_NOTIF][self.CL_IF] = None
# logging
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR (DBUS): \"%s\" in \"%s.%s\"" % (str(dbusEx), __name__, self.notifyUser.__name__))
else:
# logging
log.log(cons.TK_LOG_LEVEL_WARN, "WARNING (DBUS): \"%s\" in \"%s.%s\"" % (str(dbusEx), __name__, self.notifyUser.__name__))
# save notification ID (only if message is not about PlayTime, otherwise it may dismiss standard time or vice versa)
if pMsgType == "PlayTime":
self._lastPTNotifId = notifId
self._lastPTNotifDT = datetime.now()
else:
self._lastNotifId = notifId
self._lastNotifDT = datetime.now()
# user wants to hear things
if self._timekprClientConfig.getIsNotificationSpeechSupported() and self._timekprClientConfig.getClientUseSpeechNotifications():
# say that out loud
self._timekprSpeechManager.saySmth(msgStr)
# --------------- admininstration / verification methods --------------- #
def verifySessionAttributes(self, pWhat, pKey):
"""Receive the signal and process the data"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "prepare verification of attributes for server: %s, %s" % (pWhat, "key"))
# def
value = None
# for screensaver status
if pWhat == cons.TK_CTRL_SCR_N:
# value
value = str(bool(self._dbusConnections[self.CL_CONN_SCR][self.CL_IF].GetActive()))
# resend stuff to server
self.processUserSessionAttributes(pWhat, pKey, value)
# --------------- admininstration / verification signals --------------- #
def receiveScreenSaverActivityChange(self, pIsActive):
"""Receive the signal and process the data"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive screensaver activity changes: %s" % (str(bool(pIsActive))))
# request to server for verification
self.processUserSessionAttributes(cons.TK_CTRL_SCR_N)
def receiveNotificationClosed(self, pNotifId, pReason):
"""Receive the signal and process the data"""
log.log(cons.TK_LOG_LEVEL_DEBUG, "receive notification closed: %i, %i" % (pNotifId, pReason))
# check and reset which notification has changed
if self._lastNotifId == pNotifId:
self._lastNotifId = 0
elif self._lastPTNotifId == pNotifId:
self._lastPTNotifId = 0
# --------------- request methods to timekpr --------------- #
def requestTimeLeft(self):
"""Request time left from server"""
# if we have dbus connection, let"s do so
if self._dbusConnections[self.CL_CONN_TK][self.CL_IF] is None:
# init
self.initClientConnections()
# if we have end-point
if self._dbusConnections[self.CL_CONN_TK][self.CL_IF] is not None:
log.log(cons.TK_LOG_LEVEL_INFO, "requesting timeleft")
# notify through dbus
try:
# call dbus method
result, message = self._dbusConnections[self.CL_CONN_TK][self.CL_IF].requestTimeLeft(self._userName)
# check call result
if result != 0:
# show message to user as well
self.notifyUser(cons.TK_MSG_CODE_REMOTE_INVOCATION_ERROR, None, cons.TK_PRIO_CRITICAL, pAdditionalMessage=message)
except Exception as dbusEx:
# we cannot send notif through dbus
self._dbusConnections[self.CL_CONN_TK][self.CL_IF] = None
# logging
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR (DBUS): \"%s\" in \"%s.%s\"" % (str(dbusEx), __name__, self.requestTimeLeft.__name__))
# show message to user as well
self.notifyUser(cons.TK_MSG_CODE_REMOTE_COMMUNICATION_ERROR, None, cons.TK_PRIO_CRITICAL, pAdditionalMessage=msg.getTranslation("TK_MSG_NOTIFICATION_CONNECTION_ERROR"))
def requestTimeLimits(self):
"""Request time limits from server"""
# if we have dbus connection, let"s do so
if self._dbusConnections[self.CL_CONN_TK][self.CL_IF] is None:
# init
self.initClientConnections()
# if we have end-point
if self._dbusConnections[self.CL_CONN_TK][self.CL_IF] is not None:
log.log(cons.TK_LOG_LEVEL_INFO, "requesting timelimits")
# notify through dbus
try:
# call dbus method
result, message = self._dbusConnections[self.CL_CONN_TK][self.CL_IF].requestTimeLimits(self._userName)
# check call result
if result != 0:
# show message to user as well
self.notifyUser(cons.TK_MSG_CODE_REMOTE_INVOCATION_ERROR, None, cons.TK_PRIO_CRITICAL, pAdditionalMessage=message)
except Exception as dbusEx:
# we cannot send notif through dbus
self._dbusConnections[self.CL_CONN_TK][self.CL_IF] = None
# logging
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR (DBUS): \"%s\" in \"%s.%s\"" % (str(dbusEx), __name__, self.requestTimeLimits.__name__))
# show message to user as well
self.notifyUser(cons.TK_MSG_CODE_REMOTE_COMMUNICATION_ERROR, None, cons.TK_PRIO_CRITICAL, pAdditionalMessage=msg.getTranslation("TK_MSG_NOTIFICATION_CONNECTION_ERROR"))
def processUserSessionAttributes(self, pWhat, pKey=None, pValue=None):
"""Process user session attributes from server"""
# if we have dbus connection, let"s do so
if self._dbusConnections[self.CL_CONN_TK][self.CL_IFA] is None:
# init
self.initClientConnections()
# if we have end-point
if self._dbusConnections[self.CL_CONN_TK][self.CL_IFA] is not None:
log.log(cons.TK_LOG_LEVEL_INFO, "%s session attributes" % ("requesting" if pKey is None else "verifying"))
# notify through dbus
try:
# call dbus method
result, message = self._dbusConnections[self.CL_CONN_TK][self.CL_IFA].processUserSessionAttributes(
self._userName,
dbus.String(pWhat if pWhat is not None else ""),
dbus.String(pKey if pKey is not None else ""),
dbus.String(pValue if pValue is not None else ""))
# check call result
if result != 0:
# show message to user as well
self.notifyUser(cons.TK_MSG_CODE_REMOTE_INVOCATION_ERROR, None, cons.TK_PRIO_CRITICAL, None, pAdditionalMessage=message)
except Exception as dbusEx:
# we cannot send notif through dbus
self._dbusConnections[self.CL_CONN_TK][self.CL_IF] = None
self._dbusConnections[self.CL_CONN_TK][self.CL_IFA] = None
# logging
log.log(cons.TK_LOG_LEVEL_INFO, "ERROR (DBUS): \"%s\" in \"%s.%s\"" % (str(dbusEx), __name__, self.processUserSessionAttributes.__name__))
# show message to user as well
self.notifyUser(cons.TK_MSG_CODE_REMOTE_COMMUNICATION_ERROR, None, cons.TK_PRIO_CRITICAL, pAdditionalMessage=msg.getTranslation("TK_MSG_NOTIFICATION_CONNECTION_ERROR"))
timekpr-next/client/interface/dbus/__init__.py 000664 001750 001750 00000000000 13476006650 023456 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/client/interface/dbus/administration.py 000644 001750 001750 00000110362 15122253261 024747 0 ustar 00bezvfedu bezvfedu 000000 000000 """
Created on Aug 28, 2018
@author: mjasnik
"""
# import
import dbus
from gi.repository import GLib
from dbus.mainloop.glib import DBusGMainLoop
# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.common.utils import misc
from timekpr.common.constants import messages as msg
# default loop
DBusGMainLoop(set_as_default=True)
class timekprAdminConnector(object):
"""Main class for supporting indicator notifications"""
def __init__(self):
"""Initialize stuff for connecting to timekpr server"""
# times
self._retryCountLeft = 10
self._initFailed = False
self._isCLI = False
# dbus (timekpr)
self._timekprBus = (dbus.SessionBus() if (cons.TK_DEV_ACTIVE and cons.TK_DEV_BUS == "ses") else dbus.SystemBus())
self._timekprObject = None
self._timekprUserAdminDbusInterface = None
self._timekprAdminDbusInterface = None
def initTimekprConnection(self, pTryOnce, pRescheduleConnection=False, pCLI=None):
"""Init dbus (connect to timekpr for info)"""
# def
if pCLI is not None:
self._isCLI = pCLI
# reschedule
if pRescheduleConnection:
# rescheduling means dropping existing state and try again
self._timekprObject = None
self._timekprUserAdminDbusInterface = None
self._timekprAdminDbusInterface = None
self._retryCountLeft = 10
self._initFailed = False
# only if notifications are ok
if self._timekprObject is None:
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# timekpr connection stuff
self._timekprObject = self._timekprBus.get_object(cons.TK_DBUS_BUS_NAME, cons.TK_DBUS_SERVER_PATH)
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pPrintToConsole=True, pDbusIFName=cons.TK_DBUS_BUS_NAME)
except Exception:
self._timekprObject = None
# logging
lMsg = "FAILED to obtain connection to timekpr. Please check that timekpr daemon is working and you have sufficient permissions to access it (either superuser or timekpr group)"
log.log(cons.TK_LOG_LEVEL_ERR, lMsg)
# console too, if CLI
if self._isCLI:
log.consoleOut(lMsg)
# only if notifications are ok
if self._timekprObject is not None and self._timekprUserAdminDbusInterface is None:
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# getting interface
self._timekprUserAdminDbusInterface = dbus.Interface(self._timekprObject, cons.TK_DBUS_USER_ADMIN_INTERFACE)
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pPrintToConsole=True, pDbusIFName=cons.TK_DBUS_USER_ADMIN_INTERFACE)
except Exception:
self._timekprUserAdminDbusInterface = None
# logging
lMsg = "FAILED to connect to timekpr user admin interface. Please check that timekpr daemon is working and you have sufficient permissions to access it (either superuser or timekpr group)"
log.log(cons.TK_LOG_LEVEL_ERR, lMsg)
# console too, if CLI
if self._isCLI:
log.consoleOut(lMsg)
# only if notifications are ok
if self._timekprObject is not None and self._timekprAdminDbusInterface is None:
try:
# dbus performance measurement
misc.measureDBUSTimeElapsed(pStart=True)
# getting interface
self._timekprAdminDbusInterface = dbus.Interface(self._timekprObject, cons.TK_DBUS_ADMIN_INTERFACE)
# measurement logging
misc.measureDBUSTimeElapsed(pStop=True, pPrintToConsole=True, pDbusIFName=cons.TK_DBUS_ADMIN_INTERFACE)
except Exception:
self._timekprAdminDbusInterface = None
# logging
lMsg = "FAILED to connect to timekpr user admin interface. Please check that timekpr daemon is working and you have sufficient permissions to access it (either superuser or timekpr group)"
log.log(cons.TK_LOG_LEVEL_ERR, lMsg)
# console too, if CLI
if self._isCLI:
log.consoleOut(lMsg)
# if either of this fails, we keep trying to connect
if self._timekprUserAdminDbusInterface is None or self._timekprAdminDbusInterface is None:
if self._retryCountLeft > 0 and not pTryOnce:
self._retryCountLeft -= 1
lMsg = "connection failed, %i attempts left, will retry in %i seconds" % (self._retryCountLeft, cons.TK_POLLTIME)
log.log(cons.TK_LOG_LEVEL_ERR, lMsg)
# console too, if CLI
if self._isCLI:
log.consoleOut(lMsg)
# if either of this fails, we keep trying to connect
GLib.timeout_add_seconds(cons.TK_POLLTIME, self.initTimekprConnection, pTryOnce)
else:
# failed
self._initFailed = True
# --------------- helper methods --------------- #
def isConnected(self):
"""Return status of connection to DBUS"""
# if either of this fails, we keep trying to connect
return not (self._timekprUserAdminDbusInterface is None or self._timekprAdminDbusInterface is None), not self._initFailed
def formatException(self, pExceptionStr, pFPath, pFName):
"""Format exception and pass it back"""
# check for permission error
if "org.freedesktop.DBus.Error.AccessDenied" in pExceptionStr:
result = -1
message = msg.getTranslation("TK_MSG_DBUS_COMMUNICATION_COMMAND_FAILED")
else:
result = -1
message = msg.getTranslation("TK_MSG_UNEXPECTED_ERROR") % (("\"%s\" in \"%s.%s\"") % (pExceptionStr, pFPath, pFName))
# log error
log.log(cons.TK_LOG_LEVEL_ERR, "ERROR: \"%s\" in \"%s.%s\"" % (pExceptionStr, pFPath, pFName))
# result
return result, message
def initReturnCodes(self, pInit, pCall):
"""Initialize the return codes for calls"""
return -2 if pInit else -1 if pCall else 0, msg.getTranslation("TK_MSG_STATUS_INTERFACE_NOTREADY") if pInit else msg.getTranslation("TK_MSG_DBUS_COMMUNICATION_COMMAND_NOT_ACCEPTED") if pCall else ""
# --------------- user configuration info population methods --------------- #
def getUserList(self):
"""Get user list from server"""
# defaults
result, message = self.initReturnCodes(pInit=True, pCall=False)
userList = []
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message, userList = self._timekprUserAdminDbusInterface.getUserList()
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.getUserList.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message, userList
def getUserConfigurationAndInformation(self, pUserName, pInfoLvl):
"""Get user configuration from server"""
# defaults
result, message = self.initReturnCodes(pInit=True, pCall=False)
userConfig = {}
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message, userConfig = self._timekprUserAdminDbusInterface.getUserInformation(pUserName, pInfoLvl)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.getUserConfigurationAndInformation.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message, userConfig
# --------------- user configuration set methods --------------- #
def setAllowedDays(self, pUserName, pDayList):
"""Set user allowed days"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setAllowedDays(pUserName, pDayList)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setAllowedDays.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setAllowedHours(self, pUserName, pDayNumber, pHourList):
"""Set user allowed days"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setAllowedHours(pUserName, pDayNumber, pHourList)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setAllowedHours.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimeLimitForDays(self, pUserName, pDayLimits):
"""Set user allowed limit for days"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setTimeLimitForDays(pUserName, pDayLimits)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimeLimitForDays.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimeLimitForWeek(self, pUserName, pTimeLimitWeek):
"""Set user allowed limit for week"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setTimeLimitForWeek(pUserName, pTimeLimitWeek)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimeLimitForWeek.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimeLimitForMonth(self, pUserName, pTimeLimitMonth):
"""Set user allowed limit for month"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setTimeLimitForMonth(pUserName, pTimeLimitMonth)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimeLimitForMonth.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTrackInactive(self, pUserName, pTrackInactive):
"""Set user allowed days"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setTrackInactive(pUserName, pTrackInactive)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.getUserList.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setHideTrayIcon(self, pUserName, pHideTrayIcon):
"""Set user allowed days"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setHideTrayIcon(pUserName, pHideTrayIcon)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setHideTrayIcon.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setLockoutType(self, pUserName, pLockoutType, pWakeFrom, pWakeTo):
"""Set user restriction / lockout type"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setLockoutType(pUserName, pLockoutType, pWakeFrom, pWakeTo)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setLockoutType.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimeLeft(self, pUserName, pOperation, pTimeLeft):
"""Set user time left"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setTimeLeft(pUserName, pOperation, pTimeLeft)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimeLeft.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
# --------------- PlayTime user configuration info set methods --------------- #
def setPlayTimeEnabled(self, pUserName, pPlayTimeEnabled):
"""Set PlayTime enabled flag for user"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeEnabled(pUserName, pPlayTimeEnabled)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeEnabled.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setPlayTimeLimitOverride(self, pUserName, pPlayTimeLimitOverride):
"""Set PlayTime override flag for user"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeLimitOverride(pUserName, pPlayTimeLimitOverride)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeLimitOverride.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setPlayTimeUnaccountedIntervalsEnabled(self, pUserName, pPlayTimeUnaccountedIntervalsEnabled):
"""Set PlayTime allowed during unaccounted intervals flag for user"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeUnaccountedIntervalsEnabled(pUserName, pPlayTimeUnaccountedIntervalsEnabled)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeUnaccountedIntervalsEnabled.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setPlayTimeAllowedDays(self, pUserName, pPlayTimeAllowedDays):
"""Set allowed days for PlayTime for user"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeAllowedDays(pUserName, pPlayTimeAllowedDays)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeAllowedDays.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setPlayTimeLimitsForDays(self, pUserName, pPlayTimeLimits):
"""Set PlayTime limits for the allowed days for the user"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeLimitsForDays(pUserName, pPlayTimeLimits)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeLimitsForDays.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setPlayTimeActivities(self, pUserName, pPlayTimeActivities):
"""Set PlayTime limits for the allowed days for the user"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeActivities(pUserName, pPlayTimeActivities)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeActivities.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setPlayTimeLeft(self, pUserName, pOperation, pTimeLeft):
"""Set user time left"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprUserAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprUserAdminDbusInterface.setPlayTimeLeft(pUserName, pOperation, pTimeLeft)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setPlayTimeLeft.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
# --------------- timekpr configuration info population / set methods --------------- #
def getTimekprConfiguration(self):
"""Get configuration from server"""
# defaults
result, message = self.initReturnCodes(pInit=True, pCall=False)
timekprConfig = {}
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message, timekprConfig = self._timekprAdminDbusInterface.getTimekprConfiguration()
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.getTimekprConfiguration.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message, timekprConfig
def setTimekprLogLevel(self, pLogLevel):
"""Set the logging level for server"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprLogLevel(pLogLevel)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprLogLevel.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprPollTime(self, pPollTimeSecs):
"""Set polltime for timekpr"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprPollTime(pPollTimeSecs)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprPollTime.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprSaveTime(self, pSaveTimeSecs):
"""Set save time for timekpr"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprSaveTime(pSaveTimeSecs)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprSaveTime.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprTrackInactive(self, pTrackInactive):
"""Set default value for tracking inactive sessions"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprTrackInactive(pTrackInactive)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprTrackInactive.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprTerminationTime(self, pTerminationTimeSecs):
"""Set up user termination time"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprTerminationTime(pTerminationTimeSecs)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprTerminationTime.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprFinalWarningTime(self, pFinalWarningTimeSecs):
"""Set up final warning time for users"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprFinalWarningTime(pFinalWarningTimeSecs)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprFinalWarningTime.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprFinalNotificationTime(self, pFinalNotificationTimeSecs):
"""Set up final notification time for users"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprFinalNotificationTime(pFinalNotificationTimeSecs)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprFinalNotificationTime.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprSessionsCtrl(self, pSessionsCtrl):
"""Set accountable session types for users"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprSessionsCtrl(pSessionsCtrl)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprSessionsCtrl.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprSessionsExcl(self, pSessionsExcl):
"""Set NON-accountable session types for users"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprSessionsExcl(pSessionsExcl)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprSessionsExcl.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprUsersExcl(self, pUsersExcl):
"""Set excluded usernames for timekpr"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprUsersExcl(pUsersExcl)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprUsersExcl.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprPlayTimeEnabled(self, pPlayTimeEnabled):
"""Set up global PlayTime enable switch"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprPlayTimeEnabled(pPlayTimeEnabled)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprPlayTimeEnabled.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
def setTimekprPlayTimeEnhancedActivityMonitorEnabled(self, pPlayTimeEnabled):
"""Set up global PlayTime enhanced activity monitor enable switch"""
# initial values
result, message = self.initReturnCodes(pInit=True, pCall=False)
# if we have end-point
if self._timekprAdminDbusInterface is not None:
# defaults
result, message = self.initReturnCodes(pInit=False, pCall=True)
# notify through dbus
try:
# call dbus method
result, message = self._timekprAdminDbusInterface.setTimekprPlayTimeEnhancedActivityMonitorEnabled(pPlayTimeEnabled)
except Exception as ex:
# exception
result, message = self.formatException(str(ex), __name__, self.setTimekprPlayTimeEnhancedActivityMonitorEnabled.__name__)
# we cannot send notif through dbus, we need to reschedule connecton
self.initTimekprConnection(False, True)
# result
return result, message
timekpr-next/debian/ 000775 001750 001750 00000000000 15122253261 016416 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/debian/timekpr-next.lintian-overrides 000600 001750 001750 00000000155 14575617250 024431 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next binary: desktop-command-not-in-package pkexec [usr/share/applications/timekpr-admin-su.desktop]
timekpr-next/debian/postinst 000700 001750 001750 00000004657 14575617135 020246 0 ustar 00bezvfedu bezvfedu 000000 000000 #!/bin/sh
# timekpr directories and units
TK_DIR="/usr/lib/python3/dist-packages/timekpr"
# unit name
TK_UNIT_NAME="timekpr.service"
# unit directory (starting with debian)
TK_UNIT_DIR="/lib/systemd/system"
# prefixes for other distros then debian based
TK_UNIT_DIR_PREFIXES="/usr"
# we need to take into account the various directory configs
for R_PREF in ${TK_UNIT_DIR_PREFIXES};
do
# alternatives exist
if [ -d "${R_PREF}${TK_UNIT_DIR}" ];
then
# assign additional prefix
TK_UNIT_DIR="${R_PREF}${TK_UNIT_DIR}"
# this is it
break
fi
done
# final unit
TK_UNIT="${TK_UNIT_DIR}/${TK_UNIT_NAME}"
# add timekpr group (for accessing administration functionality without sudo)
echo "Checking and/or adding timekpr group to access functionality without superuser privileges"
groupadd -g 2000 timekpr 2>/dev/null || true
# remove compiled python units (may interfere with python versions)
for R_DIR in ${TK_DIR};
do
# if compiled units exist, remove them
if [ -d "${R_DIR}" ];
then
# remove compiled
echo "Cleaning up Timekpr-nExT compiled units"
find "${R_DIR}" -type d -name '__pycache__' -exec rm -rf {} \; -prune
find "${R_DIR}" -type f -iname '*.pyc' -exec rm {} \;
fi
done
# previously we installed unit into /etc, we need to take care of it
TK_OBSOLETE_UNIT="/etc/systemd/system/${TK_UNIT_NAME}"
if [ -f "${TK_OBSOLETE_UNIT}" ];
then
echo "Removing obsolete Timekpr-nExT systemd unit"
rm "${TK_OBSOLETE_UNIT}"
fi
# enable service by default and start it
echo "Enabling Timekpr-nExT systemd unit"
systemctl enable "${TK_UNIT}"
# reload d-bus for access controls and systemd for service removals
echo "Reloading dbus daemon to refresh access policies for Timekpr-nExT daemon"
systemctl reload dbus
echo "Reloading systemd to refresh unit for Timekpr-nExT"
systemctl daemon-reload
# restart or start (if that was not started before)
echo "Starting Timekpr-nExT"
systemctl restart timekpr
# update gtk icon cache (if utility exists)
which gtk-update-icon-cache > /dev/null 2>&1
# utility exists, update cache
if [ $? -eq 0 ];
then
# determine whether cache exists and choose particular args for that
if [ -f /usr/share/icons/hicolor/icon-theme.cache ];
then
ICOARGS="-fv"
else
ICOARGS="-f"
fi
echo "Update icon cache"
touch --no-create /usr/share/icons/hicolor && gtk-update-icon-cache ${ICOARGS} /usr/share/icons/hicolor
fi
timekpr-next/debian/source/ 000775 001750 001750 00000000000 13476006650 017726 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/debian/source/format 000664 001750 001750 00000000016 13476006650 021136 0 ustar 00bezvfedu bezvfedu 000000 000000 3.0 (native)
timekpr-next/debian/compat 000664 001750 001750 00000000002 13476006650 017624 0 ustar 00bezvfedu bezvfedu 000000 000000 9
timekpr-next/debian/copyright 000644 001750 001750 00000000574 15122253204 020352 0 ustar 00bezvfedu bezvfedu 000000 000000 This package was debianized by Eduards Bezverhijs (mjasnik)
on Sun, 23 Sep 2018 01:10:17 +0200.
Upstream Authors:
Eduards Bezverhijs
Copyright:
2018-2024 Eduards Bezverhijs
All code is released under the GPLv3, see
/usr/share/common-licenses/GPL-3
With the exception of the artwork SVG source files,
which are released to the Public Domain.
timekpr-next/debian/changelog 000644 001750 001750 00000105722 15122254113 020272 0 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next (0.5.9-1ubuntu1~ppa1) unstable; urgency=medium
* fixed a bug in notifications when time is not limited was shown even the time was limited
this bug manifested itself only when there were only weekly or monthly time limits enforced on users
* fixed a bug with dbus notification signals for unknown python version (MR !492908 from alexandre-nunes / Houston4444)
* fixed a bug with config parser (#2130832)
in python 3.14 it became totally not fit for my purpose, rewrote config handling completely
* added more verbose error logging
* added synonyms for information retrieval options in CLI:
--getuserlist is the same as --userlist
--getuserinfo is the same as --userinfo
--getuserinfort is the same as --userinfort
* added possibility for CLI version to specify time as time string for those who cannot or don't want to convert time to seconds:
time can be specified as 1h10m10s as opposed to 4210 seconds previously
this affects the following options:
--settimelimits
--settimelimitweek
--settimelimitmonth
--settimeleft
--setplaytimelimits
--setplaytimeleft
time can be returned either in seconds (as currently) and in time string if "-h" is passed to information retrieval options:
--getuserinfo
--getuserinfort
* fixed a hint / description for "Hide icon and notifications" checkbox
* added fr_CA and en_GB translations from LP
* fix the shebangs, feng-shui style
* added support for dynamic users which are not specified in passwd (for example, gdm-greeter)
* added support for journald logging
timekpr logs are pushed to systemd logging facilities, if available
fallback to standard logging
* lock sessions are now performed from login1 manager interface rather than from user session one
-- Eduards Bezverhijs Mon, 22 Dec 2025 16:39:21 +0200
timekpr-next (0.5.8-1ubuntu1~ppa1) unstable; urgency=medium
* added translations for Russian, Dutch, Portugese languages
* improved sound notificatons:
added support for espeak-ng
system "bell" notifications are no longer relient on freedesktop sound files
* packaging warning fixes (#2089143)
* enable standard window close button (workaround for #2091011)
* fixes for .rpm packaging from Alexey (#2091080)
* improve pidfile handling (#2097253)
* added an option to kill sessions instead of terminate
at least Kubuntu 24.04 will not terminate sessions with TerminateSession, it works fine with KillSession + SIGTERM
* implement a workaround for weird CMOS date reset bug which results time being reported 55 years in the future
which results in a lot of free time for the subordinate (this is exceptionally rare condition)
-- Eduards Bezverhijs Sat, 10 May 2025 17:29:42 +0300
timekpr-next (0.5.7-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* improve username and user shell filter (fixes #2061003)
usernames which do not conform to standard username patterns are filtered out
users with no login shell are filtered out
-- Eduards Bezverhijs Thu, 30 May 2024 21:09:45 +0300
timekpr-next (0.5.6-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* remove UUID upper boundary check (fixes #2046222)
any user whose ID is greater than UUID_MIN is considered controllable by timekpr
this helps with systemd-homed, remote LDAP logins etc, where UID is arbitrary high
* fix a long standding bug when weekly limits are reset together with monthly limits
this occurs only when new month starts in the middle of the week
* improve session time accounting for display / reporting purposes
actually account and display all active and sleep time in case user is logged in for more than 2 days or even more than a week
* color "apply" buttons and tab pages in red
to warn users that there are pending changes to apply
* added keyboard navigation focus order to list type of elements (where there are ediable columns)
swapped day limit and enabled columns due to weird GTK behaviour when toggle can not be activated using keyboard as first editable item in list
* disable search boxes on list elements as they pop up using keyboard navigation
they are not needed and just confuse users
* fix "verify" button enablement
it was enabled even when user clicks in and out of "from" field and it contained 0 hour
* made lists a little wider for adwaita theme not to show horizontal scrollbar
* fix row positioning "Gtk-CRITICAL" when there is no rows in the list
* minor dbus performance measurement optimization
* prevent saving / processing of duplicate timekpr configuration which can happen very rarely
* when user sets / saves configuration or timekpr loads it from file, log it to the log file
this way the log file will contain configuration too and that will be enough to ask for debugging if smth goes south
* added new "realtime" information get method to CLI + translations
* trying to cover as much DE as we can for idle time accounting (still have to be compatible with freedesktop / gnome standard)
add more delay to connect to screensaver (hopefully that helps computers with slower DE start)
rewrote screensaver dbus name detection based on DE name - this should help with some DEs which are
less known but still have a compatible screensaver interface
fall back to gnome and then to freedesktop interfaces (gnome is more popular, freedesktop - very few actually implement it)
if nothing else is detected and works
-- Eduards Bezverhijs Sun, 17 Mar 2024 18:02:25 +0200
timekpr-next (0.5.5-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* add initial support for systemd-homed users and systemd-homed dynamic users
* Spanish translations, thanks to Ivan L.
* add couple of Debian specific packaging fixes
* check for login.defs in multiple possible directories (for example, openSUSE moved the file to /usr)
-- Eduards Bezverhijs Fri, 1 Sep 2023 13:22:13 +0300
timekpr-next (0.5.4-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* translations
Belorussian translations, thanks to Aliaxandr
enabled German translations
* misc
logging changes (more refined and enough verbose)
regular and PT notifications now are separate and follow the same logic (+logging)
added logging callback to know when the notification is closed
-- Eduards Bezverhijs Fri, 7 Oct 2022 07:39:11 +0300
timekpr-next (0.5.3-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* bug fixes
fix DBUS time notification signal and intermittent user session restriction enforcement issues due to this error:
"TypeError: 'float' object cannot be interpreted as an integer"
this seems to happen on very recent python versions
* translations
German translations, thanks to Robert E.
* misc
add logging for linux distribution identification and core components (python version, dbus-python version)
-- Eduards Bezverhijs Mon, 21 Feb 2022 22:50:53 +0200
timekpr-next (0.5.2-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* features
PlayTime left is now shown besides the icon / on label
* bug fixes
fix #1927771 (the issue was improper configuration when limit count was less then allowed day count)
additionally allow setting empty day / limit lists for regular time and PlayTime
fix error handling with empty lists
properly check allowed day numbers according to allowed values (1-7)
a small fix for time leak (couple of seconds) on day change
* technical improvements
address the situation when session type can change during the session in logind (https://github.com/systemd/systemd/pull/14925)
improved session accounting for situations when session type is in tracked list at the same time as in excluded, exclusion takes precedence
improved session logging to determine why the time for session is or is not accounted
improved session remaining process killer to include processes that use pseudo-terminals as well as recording a summary of killing results
prevent an installation error when blindly recreating GTK icon cache even if it is not present, in these cases just create it
properly check whether an instance of timekpr applications is actually running
on day change, reset not only this day spent and sleep times, but for next day too
tiny improvement on sleep time precision
make admin application to exit gracefully when it was opened from terminal and ctrl+c was pressed (SIGINT)
less logging in standard debug mode / remove duplicate logging for PT
-- Eduards Bezverhijs Wed, 23 Jun 2021 12:34:38 +0300
timekpr-next (0.5.1-2ubuntu1~ppa1) UNRELEASED; urgency=medium
* quick fix for PlayTime notifications
revert the cap in daemon for sending PlayTime left to clients
admin app will always show actual PlayTime left
client app will receive actual PlayTime left, but it will show
minimum of PlayTime or standard time left for that particular day to user (child, subordinate)
* possibly fix a bug #1917916 when process changed the owner from regular user to system user but was still left in user process list
-- Eduards Bezverhijs Fri, 5 Mar 2021 11:48:25 +0200
timekpr-next (0.5.1-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* adjust README to fix MD parsing nuances on gitlab
* add gnome-shell-extension-appindicator to suggestions list
* fix project description to match the README
* technical improvements
added ":" as allowed separator for minute intervals (undocumented feature)
improve a workaround in config loader routines for cases when user makes a mistake while manually editing the config files
added explanatory comment for values in control files
print out uname when timekpr starts to better understand the environment the timekpr is running in
log more information about timekpr performance and computer load average
log information about day / week / month change
added elapsed time information output on lowest log level
rewritten logging routines and implemented buffered log save
time spent is accounted and saved even when file has been modified externally (this should help with proper time accounting
(2x+) if user is logged in from multiple machines when commonly shared directory is used)
* registered bug fixes
adjust Debian installation suggestions in README (fixes #1910457)
start using ayatana appindicators because regular ones seem to be deprecated (fixes #1910461)
adjust for openSUSE too (Fedora does not seem to be using ayatana appindicators yet)
* unregistered bug fixes
fixes to client application when is pressed instead of close window which leads to empty timekpr windows
fixes to PlayTime daily limit reset when the day has changed
fix for PlayTime disabled -> enabled did not work right away when user is already logged in
fix for user time accounting which added couple of seconds to time spent when using unaccountable intervals
fix time accounting for PlayTime in override mode and unaccounted time intervals
fix process handling when process does not have a normal user id that process monitor is interested in and it changed executable
or process changed user id from one we were interested in to one we are not
fix timekpr behaviour when user was able to use computer prior the starting minutes of unaccounted hour interval
now user will be dealt according to defined rules instead of using the computer when he is not allowed to
lift the terminate / shutdown restriction when termination sequence has been initialized, but admin gives more time prior to
terminate or shutdown
* new features
introduce an option to prevent PlayTime activities to be started during unaccountable ("∞") intervals
introduce an option to monitor PlayTime activities (processes) using first 512 characters of command line
by default only process name is used for activity detection
logging level changes are applied immediately
gray icon for unaccounted hours, which help to distinguish the interval, especially when there is no time left (was: red icon)
add possibility to edit single daily, daily PlayTime and weekly/monthly limits by editing value directly
-- Eduards Bezverhijs Mon, 1 Mar 2021 21:49:33 +0200
timekpr-next (0.5.0-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* fix a bug when administration application tries to get user information
from server even when no user has been selected
* added clarifications to the template on some keywords not to be translated
fixed LV and IT translations with keywords
* enable Italian translations
* added Italian translations
* added clarifications on some keywords not to be translated
* translation template update, spelling fixes and Latvian translations
* replaced re.escape with simple replace due to it's different behavior in different versions
this fixes user list retrieval in Ubuntu 18.04 (python 3.6.9)
* fixed a bug when user names will not show up in administration application if username contains a . (dot)
* introduced PlayTime and PlayTime override modes
this basically allows a supervisor to account and control specific processes in the system for their subordinates
supervisor has to configure which processes are accounted by entering a process executable names / masks
* introducing "freeride" time periods
* some GUI improvements in client app
* total rewrite of administration app to improve usability
* introducing user configurable notifications
-- Eduards Bezverhijs Tue, 29 Dec 2020 17:51:19 +0200
timekpr-next (0.4.4-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* adjust previously built workaround for switching to login manager screen
-- Eduards Bezverhijs Sun, 25 Oct 2020 19:25:59 +0300
timekpr-next (0.4.3-2ubuntu1~ppa1) UNRELEASED; urgency=medium
* added lock, suspend and shutdown options as a restriction / lockout types
* added option for computer to be woken up automatically if it was put to sleep by timekpr
additionally it's possible to specify hour interval when it is allowed to be woken up
* changed termination sequence a little
if user has no time and locked the screen, user is removed from termination list, which effectively means that
user sessions won't be terminated unless user unlocks the screen or other user logs in via user switching
(we should not leave sessions without time left floating around forever)
* proper english label / message corrections from Phil Hudson (thanks!)
-- Eduards Bezverhijs Sat, 10 Oct 2020 19:59:35 +0300
timekpr-next (0.4.2-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* added verbose output to timekpr installation
* fix license names in AppStream file
* better explain usage notes for CLI usage
* improvements
added average performance metrics for timekpr execution
introduce server ability to survive login manager DBUS interface crash and restore functional timekpr state after recovery
* refactoring
improve various functionality in timekpr codebase
tiny optimizations for variables
improvements for config reader / writer
improvements for in-memory config creation
* fixed #1891921, critical notifications did not show up until final countdown (if show all notifications was checked)
now critical notification will be shown approx one minute before time will end
* improved notification frequency and urgency (now only critical notifications are actually critical)
new frequency values:
if more than 2 hours are left, notifications are shown every 2 hours
if more than 1 hour is left, notifications are shown every 1 hour, i.e. one notification
if more than 30 minutes are left, notifications are shown every 30 minutes, i.e. one notification
if more than 5 minutes are left, notifications are shown every 10 minutes as warnings
if more than a minute is left, notifications are shown every 2 minutes as important warnings
if about a minute is left, notifications are shown every 1 minute (i.e. one notification) as CRITICAL notification
* improved handling of icons
use non GTK icons that should work in every DE
* improved allowed days and respective limits processing in case these are not set up properly
mismatch possible only by manually and incorrectly editing the config file
* fix continous time calculation on day change
this issue did not affect functionality
-- Eduards Bezverhijs Fir, 4 Sep 2020 11:45:09 +0300
timekpr-next (0.4.1-2ubuntu1~ppa1) UNRELEASED; urgency=medium
* fixed a packaging bug with systemd units
-- Eduards Bezverhijs Thu, 30 Jul 2020 20:13:39 +0300
timekpr-next (0.4.1-1ubuntu1~ppa1) UNRELEASED; urgency=medium
* added quality of life configuration options for client application (as suggested by one of Timekpr-nExT users)
** added configuration option to set how long regular notifications should be visible
** added configuration option to set how long Critical notifications should be visible
** if time in seconds is set to 0 for either of options, notification will stay on until dismissed
** added option to play a short (standard) notification bell sound when showing notifications (if supported by current DE)
** added username verification for configuration files
** translations for new options in client application
** added full name display for username
* added quality of life improvements to administration app
** added refresh to actual ("realtime") values for users that are logged in
** a little optimization for time refresh in administration application
** added full name display for usernames (if username is the same as full name, show only username)
* fixed and changed warnings / suggestions from user foka based on his observations when importing package into Debian:
** adjust code install paths (with support for non-Debian distros as well)
** create metainfo file
** adjust service / desktop / control / install / changelog files
** fixed some lintian warnings (will not be fixing all)
** project README file
* changed version numbering scheme
* bug fixes in administration application
** time spent / left was shown from previous time (day / week / month) user was logged in
-- Eduards Bezverhijs Mon, 27 Jul 2020 13:40:01 +0300
timekpr-next (0.4.0~ppa1~ubuntu2) UNRELEASED; urgency=medium
* minor change for debian/install
* added Fedora/openSUSE build instructions
* improved user detection
-- Eduards Bezverhijs Sun, 12 Jul 2020 13:48:36 +0300
timekpr-next (0.4.0~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Registered bug fixes:
** killing user sessions that do not have "seats" will not switch to login manager TTY (#1861479)
** properly show time spent for the day in administration app (#1882909)
** properly set any number values (spinbuttons) in admin app (#1826672)
** restore configuration from saved state in admin app when case uncheck / check is pressed (#1826673)
** it's now possible to set daily limits as well as intervals for multiple days at once (#1826671)
* Discovered bug fixes:
** fix config / control file recreation in case it's missing a configuration option entirely
** fix icon and time representation in system notification area when user has unlimited time left
* Changes / improvements:
** control files (.time) will now contain balance as well as time spent for day (existing TIME_SPENT will be removed, TIME_SPENT_BALANCE and TIME_SPENT_DAY are added)
** console application (timekpra) changes
*** rename TIME_SPENT (previously used as saved time spent value) to TIME_SPENT_BALANCE
*** added an option which represents hide tray icon option (HIDE_TRAY_ICON)
*** rename option "userconfig" to "userinfo" which better represents returned values
*** add method to hide tray icon for user (--sethidetrayicon)
*** added time left for today (TIME_LEFT_DAY) which is calculated from saved state
** console application (timekpra) improvements - added "live" (i.e. calculated every 3 secs) values for options below
*** ACTUAL_TIME_SPENT_SESSION - time spent in this session (since timekpr daemon startup)
*** ACTUAL_TIME_INACTIVE_SESSION - time user was inactive in this session (since timekpr daemon startup)
*** ACTUAL_TIME_SPENT_BALANCE - time spent balance (including added/removed bonus time)
*** ACTUAL_TIME_SPENT_DAY - time user spent this day (actual spent time, example: user has 1 hour, if there was a bonus 1 hr, this will show 2 hrs)
*** ACTUAL_TIME_LEFT_DAY - how much time is left today
*** ACTUAL_TIME_LEFT_CONTINUOUS - how much time is left continuously (but no more than this and next day)
** timekpr will not switch to login manager TTY after killing a user / session that is not active (i.e. in foreground)
** timekpr will reload user list when opening admin application (newly added users will be available in timekpr right away)
** add time inactive (actual) and time left (actual), i.e. when user is logged in, for admin app
** add translations to new features
** minor GUI changes
** message which informs user that timekpr can not connect to screensaver is now shown only when all notifications are enabled
** client application icon will not show if hide icon is checked in admin app
-- Eduards Bezverhijs Wed, 17 Jun 2020 00:33:21 +0300
timekpr-next (0.3.7~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Added Czech language (thanks for translations to NOVAK Ludek)
* Fixed Hungarian language inclusion (thanks for spotting the typo to NOVAK Ludek)
-- Eduards Bezverhijs Tue, 21 Apr 2020 23:19:42 +0300
timekpr-next (0.3.6~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Added support for LockedHint to determine whether session is locked
** requires systemd >= 230, e.g. Ubuntu 18.04 at least
** this is in addition to client screensaver detection, but less used (e.g. KDE does not use this)
* Exclude closing and lingering sessions from being considered as active when tracking inactive sessions is enabled
-- Eduards Bezverhijs Tue, 21 Apr 2020 23:19:42 +0300
timekpr-next (0.3.5~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Improved login manager detection for non-standard names
* Added a previously forgotten Hungarian language to installation
-- Eduards Bezverhijs Sun, 17 Apr 2020 09:56:07 +0300
timekpr-next (0.3.4~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Added more screensaver workarounds for implementations for major DEs
-- Eduards Bezverhijs Sun, 14 Apr 2020 10:31:32 +0300
timekpr-next (0.3.3~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Warn user if screensaver feature is not available for screen idle time detection
** adjust translations as well
-- Eduards Bezverhijs Sun, 14 Apr 2020 11:02:31 +0300
timekpr-next (0.3.2~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Introduced functionality to limit retries to screensaver for DEs that are not compatible with freedesktop or gnome screensaver interface
-- Eduards Bezverhijs Sun, 14 Apr 2020 16:04:02 +0300
timekpr-next (0.3.1~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Introduced workarounds section for screensaver dbus interface to use gnome version instead of freedesktop for Unity and Gnome itself
-- Eduards Bezverhijs Sun, 13 Apr 2020 16:46:35 +0300
timekpr-next (0.3.0~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Added possibility to determine screen lock status reliably for every DE that exposes screensaver status to DBUS (see below)
** It's implemented in client <-> server fashion using org.freedesktop.ScreenSaver
** but as Gnome3 is "special", I had to implement org.gnome.ScreenSaver as well
-- Eduards Bezverhijs Sun, 12 Apr 2020 17:10:06 +0300
timekpr-next (0.2.13~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Fix time reporting
-- Eduards Bezverhijs Mon, 24 Feb 2020 17:39:44 +0300
timekpr-next (0.2.12~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Fix time accounting when check interval is exactly at 0 secs in new hour (fixes a bug #1863482)
-- Eduards Bezverhijs Mon, 17 Feb 2020 00:08:29 +0300
timekpr-next (0.2.11~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Rework time accouting routines (fixes a bug #1861758)
* Fix for week and month accounting when showing continous time (#1856744)
* Rework session caching routines (for preparation to locking detection)
* Fixed dependencies for package
* Improvements for old StatusIcon (at start there will be a default icon and labels as with indicator)
* Tiny python import cleanup
-- Eduards Bezverhijs Fri, 7 Feb 2020 11:29:49 +0300
timekpr-next (0.2.10~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Fixed bug in configuration load when only couple of days are allowed per week
* Added hungarian translation (thanks)
-- Eduards Bezverhijs Sun, 12 Jan 2020 15:46:08 +0300
timekpr-next (0.2.9~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Improve user detection when username is the same as one of the login managers
* Small improvements with login manager detection
* Fix user time accounting due to issues with Python 3.8 and DBUS typecasts
-- Eduards Bezverhijs Mon, 25 Nov 2019 19:50:05 +0300
timekpr-next (0.2.8~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Fixed an issue for huuuuuuuge icons in in Budgie DE (however other DEs did not show such issue)
* A little logging improvement in VTNr search for login managers
* User list is now sorted
* Fixed an issue with object paths (timekpr uses usernames as part of pathnames) on DBUS which contain dots (.) and/or hyphens (-)
-- Eduards Bezverhijs Tue, 24 Sep 2019 01:28:12 +0300
timekpr-next (0.2.7~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Fixed an issue when Timekpr-nExT confiuration may get corrupted or empty after sudden power outage
** all config files as well as time accounting files have previous backups (.prev files)
** invalid config / time files will have .invalid files to inspect later
** improved configuration handling by retrieving as much as possible from non-qualified config file (if user messes up config manually)
* Improved CLI tools (error checking and help)
* Fixed icon handling in race conditions between client and server as well improved icon handling during limit changes
-- Eduards Bezverhijs Thu, 22 Aug 2019 19:23:42 +0300
timekpr-next (0.2.6~ppa1~ubuntu1) UNRELEASED; urgency=medium
* Fixed an issue with Timekpr-nExT client not starting in non-major DE's (for example Deepin)
-- Eduards Bezverhijs Wed, 5 Jun 2019 20:12:12 +0300
timekpr-next (0.2.5~ppa1~ubuntu1) UNRELEASED; urgency=medium
* first official release
-- Eduards Bezverhijs We, 5 Jun 2019 20:12:12 +0300
timekpr-next (0.2.5~ppa1~ubuntu1) UNRELEASED; urgency=medium
* fixed application launcher names for unfortunate Gnome3 users
-- Eduards Bezverhijs Wed, 5 Jun 2019 20:12:12 +0300
timekpr-next (0.2.4~ppa1~ubuntu2) UNRELEASED; urgency=medium
* localization updates (fixes to translations all over the place, thanks to JP Lord)
** updated all other languages with capitalizations
* added French translations
-- Eduards Bezverhijs Mon, 29 Apr 2019 12:47:46 +0300
timekpr-next (0.2.4~ppa1~ubuntu1) UNRELEASED; urgency=medium
* implemented TTY switching functionality for those cases, when login manager sits on different TTY than session itself (this should fix #1823717)
** the implementation is as follows: Timekpr-nExT tries to determine on which TTY login manager sits and saves it's TTY number, login managers have their
** own users in standard installations, therefore we detect them using specific usernames (maybe not the best solution, but it's a solution)
* fixed control file initialization when timekpr starts (fixes #1802583)
* fixed icon in notification area when there is no time limit per day, but time intervals are limited
* improved termination list handling
-- Eduards Bezverhijs Sat, 27 Apr 2019 23:26:05 +0300
timekpr-next (0.2.3~ppa1~ubuntu3) UNRELEASED; urgency=medium
* fixed a bug in logging when multiple users use machine at the same time (permission problem)
-- Eduards Bezverhijs Sat, 27 Apr 2019 17:50:24 +0300
timekpr-next (0.2.3~ppa1~ubuntu2) UNRELEASED; urgency=medium
* kill leftover processes for the user which do not originate from session processes
-- Eduards Bezverhijs Sun, 14 Apr 2019 11:51:41 +0300
timekpr-next (0.2.3~ppa1~ubuntu1) UNRELEASED; urgency=medium
* kill inactive sessions as well (up until this point inactive sessions were saved from killing)
-- Eduards Bezverhijs Sun, 14 Apr 2019 11:51:41 +0300
timekpr-next (0.2.2~ppa1~ubuntu1) UNRELEASED; urgency=medium
* logging improvements (all client side logs are written to /tmp instead of HOME)
* tweak to translation credits (if none are supplied)
-- Eduards Bezverhijs Fri, 12 Apr 2019 15:58:56 +0200
timekpr-next (0.2.1~ppa1~ubuntu2) UNRELEASED; urgency=medium
* packaging fix - we do not need to reference login managers any more
-- Eduards Bezverhijs Mon, 08 Apr 2019 18:34:31 +0300
timekpr-next (0.2.1~ppa1~ubuntu1) UNRELEASED; urgency=medium
* added Italian and German translations
-- Eduards Bezverhijs Mon, 8 Apr 2019 8:25:17 +0300
timekpr-next (0.2.0~ppa1~ubuntu1) UNRELEASED; urgency=medium
* first beta release
-- Eduards Bezverhijs Fri, 29 Mar 2019 8:22:17 +0300
timekpr-next (0.2.0~ppa1~ubuntu1) UNRELEASED; urgency=medium
* implemented localization
* added Latvian translation
-- Eduards Bezverhijs Fri, 29 Mar 2019 8:22:17 +0300
timekpr-next (0.1.15~ppa1~ubuntu1) UNRELEASED; urgency=medium
* implemented localization (translations pending)
-- Eduards Bezverhijs Thu, 28 Mar 2019 18:27:42 +0300
timekpr-next (0.1.14~ppa1~ubuntu1) UNRELEASED; urgency=medium
* timekpr client code optimization and small bug fixes
* added speech output
TODO: localization
-- Eduards Bezverhijs Thu, 21 Mar 2019 21:06:43 +0300
timekpr-next (0.1.13~ppa1~ubuntu1) UNRELEASED; urgency=medium
* finalized admin GUI
* bug fixes here and there
TODO: localization and speech
-- Eduards Bezverhijs Mon, 18 Mar 2019 10:33:59 +0300
timekpr-next (0.1.12~ppa1~ubuntu1) UNRELEASED; urgency=medium
* finalized user admin GUI
* bug fixes here and there
TODO: timekpr administration via GUI
TODO: localization and speech
-- Eduards Bezverhijs Fri, 15 Mar 2019 07:11:23 +0300
timekpr-next (0.1.11~ppa1~ubuntu1) UNRELEASED; urgency=medium
* implemented user admin GUI
TODO: timekpr administration via GUI
TODO: localization and speech
-- Eduards Bezverhijs Sun, 03 Mar 2019 00:42:11 +0300
timekpr-next (0.1.10~ppa1~ubuntu1) UNRELEASED; urgency=medium
* fixed a bug for time accounting when there is a limit set up, but hours are not limited at all
* changed CLI option style from "-" to "--"
TODO: timekpr administration via GUI
TODO: user administration via GUI
TODO: localization and speech
-- Eduards Bezverhijs Tue, 26 Feb 2019 23:08:17 +0300
timekpr-next (0.1.9~ppa1~ubuntu1) UNRELEASED; urgency=medium
* added weekly and monthly quota administration and processing
* minor time accounting fixes
TODO: timekpr administration via GUI
TODO: user administration via GUI
TODO: week and month quotas reporting to user
TODO: localization and speech
-- Eduards Bezverhijs Fri, 22 Feb 2019 18:11:35 +0300
timekpr-next (0.1.8~ppa1~ubuntu1) UNRELEASED; urgency=medium
* fixes for user administration via DBUS and CLI
* major time accounting fixes
* clean up for user data processor
TODO: timekpr administration via GUI
TODO: user administration via GUI
TODO: week and month quotas
TODO: localization and speech
-- Eduards Bezverhijs Fri, 15 Feb 2019 6:53:03 +0300
timekpr-next (0.1.7~ppa1~ubuntu1) UNRELEASED; urgency=medium
* implemented user administration via DBUS
* fixed some bugs with client configuration
* fixed logrotate configuration
TODO: administration via DBUS and CLI/GUI around it
TODO: localization and speech
-- Eduards Bezverhijs Thu, 17 Jan 2019 07:35:19 +0300
timekpr-next (0.1.6~ppa1~ubuntu1) UNRELEASED; urgency=medium
* fixed issue with time being improperly calculated when user is set from "unlimited" mode to limited
* fixed issue about time initialization when day changes and previously user was in "unlimited" mode (mode was changed to limited + he never logged off)
TODO: administration via DBUS and CLI/GUI around it
TODO: localization and speech
-- Eduards Bezverhijs Sun, 18 Dec 2018 20:03:22 +0300
timekpr-next (0.1.5~ppa1~ubuntu1) UNRELEASED; urgency=medium
* fixed lazy dbus connection initialization on client side
* fixes for client side GUI
* minor fixes for time accounting and logging
TODO: administration via DBUS and CLI/GUI around it
TODO: localization and speech
-- Eduards Bezverhijs Sun, 11 Nov 2018 07:55:46 +0300
timekpr-next (0.1.4~ppa1~ubuntu1) UNRELEASED; urgency=medium
* first alpha version of timekpr (a lot has changed since old timekpr 0.3x version)
** new impementation of timekpr-revived (there is almost nothing left of timekpr-revived)
** released to very limited testers
** python3 and gtk3 are the must
** all based on DBUS (server and client)
** session management relies on systemd's login1
** notifications through DBUS (exclusively)
** server is managing everything, except notifications which is the responsibility of client
** major features include the following
*** support DE's which implement appindicaor3 (Unity/Gnome3¹/KDE5/...) and legacy statusIcon
*** support Ubuntu 12.04+ (on launchpad timekpr can support the versions which are on supported distro list)
*** it's possible to configure which session types are accountable
*** inactive session time is NOT accounted (if configured so)
*** time is configurable by days, hours and minutes (yes, limits within hour are possible)
*** there is no "hard" account lock, user will be logged out in 15 seconds if time is over
*** synchronous operation between server and client
*** improved notifications about system behaviour
**** notification about actual limit change
**** notification about general configuration change
**** notification logic improved
**** client acts merely as notificator
**** client can view limits applied by admin
¹ one has to install extension to show timekpr icon, otherwise icon is not shown (gnome architects do not want status icons in their design anymore, timekpr can not do anything about it)
TODO: administration via DBUS and CLI/GUI around it
-- Eduards Bezverhijs Mon, 17 Sep 2018 08:02:17 +0300
timekpr-next/debian/postrm 000775 001750 001750 00000002461 13745331025 017677 0 ustar 00bezvfedu bezvfedu 000000 000000 #!/bin/sh
# timekpr directories and units
TK_DIR="/usr/lib/python3/dist-packages/timekpr"
# unit name
TK_UNIT_NAME="timekpr.service"
# unit directory (starting with debian)
TK_UNIT_DIR="/lib/systemd/system"
# prefixes for other distros then debian based
TK_UNIT_DIR_PREFIXES="/usr"
# we need to take into account the various directory configs
for R_PREF in ${TK_UNIT_DIR_PREFIXES};
do
# alternatives exist
if [ -d "${R_PREF}${TK_UNIT_DIR}" ];
then
# assign additional prefix
TK_UNIT_DIR="${R_PREF}${TK_UNIT_DIR}"
# this is it
break
fi
done
# if there are units, let's handle them
if [ -f "${TK_UNIT_DIR}/${TK_UNIT_NAME}" ];
then
# systemctl
echo "Stopping and disabling Timekpr-nExT units"
systemctl stop timekpr
systemctl disable timekpr
fi
# remove compiled python units (may interfere with python versions)
if [ -d "${TK_DIR}" ];
then
# remove compiled
echo "Cleaning up Timekpr-nExT compiled units"
find "${TK_DIR}" -type d -name '__pycache__' -exec rm -rf {} \; -prune
find "${TK_DIR}" -type f -iname '*.pyc' -exec rm {} \;
fi
# reload d-bus for access controls and systemd for service removals
echo "Reloading dbus daemon to refresh access policies"
systemctl reload dbus
echo "Reloading systemd to refresh units"
systemctl daemon-reload
timekpr-next/debian/conffiles 000600 001750 001750 00000000032 14575617135 020311 0 ustar 00bezvfedu bezvfedu 000000 000000 /etc/timekpr/timekpr.conf
timekpr-next/debian/rules 000775 001750 001750 00000000235 13476006650 017506 0 ustar 00bezvfedu bezvfedu 000000 000000 #!/usr/bin/make -f
clean:
find . -name "*.pyc" -delete
find . -name "__pycache__" -delete
#doc/update.sh
dh $@ --with python3
%:
dh $@ --with python3
timekpr-next/debian/install 000644 001750 001750 00000015364 15122253261 020016 0 ustar 00bezvfedu bezvfedu 000000 000000 # binaries
bin/timekpra usr/bin/
bin/timekprc usr/bin/
bin/timekprd usr/bin/
# config files
resource/server/dbus/timekpr.conf etc/dbus-1/system.d/
resource/server/logrotate.d/timekpr etc/logrotate.d/
resource/server/polkit/com.ubuntu.timekpr.pkexec.policy usr/share/polkit-1/actions/
resource/server/systemd/timekpr.service lib/systemd/system/
resource/server/timekpr.conf etc/timekpr/
# sample runtime files
resource/server/timekpr.USER.conf var/lib/timekpr/config/
resource/server/USER.time var/lib/timekpr/work/
# icons
resource/icons/timekpr-128.png usr/share/icons/hicolor/128x128/apps/
resource/icons/timekpr-48.png usr/share/icons/hicolor/48x48/apps/
resource/icons/timekpr-64.png usr/share/icons/hicolor/64x64/apps/
resource/icons/timekpr-client-128.png usr/share/icons/hicolor/128x128/apps/
resource/icons/timekpr-client-48.png usr/share/icons/hicolor/48x48/apps/
resource/icons/timekpr-client-64.png usr/share/icons/hicolor/64x64/apps/
resource/icons/timekpr-client-logo.svg usr/share/timekpr/icons/
resource/icons/timekpr-client.svg usr/share/icons/hicolor/scalable/apps/
resource/icons/timekpr-logo.svg usr/share/timekpr/icons/
resource/icons/timekpr-padlock-limited-green.svg usr/share/timekpr/icons/
resource/icons/timekpr-padlock-limited-red.svg usr/share/timekpr/icons/
resource/icons/timekpr-padlock-limited-yellow.svg usr/share/timekpr/icons/
resource/icons/timekpr-padlock-limited-uacc.svg usr/share/timekpr/icons/
resource/icons/timekpr-padlock-unlimited-green.svg usr/share/timekpr/icons/
resource/icons/timekpr.svg usr/share/icons/hicolor/scalable/apps/
# launchers
resource/launchers/timekpr-admin.desktop usr/share/applications/
resource/launchers/timekpr-admin-su.desktop usr/share/applications/
resource/launchers/timekpr-client.desktop etc/xdg/autostart/
# metainfo
resource/appstream/org.timekpr.timekpr-next.metainfo.xml usr/share/metainfo/
# forms
resource/client/forms/about.glade usr/share/timekpr/client/forms/
resource/client/forms/admin.glade usr/share/timekpr/client/forms/
resource/client/forms/client.glade usr/share/timekpr/client/forms/
# python common
common/constants/constants.py usr/lib/python3/dist-packages/timekpr/common/constants/
common/constants/__init__.py usr/lib/python3/dist-packages/timekpr/common/constants/
common/constants/messages.py usr/lib/python3/dist-packages/timekpr/common/constants/
common/log/__init__.py usr/lib/python3/dist-packages/timekpr/common/log/
common/log/log.py usr/lib/python3/dist-packages/timekpr/common/log/
common/utils/config.py usr/lib/python3/dist-packages/timekpr/common/utils/
common/utils/__init__.py usr/lib/python3/dist-packages/timekpr/common/utils/
common/utils/misc.py usr/lib/python3/dist-packages/timekpr/common/utils/
common/utils/notifications.py usr/lib/python3/dist-packages/timekpr/common/utils/
# python client
client/admin/adminprocessor.py usr/lib/python3/dist-packages/timekpr/client/admin/
client/gui/admingui.py usr/lib/python3/dist-packages/timekpr/client/gui/
client/gui/clientgui.py usr/lib/python3/dist-packages/timekpr/client/gui/
client/gui/__init__.py usr/lib/python3/dist-packages/timekpr/client/gui/
client/__init__.py usr/lib/python3/dist-packages/timekpr/client/
client/interface/dbus/administration.py usr/lib/python3/dist-packages/timekpr/client/interface/dbus/
client/interface/dbus/daemon.py usr/lib/python3/dist-packages/timekpr/client/interface/dbus/
client/interface/dbus/__init__.py usr/lib/python3/dist-packages/timekpr/client/interface/dbus/
client/interface/dbus/notifications.py usr/lib/python3/dist-packages/timekpr/client/interface/dbus/
client/interface/__init__.py usr/lib/python3/dist-packages/timekpr/client/interface/
client/interface/speech/espeak.py usr/lib/python3/dist-packages/timekpr/client/interface/speech/
client/interface/speech/__init__.py usr/lib/python3/dist-packages/timekpr/client/interface/speech/
client/interface/ui/appindicator.py usr/lib/python3/dist-packages/timekpr/client/interface/ui/
client/interface/ui/__init__.py usr/lib/python3/dist-packages/timekpr/client/interface/ui/
client/interface/ui/notificationarea.py usr/lib/python3/dist-packages/timekpr/client/interface/ui/
client/interface/ui/statusicon.py usr/lib/python3/dist-packages/timekpr/client/interface/ui/
client/timekpra.py usr/lib/python3/dist-packages/timekpr/client/
client/timekprc.py usr/lib/python3/dist-packages/timekpr/client/
# python server
server/config/configprocessor.py usr/lib/python3/dist-packages/timekpr/server/config/
server/config/userhelper.py usr/lib/python3/dist-packages/timekpr/server/config/
server/__init__.py usr/lib/python3/dist-packages/timekpr/server/
server/interface/dbus/consolekit/__init__.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/consolekit/
server/interface/dbus/consolekit/manager.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/consolekit/
server/interface/dbus/consolekit/user.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/consolekit/
server/interface/dbus/daemon.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/
server/interface/dbus/__init__.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/
server/interface/dbus/logind/__init__.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/logind/
server/interface/dbus/logind/manager.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/logind/
server/interface/dbus/logind/user.py usr/lib/python3/dist-packages/timekpr/server/interface/dbus/logind/
server/interface/__init__.py usr/lib/python3/dist-packages/timekpr/server/interface/
server/timekprd.py usr/lib/python3/dist-packages/timekpr/server/
server/user/playtime.py usr/lib/python3/dist-packages/timekpr/server/user/
server/user/__init__.py usr/lib/python3/dist-packages/timekpr/server/user/
server/user/userdata.py usr/lib/python3/dist-packages/timekpr/server/user/
# translations (only the ones that are ready will be included)
resource/locale/be/LC_MESSAGES/timekpr.mo usr/share/locale/be/LC_MESSAGES/
#resource/locale/cs/LC_MESSAGES/timekpr.mo usr/share/locale/cs/LC_MESSAGES/
resource/locale/de/LC_MESSAGES/timekpr.mo usr/share/locale/de/LC_MESSAGES/
resource/locale/en_GB/LC_MESSAGES/timekpr.mo usr/share/locale/en_GB/LC_MESSAGES/
resource/locale/es/LC_MESSAGES/timekpr.mo usr/share/locale/es/LC_MESSAGES/
resource/locale/fr/LC_MESSAGES/timekpr.mo usr/share/locale/fr/LC_MESSAGES/
resource/locale/fr_CA/LC_MESSAGES/timekpr.mo usr/share/locale/fr_CA/LC_MESSAGES/
#resource/locale/hu/LC_MESSAGES/timekpr.mo usr/share/locale/hu/LC_MESSAGES/
resource/locale/it/LC_MESSAGES/timekpr.mo usr/share/locale/it/LC_MESSAGES/
resource/locale/lv/LC_MESSAGES/timekpr.mo usr/share/locale/lv/LC_MESSAGES/
resource/locale/nl/LC_MESSAGES/timekpr.mo usr/share/locale/nl/LC_MESSAGES/
resource/locale/pt/LC_MESSAGES/timekpr.mo usr/share/locale/pt/LC_MESSAGES/
resource/locale/ru/LC_MESSAGES/timekpr.mo usr/share/locale/ru/LC_MESSAGES/
timekpr-next/debian/control 000644 001750 001750 00000003152 15122253705 020023 0 ustar 00bezvfedu bezvfedu 000000 000000 Source: timekpr-next
Maintainer: Eduards Bezverhijs
Uploaders: Eduards Bezverhijs
Section: admin
Priority: optional
Build-Depends: debhelper (>= 7) | debhelper-compat (= 12),
dh-python | python-support,
gir1.2-gtk-3.0,
po-debconf,
python3 (>= 3.2),
python3-dbus,
python3-gi
Standards-Version: 3.9.5
Homepage: https://launchpad.net/timekpr-next
Rules-Requires-Root: no
Package: timekpr-next
Architecture: any
Depends: gir1.2-ayatanaappindicator3-0.1 | gir1.2-appindicator3-0.1,
gir1.2-gtk-3.0,
lxpolkit | lxqt-policykit | mate-polkit | policykit-1-gnome | polkit-kde-agent-1 | polkit-1-auth-agent,
pkexec | policykit-1,
python3 (>= 3.2),
python3-dbus,
python3-gi,
python3-psutil (>= 3.4),
systemd | systemd-shim,
${misc:Depends},
${python3:Depends}
Suggests: espeak,
gnome-shell-extension-appindicator,
python3-espeak,
python3-systemd
Conflicts: timekpr-next
Provides: timekpr
Replaces: timekpr,
timekpr-beta,
timekpr-next-beta
Description: Keep control of computer usage
Timekpr-nExT is a fresh, simple and easy-to-use time managing software
that helps optimizing time spent at computer for Your subordinates,
children or even for Yourself.
.
The software is targeted at parents or supervisors to optimize / limit
time spent at computer as they see fit.
.
Please report any bugs to Timekpr-nExT’s bug tracker on Launchpad at:
https://bugs.launchpad.net/timekpr-next
timekpr-next/spec/ 000775 001750 001750 00000000000 15122253261 016126 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/spec/timekpr-next.spec 000644 001750 001750 00000022635 15122253417 021442 0 ustar 00bezvfedu bezvfedu 000000 000000 %global debug_package %{nil}
%define _sitelib %( echo %{python3_sitelib} | cut -c 10- )
Name: timekpr-next
Version: 0.5.9
Release: 1.0%{?dist}
Summary: Keep control of computer usage
Group: System Environment/Daemons
License: GPLv3
URL: https://launchpad.net/timekpr-next
Source0: https://launchpad.net/%{name}/stable/%{version}/+download/%{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: ( python3 )
BuildRequires: ( desktop-file-utils )
BuildRequires: ( libappstream-glib or appstream-glib )
BuildRequires: ( systemd )
BuildRequires: ( sed )
BuildRequires: ( grep )
BuildRequires: ( python3-devel)
Requires: ( gtk3 >= 3.4 )
Requires: ( polkit or pkexec )
Requires: ( python3 )
Requires: ( python3-dbus or python3-dbus-python )
Requires: ( python3-gobject )
Requires: ( python3-psutil )
Requires: ( ( libindicator-gtk3 and libappindicator-gtk3 ) or ( typelib-1_0-Gtk-3_0 and ( ( libayatana-indicator3-7 and typelib-1_0-AyatanaAppIndicator3-0_1 ) or ( libindicator3-7 and typelib-1_0-AppIndicator3-0_1 ) ) ) )
Requires: ( gettext )
Suggests: ( python3-systemd )
Requires(post): ( systemd )
Requires(preun): ( systemd )
Requires(postun): ( systemd )
%description
Timekpr-nExT is a program that tracks and controls the computer usage
of your user accounts. You can limit their daily usage based on a
timed access duration and configure periods of day when they can or
cannot log in.
.
This may be used for parental control to limit the amount of screen time
a child spends in front of the computer.
.
Please report any bugs to Timekpr-nExT’s bug tracker on Launchpad at:
https://bugs.launchpad.net/timekpr-next
%prep
%setup -q -n %{name}
sed -i 's|python3/dist-packages|%{_sitelib}|g' bin/* client/*.py debian/install resource/server/systemd/timekpr.service server/timekprd.py
%build
%install
# remove all root before build
rm -rf $RPM_BUILD_ROOT
# install files
grep -v -e '^#' -e '^$' debian/install | sed -e 's|/$||' -e 's| lib/systemd/| usr/lib/systemd/|g' -e 's|^\(.\+/\)\(.*\) \(.*\)/\?$|mkdir -p %{buildroot}/\3 ; cp \1\2 %{buildroot}/\3|g' | sh -
# install pre/post files
mkdir mkdir -p %{buildroot}/%{_sharedstatedir}/timekpr
%__cp debian/postinst %{buildroot}/%{_sharedstatedir}/timekpr/%{name}.postinst
%__cp debian/postrm %{buildroot}/%{_sharedstatedir}/timekpr/%{name}.postrm
# appdata file
install -Dpm 644 resource/appstream/org.timekpr.%{name}.metainfo.xml %{buildroot}%{_datadir}/metainfo/org.timekpr.%{name}.metainfo.xml
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.timekpr.%{name}.metainfo.xml
%post
# reload units
systemctl daemon-reload
# post installation
%{_sharedstatedir}/timekpr/%{name}.postinst
# update mime / desktop
update-mime-database %{_datadir}/mime &> /dev/null || :
update-desktop-database &> /dev/null || :
%preun
# before removal
if [ $1 == 0 ];
then
%{_sharedstatedir}/timekpr/%{name}.postrm
fi
%postun
# update mime / desktop
update-mime-database %{_datadir}/mime &> /dev/null || :
update-desktop-database &> /dev/null || :
%files
# specific purpose files
%defattr(-,root,root,-)
%doc debian/changelog debian/copyright
%config(noreplace) /etc/timekpr/timekpr.conf
# package files
%{_bindir}/*
%{_datadir}/applications/*
%{_datadir}/icons/hicolor/128x128/apps/*
%{_datadir}/icons/hicolor/48x48/apps/*
%{_datadir}/icons/hicolor/64x64/apps/*
%{_datadir}/icons/hicolor/scalable/apps/*
%{_datadir}/locale/be/LC_MESSAGES/*
#%%{_datadir}/locale/cs/LC_MESSAGES/*
%{_datadir}/locale/de/LC_MESSAGES/*
%{_datadir}/locale/en_GB/LC_MESSAGES/*
%{_datadir}/locale/es/LC_MESSAGES/*
%{_datadir}/locale/fr/LC_MESSAGES/*
%{_datadir}/locale/fr_CA/LC_MESSAGES/*
#%%{_datadir}/locale/hu/LC_MESSAGES/*
%{_datadir}/locale/it/LC_MESSAGES/*
%{_datadir}/locale/lv/LC_MESSAGES/*
%{_datadir}/locale/nl/LC_MESSAGES/*
%{_datadir}/locale/pt/LC_MESSAGES/*
%{_datadir}/locale/ru/LC_MESSAGES/*
%{_datadir}/metainfo/*
%{_datadir}/polkit-1/actions/*
%{_datadir}/timekpr
%{_prefix}/lib/%{_sitelib}/timekpr
%{_prefix}/lib/systemd/system/*
%{_sharedstatedir}/timekpr
%{_sysconfdir}/dbus-1/system.d/*
%{_sysconfdir}/logrotate.d/*
%{_sysconfdir}/timekpr
%{_sysconfdir}/xdg/autostart/*
%changelog
* Mon Dec 22 2025 Eduards Bezverhijs - 0.5.9-1.0
- Updated spec file for version 0.5.9, release 1.0 (STABLE)
* Mon Nov 24 2025 Eduards Bezverhijs - 0.5.9-0.2
- Updated spec file for version 0.5.9, release 0.2 (BETA)
* Mon Nov 10 2025 Eduards Bezverhijs - 0.5.9-0.1
- Updated spec file for version 0.5.9, release 0.1 (BETA)
* Sat May 10 2025 Eduards Bezverhijs - 0.5.8-1.0
- Updated spec file for version 0.5.8, release 1.0 (STABLE)
* Tue Apr 29 2025 Eduards Bezverhijs - 0.5.8-0.3
- Updated spec file for version 0.5.8, release 0.3 (BETA)
* Mon Apr 28 2025 Eduards Bezverhijs - 0.5.8-0.2
- Updated spec file for version 0.5.8, release 0.2 (BETA)
* Mon Nov 25 2024 Eduards Bezverhijs - 0.5.8-0.1
- Updated spec file for version 0.5.8, release 0.1 (BETA)
* Thu May 30 2024 Eduards Bezverhijs - 0.5.7-1.0
- Updated spec file for version 0.5.7, release 1.0 (STABLE)
* Thu Apr 25 2024 Eduards Bezverhijs - 0.5.7-0.1
- Updated spec file for version 0.5.7, release 0.1 (BETA)
* Sun Mar 17 2024 Eduards Bezverhijs - 0.5.6-1.0
- Updated spec file for version 0.5.6, release 1.0 (STABLE)
* Fri Mar 8 2024 Eduards Bezverhijs - 0.5.6-0.6
- Updated spec file for version 0.5.6, release 0.6 (BETA)
* Thu Feb 29 2024 Eduards Bezverhijs - 0.5.6-0.5
- Updated spec file for version 0.5.6, release 0.5 (BETA)
* Tue Feb 20 2024 Eduards Bezverhijs - 0.5.6-0.4
- Updated spec file for version 0.5.6, release 0.4 (BETA)
* Sat Feb 10 2024 Eduards Bezverhijs - 0.5.6-0.3
- Updated spec file for version 0.5.6, release 0.3 (BETA)
* Thu Feb 8 2024 Eduards Bezverhijs - 0.5.6-0.2
- Updated spec file for version 0.5.6, release 0.2 (BETA)
* Wed Aug 30 2023 Eduards Bezverhijs - 0.5.6-0.1
- Updated spec file for version 0.5.6, release 0.1 (BETA)
* Wed Aug 30 2023 Eduards Bezverhijs - 0.5.5-0.4
- Updated spec file for version 0.5.5, release 0.4 (BETA)
* Sun Aug 27 2023 Eduards Bezverhijs - 0.5.5-0.3
- Updated spec file for version 0.5.5, release 0.3 (BETA)
* Thu Jun 22 2023 Eduards Bezverhijs - 0.5.5-0.2
- Updated spec file for version 0.5.5, release 0.2 (BETA)
* Sat Dec 31 2022 Eduards Bezverhijs - 0.5.5-0.1
- Updated spec file for version 0.5.5, release 0.1 (BETA)
* Wed Sep 14 2022 Eduards Bezverhijs - 0.5.4-0.4
- Updated spec file for version 0.5.4, release 0.4 (BETA)
* Thu Sep 8 2022 Eduards Bezverhijs - 0.5.4-0.3
- Updated spec file for version 0.5.4, release 0.3 (BETA)
* Mon Sep 5 2022 Eduards Bezverhijs - 0.5.4-0.2
- Updated spec file for version 0.5.4, release 0.2 (BETA)
* Wed Jun 15 2022 Eduards Bezverhijs - 0.5.4-0.1
- Updated spec file for version 0.5.4, release 0.1 (BETA)
* Wed Feb 16 2022 Eduards Bezverhijs - 0.5.3-0.2
- Updated spec file for version 0.5.3, release 0.2 (BETA)
* Tue Feb 15 2022 Eduards Bezverhijs - 0.5.3-0.1
- Updated spec file for version 0.5.3, release 0.1 (BETA)
* Tue Jun 22 2021 Eduards Bezverhijs - 0.5.2-0.5
- Updated spec file for version 0.5.2, release 0.5 (BETA)
* Wed Jun 16 2021 Eduards Bezverhijs - 0.5.2-0.4
- Updated spec file for version 0.5.2, release 0.4 (BETA)
* Wed Jun 9 2021 Eduards Bezverhijs - 0.5.2-0.3
- Updated spec file for version 0.5.2, release 0.3 (BETA)
* Mon Jun 7 2021 Eduards Bezverhijs - 0.5.2-0.2
- Updated spec file for version 0.5.2, release 0.2 (BETA)
* Fri May 7 2021 Eduards Bezverhijs - 0.5.2-0.1
- Updated spec file for version 0.5.2, release 0.1 (BETA)
* Fri Mar 5 2021 Eduards Bezverhijs - 0.5.1-0.10
- Updated spec file for version 0.5.1, release 0.10 (BETA)
* Thu Jan 7 2021 Eduards Bezverhijs - 0.5.0-8.0
- Updated spec file for version 0.5.0, release 8 (STABLE)
* Tue Dec 29 2020 Eduards Bezverhijs - 0.5.0-7.0
- Updated spec file for version 0.5.0, release 7 (STABLE)
* Thu Dec 17 2020 Eduards Bezverhijs - 0.5.0-4.0
- Updated spec file for version 0.5.0, release 4 (BETA)
* Tue Dec 1 2020 Eduards Bezverhijs - 0.5.0-3.0
- Updated spec file for version 0.5.0, release 3 (BETA)
* Wed Nov 18 2020 Eduards Bezverhijs - 0.5.0-2.0
- Updated spec file for version 0.5.0, release 2 (BETA)
* Sun Nov 1 2020 Eduards Bezverhijs - 0.5.0-1.0
- Updated spec file for version 0.5.0 (BETA)
* Sat Oct 31 2020 Eduards Bezverhijs - 0.4.4-1.0
- Updated spec file for version 0.4.4 (STABLE)
* Tue Sep 8 2020 Eduards Bezverhijs - 0.4.3-1.0
- Updated spec file for version 0.4.3 (STABLE)
* Tue Aug 18 2020 Eduards Bezverhijs - 0.4.2-1.0
- Updated spec file for version 0.4.2 (STABLE)
* Wed Jul 15 2020 Eduards Bezverhijs - 0.4.1-1.0
- Updated spec file for version 0.4.1 (STABLE)
* Fri Jul 10 2020 Eduards Bezverhijs - 0.4.0-1.0
- Initial version of the spec file (STABLE)
timekpr-next/spec/README 000644 001750 001750 00000006173 15122253261 017013 0 ustar 00bezvfedu bezvfedu 000000 000000 INSTALLATION INSTRUCTIONS
-------------------------
Fedora / openSUSE:
Use whichever GUI your distribution has or:
sudo rpm -Uhv
VERSIONING
----------
Release less than 1.0 is BETA, 1.0+ is STABLE, example:
BETA (release 0.6): timekpr-next-0.5.1-0.6.fc32.x86_64
STABLE (release 7.0): timekpr-next-0.5.0-7.0.fc32.x86_64
NOTES
-----
If You are using Gnome3 with Gnome Shell, please DO NOT forget to install this (otherwise icon will be hidden)!
Fedora:
sudo dnf install gnome-shell-extension-appindicator
openSUSE:
install "KStatusNotifierItem/AppIndicator Support" extension manually from https://extensions.gnome.org/
Enable extensions if needed (for Fedora install Gnome Tweaks application and enable said extension)
Let the user log out and in to see the timekpr-nExT in action.
-------------------------
REMOVAL instructions
--------------------
Fedora:
sudo dnf remove timekpr-next
openSUSE:
sudo zypper remove timekpr-next
-------------------------
BUILD instructions (please open terminal and install this in one go not leaving the terminal (unless You know what to do))!
---------------------------------------------------------------------------------------------------------------------------
# set version
Fedora / openSUSE:
export TNVER="0.5.9"
export TNREL="0.2"
# install build tools
Fedora:
sudo dnf install rpmdevtools
openSUSE:
sudo zypper install rpmdevtools rpm-build
# this sets up build tree (rpmbuild dir in home directory)
Fedora / openSUSE:
rpmdev-setuptree
# get the timekpr sources
Fedora / openSUSE:
wget https://launchpad.net/timekpr-next/stable/${TNVER}/+download/timekpr-next-${TNVER}.tar.gz -P $HOME/rpmbuild/SOURCES/
# got to source directory and get the spec file
Fedora / openSUSE:
cd $HOME/rpmbuild/SPECS/
tar xzf $HOME/rpmbuild/SOURCES/timekpr-next-${TNVER}.tar.gz --strip-components=2 timekpr-next/spec/timekpr-next.spec
# install build dependencies
Fedora:
sudo dnf builddep timekpr-next.spec
sudo dnf install libindicator-gtk3 libappindicator-gtk3 python3-psutil gettext
openSUSE:
sudo zypper install python3 desktop-file-utils appstream-glib systemd sed grep gtk3 python3-dbus-python python3-gobject python3-psutil libayatana-indicator3-7 gettext typelib-1_0-Gtk-3_0 typelib-1_0-AyatanaAppIndicator3-0_1
# build binary package
Fedora / openSUSE:
rpmbuild -bb timekpr-next.spec
# install needs to be adjusted to correct version of distro (example given for FC32)
# if installation complains about conflicts with existing package, please remove the old version of package
# and then install new one or use --force at the end of the command
Fedora / openSUSE:
sudo rpm -Uhv --force $HOME/rpmbuild/RPMS/$(arch)/timekpr-next-${TNVER}-${TNREL}*.x86_64.rpm
---------------------------------------------------------------------------------------------------------------------------
timekpr-next/spec/debian.install.2.spec.install.sh 000775 001750 001750 00000000734 13716566163 024135 0 ustar 00bezvfedu bezvfedu 000000 000000 #!/bin/bash
# convert debian install file to RPM install file list
grep -v -e '^#' -e '^$' ../debian/install | sort | sed \
-e 's|/\+$||' \
-e 's|^\(.\+/\)\(.\+\) \(.\+\)$|/\3|g' \
-e 's|\(.\+\)\(/timekpr\)/.\+$|\1\2|g' \
-e '/\/timekpr$/! s|$|/*|' \
-e 's|/usr/share/|%{_datadir}/|' \
-e 's|/usr/bin/|%{_bindir}/|' \
-e 's|/usr/|%{_prefix}/|' \
-e 's|/etc/|%{_sysconfdir}/|' \
-e 's|/var/lib/|%{_sharedstatedir}/|' \
-e 's|/lib/systemd/|%{_prefix}/lib/systemd/|' \
| sort -u
timekpr-next/README.md 000644 001750 001750 00000132321 15122253261 016453 0 ustar 00bezvfedu bezvfedu 000000 000000 # Timekpr-nExT
## Keep control of computer usage
Timekpr-nExT is a fresh, simple and easy-to-use screen-time management
application. It helps you optimize the time spent on the computer by your
children, subordinates, or even yourself.
The application is intended for parents or supervisors who want to limit or
shape computer usage in a way that suits their household or environment.
## Overview
Timekpr-nExT is designed to give you reliable control over computer usage. This
includes the ability to forcibly end user sessions and apply other restrictions
or limitations.
Please be responsible and inform your users that their activity is being timed
and that their session may end abruptly. Notifications can be configured to warn
them ahead of time, but ultimately it is up to the user to pay attention to
these warnings.
When setting up restrictions, supervisors should read the descriptions of all
options and take some time to explore the application before enforcing any
settings. Different combinations of options can create a very tailored
experience, but remember that Timekpr-nExT is a restrictive tool by design.
_**Note**: This README is updated along with the `beta` releases. If you do not
see a specific option in the `stable` series, please wait for its release or use
the `beta` version._
## Navigation guide
**This README** contains all the details you need, but it is quite large.
Below is a simple guide to help you find what you are looking for.
* About functionality:
- A short description of the [applications](#applications) with a
[typical use case](#typicalusecase).
*(This is likely the section most parents or supervisors will want to read.)*
- For a deeper understanding, see the
[detailed description of functionality](#detaileddescription):
- Information about the user's side of the software:
[client application](#clientapplication)
- Information about the supervisor's tools:
[administration application](#administrationapplication)
- Recently added important features:
- Alternative restriction types:
[suspend / lock / shutdown](#restrictionlockouttypes)
- The ability to limit specific applications or games:
[PlayTime functionality](#playtimeconfiguration)
- User-controlled notifications:
[user configurable notifications](#userconfigurablenotifications)
- Time periods where usage does not count toward limits:
["freeride" time periods](#freerideintervals)
- Logging to system journal:
[systemd journal](#logtojournald)
- CLI (console) options and file-based configuration:
[additional configuration possibilities](#additionalconfigpossibilities)
* **Installation guide**:
- Installation and removal
[instructions](#installation) for most popular Linux systems
* **Support the project**:
- You can support development by **[donating](#support)**
* Translations:
- Help translate Timekpr-nExT into your
[language](#translate)
* Disclaimer, questions, suggestions, and reporting bugs:
- The disclaimer is located
[here](#disclaimer), and information about questions, suggestions,
and bug reports is found [here](#bugs)
## Applications overview
Timekpr-nExT provides two user-facing applications that control how the system
manages computer time and restrictions.
#### Timekpr-nExT Client indicator icon
This small application places an indicator icon in the system notification area.
It shows remaining time, limits, and notifications to the user.

_An informational "padlock" icon appears in the system’s notification area. This
is what users automatically see when they log in._
#### Timekpr-nExT Client application
The client application opens when the user clicks the indicator icon and selects
**"Limits & configuration"**. It provides more detailed information as well as
useful configuration options.

_This application displays detailed time limits and allows users to adjust some
aspects of the functionality. It is intended for the end user._
#### Timekpr-nExT Administration application
This application is designed for supervisors or parents. It lets you configure
time limits for any user on the system and adjust technical settings for how
Timekpr-nExT behaves.

_The administration tool provides multiple configuration tabs so supervisors can
customize restrictions and user experience as needed._
#### Core of Timekpr-nExT
Behind the scenes, Timekpr-nExT includes a background service (often called a
"daemon"—not a demon!) that monitors the system and enforces all restrictions.
There is no screenshot for it, but rest assured: it is always running quietly in
the background, doing its job for you. ;)
## Description of functionality
This section explains how Timekpr-nExT works behind the scenes, rather than
listing every configuration option in detail.
**Please note:** Timekpr-nExT relies heavily on tool-tips for explaining
individual options. Hover your mouse over any setting to see its description.
These hints are very helpful for understanding what each option does.
### Overall features
Timekpr-nExT aims to be as precise and user-friendly as possible, while still
providing reliable restrictions.
A key part of this is predictable and accurate time tracking. By default,
Timekpr-nExT checks and updates the user’s time usage every 3 seconds. This
ensures precise accounting and provides clear information about remaining time.
If users want more or fewer notifications, they can adjust this themselves.
Time is not counted when it should not be. For example, Timekpr-nExT avoids
counting time when the session is inactive. A session is considered inactive
when:
- another user is using the computer via user switching, or
- the current user’s session is locked and/or the screensaver is active.
Time is also not counted when the computer is sleeping or shut down.
The rest depends on how the supervisor configures the system. Keep reading for a
complete overview of everything Timekpr-nExT can do.
### Administration application
The administration application is where supervisors or parents configure time
limits, restrictions, and various technical settings for Timekpr-nExT.
To open the administration tool, you must either run it as a superuser or add
yourself to the `timekpr` system group for password-less access. Most people use
superuser mode because it works immediately without additional setup.
Running it as superuser is simple: open your application menu, dock, or launcher
and choose **"(SU) Timekpr-nExT Control Panel (superuser mode)"**.
If you have added yourself to the `timekpr` system group, you can instead launch
**"Timekpr-nExT Control Panel"** without entering a password.
_**Be aware**: this will NOT work unless you are actually in the `timekpr` group, which
may require you to log out and back in to work or a simple restart will take care
of that._
_**Note**: The "(SU)" prefix exists because older Gnome3 versions would truncate long
application names, making the two modes look identical._
To add yourself to the `timekpr` group, you can use your desktop environment’s
user management tool, or run `sudo gpasswd -a $USER timekpr`
Replace `$USER` with the correct administrator account if needed. Do not add
your child’s or subordinate’s username. After adding yourself, you must log out
and back in for the change to take effect.
_**Please note**: Some configuration options are unavailable in password-less
mode. These advanced options are rarely needed and should only be used in
special situations. Running in superuser mode provides full access to all
settings._
### User configuration
To configure each user’s limits and restrictions, you must first select them from
the user list. The list is taken directly from your operating system, and the
initial configuration is created automatically. After that, the list is stored
in the configuration directory, and **the configuration remains even if the OS
user is deleted**.
This means you can re-create a system user without losing their Timekpr-nExT
settings.
_**Please note:** When you open the administration application, **no user is
pre-selected**. This is intentional to ensure the supervisor chooses the correct
user and avoids accidental misconfiguration._
#### Tab "Info & today"
As the name suggests, this tab shows how much time the user has already spent
today and how much time they still have available.
Some information is shown only when the user is currently logged in. Live data
is more accurate and lets you monitor activity in real time. When the user is
not logged in, Timekpr-nExT shows the saved state instead. This saved
information is always available but is refreshed less frequently.
Time adjustment controls allow supervisors to reward a user with extra time or,
if needed, reduce their available time for the day.
Remember that adding time does **not** override allowed time periods or the
maximum daily allowance. These rules remain in place regardless of adjustments.
_**Pro tip**: Move your mouse over any item to read its tool-tip for additional
details._
A special value called **"Continuous time available"** shows how much time the
user can stay logged in **without interruption**, based on their current daily
allowance and allowed time periods. However, it never counts more than “today +
tomorrow� combined.
Continuous time can stretch past midnight if you configure the allowed periods
correctly. For example, if today’s time period ends at 24:00 and tomorrow’s
period starts at 00:00, usage can continue smoothly across midnight.
#### Tab "Limit configuration"
This tab lets you configure a user’s time restrictions. You can mix and match
options to create the daily routine you want.
Before diving into the full explanation, here are the two most common setup
patterns:
* Allow the full day (24:00:00), but restrict access to specific intervals, e.g.
09:00–12:00, 13:00–16:00, and 19:00–21:00.
* Give a fixed amount of time per day (e.g. 08:00:00), and define the time range
during which they can use those hours, e.g. 09:00–21:00.
_**Please note:** Time values follow the ISO 8601 standard: Monday is the first
day of the week and the system uses 24-hour time._
---
##### Week day limits
This section configures each day of the week. You can enable or disable computer
usage for any day and set different time limits for each one.
Days must be enabled or disabled individually, but time limits can be edited for
multiple days at once. Select several days (use Ctrl+A for all), choose whether
you’re editing hours or minutes, and press the “+� or “–� buttons.
You may also edit a single day’s limit by clicking the value in the table and
typing it manually. This allows entering seconds too (e.g. 7 → 07:00:00,
7:1:15 → 07:01:15).
By default, every day is enabled and users have unlimited time available.
---
##### Hour intervals
Hour intervals define when the user is allowed to use the computer. These can be
configured per day, or for several days at once by selecting multiple entries.
To create an interval, click the **Create** button and then define the start and
end times by clicking the interval in the table. You can modify the interval
later if needed.
Shortcuts also work here: entering `7` becomes 07:00, and `7:15` becomes 07:15.
A special type of interval marked with **“∞�** (unaccounted time interval) means
that any time used in that period **does not count** toward the daily allowance.
This effectively becomes **free time**, even if the user has no regular time
left.
This is especially useful for scheduled activities like online classes or
homework sessions, where usage should not reduce personal computer time.
Intervals must **not** overlap in any way. However, intervals may touch
continuously (e.g. 07:00–13:00 and 13:00–14:30). Adjacent intervals might merge
automatically.
Keep in mind: if there is even a one-minute gap between intervals, the user will
face their configured restriction during the gap—for example, session
termination.
A design choice in Timekpr-nExT also ensures that a single hour cannot contain
more than one interval. A period may start or end inside an hour, but two
separate intervals cannot exist within the same hour (e.g. 07:15–09:15 and
09:45–14:30 are **not** allowed, but 07:15–09:00 and 09:45–14:30 **are**).
After setting intervals, click **Verify** so the application can check for
mistakes. If all is correct, you can apply the configuration. If not, conflicting
intervals will be highlighted.
By default, the entire day is available.
---
##### Weekly and monthly limits
This section lets you adjust weekly and monthly time allowances. Select a period,
choose a time unit (day, hour, or minute), and press the “+� or “–� buttons to
change the limits.
You may also edit a limit directly by clicking the value and typing it manually.
This allows editing seconds too (though it’s rarely necessary). Shortcuts work
as well: entering `7` creates `07:00:00:00` (days:hours:minutes:seconds), and
entering `6:10:5:11` results in `06:10:05:11`.
These limits work together with daily limits and hour intervals. The time the
user actually gets is **the smallest** of all limits combined. This means that
even if the daily limit has not been reached, the user may be restricted if they
have already used up their weekly or monthly allowance.
By default, the whole week and month are unrestricted. For most families, there’s
no need to adjust these values unless weekly or monthly control is desired.
---
#### PlayTime configuration
PlayTime is a screen time limiting feature for specific applications or
processes. In PlayTime terminology, these are called **activities**.
PlayTime expands the usual time tracking by adding control over how long certain
applications can run. Effectively, it works like a focused process monitor.
Users may run configured activities for the allowed amount of PlayTime. Once the
allowance is used up, those applications are terminated. Running them again ends
them immediately.
Keep in mind that PlayTime is generally treated as a **special time limit inside
the regular time limit**, so all normal rules apply—except when **override
mode** is enabled (explained later).
PlayTime was originally designed for games, but supervisors may monitor any
software, such as web browsers, chat apps, or even calculators.
_**Please note:** PlayTime time is still counted even during “∞� (unaccounted)
intervals—**unless override mode is enabled**._
---
##### PlayTime options
This section contains PlayTime controls, including the main on/off switch and
the PlayTime override mode.
PlayTime must be enabled **per user**. If it is not enabled for a user, it will
not work for them. You must also enable the
[PlayTime master switch](#playtimemasterswitch); without it, PlayTime will not
function system-wide.
By default, PlayTime is disabled.
Below is an overview of PlayTime behavior and modes.
The **standard mode** is restrictive. Certain activities can be used for a set
amount of time. Once the limit is reached, the application is closed. For
example, if Steam is configured and the PlayTime limit is one hour, Steam will
close after one hour and cannot be used again until the next day.
The **override mode** changes the concept entirely. In override mode, the usual
PlayTime limits are ignored. Time spent at the computer is counted **only when
PlayTime activities are running**. In other words, the rest of the time becomes
“free time.�
Using the Steam example:
The user can freely use the computer until they start Steam. Then the PlayTime
clock starts. If they close Steam before the time is fully used, the remaining
time is preserved, and general usage becomes unrestricted according to normal
rules.
The option **“Allowed during ‘∞’ intervals�** controls whether PlayTime
activities can run during unaccounted ("∞") intervals.
- If the option is **disabled**, activities cannot run during unaccounted time
intervals at all, even in override mode. Using Steam as an example, the user
will be unable to launch it during “∞� time.
- If the option is **enabled**, activities may run during unaccounted intervals.
In that case, PlayTime still counts usage normally.
- If override mode is enabled during an unaccounted interval, **everything is
free**: the user can use the activity with no limits, and no time will be
counted toward any allowance.
This option is useful when unaccounted intervals are used for activities like
online school. Supervisors may prevent certain apps from running during these
periods by disabling “Allowed during ‘∞’ intervals.�
Override mode is not immediately intuitive, but once understood, it can be very
useful for precise time accounting.
By default, the option is enabled.
---
##### PlayTime limits
PlayTime limits work similarly to standard time allowances. The interface is the
same, but these limits apply **only to PlayTime activities**.
If a day is disabled for PlayTime, the user cannot run any monitored activities;
they will close instantly.
_**Please note:** PlayTime limits do not apply when override mode is enabled._
---
##### PlayTime activities
This is the most important PlayTime section. Here you configure the activities
(processes) that should be monitored. This list is not a simple allowlist or
denylist—each activity requires a **process mask**.
Determining the correct mask may require using a system monitor tool. Any KDE or
GNOME “System Monitor� works well. You can also use a console or SSH.
Linux applications can be installed almost anywhere and often by the user
themselves—Steam, Lutris, Heroic, and many others install games in user-owned
directories. Applications also do not identify themselves as “games� or
“restricted programs,� so automatic detection is essentially impossible.
That is why the supervisor must choose which programs to monitor.
So here is a general guide on how to determine which processes or masks can be
used when configuring PlayTime activities.
If you are not familiar with the terminal, this may look intimidating at first.
However, you can use graphical system monitoring tools instead. KDE and GNOME
both provide “System Monitor� applications that work well for this purpose.
Look for columns such as **Process Name**, **Command**, or **Command Line**—these
are your best helpers. You can always search online or ask your community how to
identify an executable name.
The process mask for a PlayTime activity is usually just the executable name, or
the full command line if the
["Enhanced activity monitor"](#playtimeenhancedactivitymonitor) is enabled
(case-sensitive!). A simple terminal command such as `top -c -d 1` is
often enough to find the process you need.
Games typically use the most resources while running, so they appear near the
top of the list.
Look at the COMMAND column. If something looks like the activity you want to
limit, take the executable name only—without its full path or any arguments.
Example: If you see `/opt/discord-canary/DiscordCanary --type=renderer ...`
then the correct mask is just `DiscordCanary`. This is the part after the last
slash and before the arguments.
Some games behave poorly when switching windows (Alt-Tab). In that case, you can
connect via SSH from another device and check the process list there.
You may also enter a description for each activity. This description is shown to
the user instead of the mask. If no description is provided, the mask itself is
shown.
_**Please note**: Process masks can use RegExp (regular expressions), not
glob patterns. This is an expert feature and must be used carefully._
Always verify your RegExp. Incorrect expressions may terminate the wrong
processes or fail to match anything at all.
If a RegExp is invalid, Timekpr-nExT will treat it as a literal string.
For example, `*wine*` is not really a valid and useful RegExp, `.*wine.*` is
correct. If written incorrectly, the system will literally search for a process
named `*wine*`, which almost certainly does not exist.
RegExp support does not allow the symbols [], and you should avoid using
^ and $. Internally, Timekpr-nExT automatically anchors patterns, so a mask
like `.*wine.*` becomes similar to `^.*wine.*$` (or `/.*wine.*$` when matching
full command lines in enhanced mode).
PlayTime also uses smart caching and retrieves process lists efficiently. Only
processes belonging to the monitored user are checked, counted, or terminated.
PlayTime logic is active only when at least one user has PlayTime enabled and at
least one activity is configured.
---
#### Tab "Additional options"
This section provides extra per-user configuration options.
- **Track inactive sessions**: When enabled, every user session is accounted for,
even if it is locked or another user is using the computer. Use with caution.
- **Hide icon and notifications**: Hides the Timekpr-nExT client icon and almost
all notifications. Only critical notifications are shown. If enabled for an
unrestricted user, they may not even notice Timekpr-nExT is present.
- **Restriction & lockout type**: Determines what action is taken when a user’s
time runs out. Be careful when changing from the default.
---
##### Restriction / lockout types
**Terminate sessions**
Default and restrictive. User session is forcibly logged out immediately.
**Kill sessions**
Similar to terminate sessions. Attempts a soft kill, but effect is largely the
same.
**Shutdown computer**
Restrictive option. Shuts down the computer when user time expires. Use with
caution in multi-user environments.
**Suspend computer**
Lockout option. Suspends the computer without terminating sessions. More suited
for self-control rather than restricting usage. If the computer is woken with no
time left, it remains suspended until unlocked.
**Suspend / wakeup computer**
Lockout option. Similar to suspend, but computer wakes up automatically at the
next available time period for the day. Requires specifying allowed wakeup hour
interval. Dependent on BIOS/UEFI RTC wakeup support.
**Lock screen**
Lockout option. Locks the screen when time expires. If unlocked without available
time, it locks again shortly. Suited for self-control rather than full restriction.
---
### Timekpr-nExT configuration
This tab allows supervisors to configure technical behaviors. Usually, default
values are optimal.
#### Control options
**Final notification**
Forces one last notification when user time is almost up, even if the user has
disabled notifications.
**Termination time**
Specifies seconds left before applying the selected restriction/lockout. Allows
users to request more time before enforcement.
**Countdown time**
Specifies seconds left before starting a continuous countdown notification for
the final seconds.
**Poll interval**
Defines the frequency (in seconds) at which Timekpr-nExT updates user sessions
and time values. This effectively sets the resolution of time accounting.
**Save time**
Specifies how often (in seconds) Timekpr-nExT saves calculated time values to
disk. Minimizes potential loss during crashes.
**Log level**
Controls verbosity of logs:
- Level 1: minimal (saves space, minimal info)
- Level 2: standard (sufficient info for troubleshooting)
- Level 3: verbose, memory-intensive, mostly useful for developers
Log files are handled by `logrotate` and compressed, so space is not usually an
issue.
---
#### Tracked sessions
Timekpr-nExT tracks the following session types by default:
- `x11` (Xorg)
- `wayland`
- `mir` (less common)
Do not modify these unless you understand Linux session types.
#### Excluded sessions
By default, the following session types are **not tracked**:
- `tty` (console sessions via Ctrl+Alt+F[1-7])
- `unspecified` (other unknown types)
Modify only if necessary and with understanding of the system.
#### Excluded users
Allows supervisors to exclude **system users** from time accounting, such as
login managers. Do **not** enter regular users here, as it may cause errors.
#### Additional options (PlayTime)
Several options in this section are related to **PlayTime** functionality.
---
##### PlayTime enabled
Controls the **master switch for PlayTime**.
- Must be enabled for PlayTime to work globally.
- If disabled, no user activities will be tracked, regardless of individual PlayTime settings.
---
##### Enhanced activity monitor
Controls whether PlayTime checks the **first 512 characters of the full process command line**, including arguments, against registered activity/process masks.
- Without this option, only the executable path and name are checked.
- Enables advanced RegExp matching for complex scenarios, e.g., interpreted languages like Python or Java.
- Example: For Minecraft (a Java application), a process mask could be `.*java.*minecraft.*`.
- **Note:** Changes only apply to newly started processes.
---
### Client application
The Timekpr-nExT client shows time metrics and limited configuration options for the user.
- Users can view tooltips for detailed explanations of every value.
- Appearance may vary depending on the desktop environment ([differences](#desktopenvironmentdifferences)).
---
#### Daily limits
Displays:
- Daily time allowance
- Time period restrictions
- Time spent / time left
Key points:
- Icon color reflects remaining time (configurable by the user).
- Periods marked with "∞" do not count towards the daily limit.
- Unaccounted time intervals turn the icon gray while active.
---
#### Additional limits
Displays weekly and monthly time limits and usage so far.
---
#### PlayTime limits
Displays:
- Time allowance and activity list for PlayTime
- Type of PlayTime mode enabled
- Active activity count and descriptions
**Note:** Tab is visible only if supervisor enabled PlayTime for the user.
---
#### Notifications
Users can adjust notifications for time left:
- Set thresholds for standard and PlayTime notifications
- Assign priority levels
**Note:** PlayTime notifications appear only if enabled by supervisor. Tooltips provide detailed guidance.
---
#### Configuration
Allows users to tailor Timekpr-nExT behavior:
- Show seconds in icon
- Enable or disable limit change notifications
- Configure sound notifications (requires `python3-espeak` or similar)
- Adjust notification types and log level
**Note:** There may be a [sound notification bug](#quirkssound).
---
## Typical use case
Imagine Liane wants to limit computer time for Cartman because he spends too much time online talking to Kyle and Stan _(Kenny doesn’t count—he doesn’t have a computer)_.
She also wants to reserve 2 hours on Monday (15:00–17:00) for mandatory anger management classes.
Liane wants a flexible schedule within strict time windows for weekdays and holidays. Using Timekpr-nExT, she can configure it as follows:
---
**Configuration steps in Timekpr-nExT Administration:**
1. Select username **"cartman"** and open the **Limit configuration** tab.
2. **Monday:**
- Set daily time limit: **6 hours**
- Add interval **7:30–15:00**
- Add interval **15:00–17:00**, check the "**∞**" box (time not counted against limit)
- Add interval **17:00–21:00**
3. **Tuesday–Friday:**
- Set daily time limit: **6 hours**
- Add interval **7:30–21:00**
4. **Holidays (Saturday & Sunday):**
- Set daily time limit: **8 hours**
- Add interval **9:00–22:30**
5. Click **Apply daily limits** to save restrictions.
---
**Resulting behavior:**
- Cartman has **6 hours of computer time** Monday–Friday (7:30–21:00).
- **15:00–17:00** is free for mandatory classes (doesn’t consume daily limit).
- Cartman has **8 hours of computer time** during holidays (9:00–22:30).
- Cartman **cannot use the computer** outside the defined intervals or exceed his daily allowance.
---
> Typical setups are straightforward, but Timekpr-nExT supports more advanced configurations.
> For further details, see [Detailed information](#detaileddescription).
---
## Installation / removal
To start using Timekpr-nExT, you need to install it. There are two versions: **beta** (early access) and **stable** (production-ready).
> Tip: Logging out and back in after installation is highly recommended.
**Availability:**
- Ubuntu and derivatives: via **PPA**
- ArchLinux / Manjaro: via **AUR**
- Debian: native packages (may be outdated)
- Fedora: via **johanh's COPR repository**
- openSUSE: native packages (from 2025)
- Manual installation: Fedora 32+ and openSUSE Leap 15.2+
- NixOS: via **nixos-unstable** (and future nixos-25.11)
Installation via terminal is generally faster than GUI, but both methods are supported.
**NOTE**: I am not creating packages for manual installation anymore!
The ones which were created are for historical purposes or older installations only!!!
#### Stable
| Distribution | Stable install | Stable remove |
| :--- | :--- | :--- |
| Ubuntu & co (via PPA) | ```sudo add-apt-repository ppa:mjasnik/ppa``````sudo apt-get update``````sudo apt-get install timekpr-next``` | ```sudo apt-get remove --purge timekpr-next``` |
| ArchLinux & Manjaro (via AUR) | ```yay -S timekpr-next``` | ```sudo pacman -Rdd timekpr-next``` |
| Fedora | [copr repo](https://copr.fedorainfracloud.org/coprs/johanh/timekpr-next/) _(preferred)_**or**[manual installation](https://drive.google.com/drive/folders/1iN1wcPctGhd_OISqzWZ5DigFMVvgSGq9) _(README and packages)_| [copr repo](https://copr.fedorainfracloud.org/coprs/johanh/timekpr-next/) _(preferred)_**or**[manual uninstallation](https://drive.google.com/drive/folders/1iN1wcPctGhd_OISqzWZ5DigFMVvgSGq9) _(README)_ |
| openSUSE | [manual installation](https://drive.google.com/drive/folders/1iN1wcPctGhd_OISqzWZ5DigFMVvgSGq9)_(README and packages)_| [manual uninstallation](https://drive.google.com/drive/folders/1iN1wcPctGhd_OISqzWZ5DigFMVvgSGq9)_(README)_ |
| NixOS | set [services.timekpr.enable](https://search.nixos.org/options?channel=unstable&show=services.timekpr.enable) to `true` | set [services.timekpr.enable](https://search.nixos.org/options?channel=unstable&show=services.timekpr.enable) to `false` |
---
#### Beta
| Distribution | Beta install | Beta remove |
| :-- | :--: | --: |
| Ubuntu & co (via PPA) | ```sudo add-apt-repository ppa:mjasnik/ppa``````sudo apt-get update``````sudo apt-get install timekpr-next-beta``` | ```sudo apt-get remove --purge timekpr-next-beta``` |
| ArchLinux & Manjaro (via AUR) | ```yay -S timekpr-next-git``` | ```sudo pacman -Rdd timekpr-next-git``` |
| Fedora and openSUSE | [manual installation](https://drive.google.com/drive/folders/1iN1wcPctGhd_OISqzWZ5DigFMVvgSGq9)_(README and packages)_ | [manual uninstallation](https://drive.google.com/drive/folders/1iN1wcPctGhd_OISqzWZ5DigFMVvgSGq9)_(README)_ |
> **Note:** For ArchLinux and Manjaro, choose your preferred AUR helper if different from the examples above.
> Special thanks to **SanskritFritz** (ArchLinux community) for beta testing and maintaining packages.
> Thanks to **johanh** (Fedora community) for providing the COPR repo.
---
#### Debian
Timekpr-nExT is now available as a native Debian package thanks to **Sim (smntov)** and **Anthony Fok (foka)**.
Preferred method is same as Ubuntu:
| Stable install | Stable remove | Beta |
| :--- | :--- | :--- |
| ```sudo apt-get update``````sudo apt-get install timekpr-next``` | ```sudo apt-get remove --purge timekpr-next``` | Not available |
> **Note:** Packages built for Debian "sid" (unstable) may work on other versions like "buster" if dependencies are satisfied.
> Graphical installers like **KDE Discover** or **GNOME Software** can also be used.
> Always use a package created specifically for Debian for best compatibility.
## Compatibility
Timekpr-nExT **was** developed to be compatible with most Desktop Environments. During development, around 20 VMs **were** used to test major
changes, but not all possible setups are tested in the wild. Currently, testing is mostly on demand, based on bug reports or
developer discretion. In other words, nothing gets proactively tested until there is a reason.
Tested Desktop Environments include: **KDE, GNOME, Cinnamon, MATE, XFCE**
Tested distributions include: **Ubuntu, Manjaro/ArchLinux, Debian, Fedora, openSUSE**
> Please check the [nuances](#quirks) section for more detailed information.
> If you encounter an issue, see [this](#bugs) and file a bug report.
---
## Information Representation Differences
Desktop Environments handle visual representation differently. Below are some key differences:
### Icon Differences
- The system tray icon can appear differently across environments:
- **GNOME3 / Unity / XFCE**: show the icon with a detailed label beside it.
- **KDE / Deepin**: show only the icon; hovering reveals the time left.
### Extensions May Be Needed
- Some GNOME3-based distributions require an extension to display the icon.
- Recommended extension: [AppIndicator Support](https://extensions.gnome.org/extension/615/appindicator-support/)
### Notification Timeouts / Severity
- Certain desktop environments may override Timekpr-nExT’s notification timeout and severity settings.
- KDE Plasma respects custom settings the best. Other environments may enforce preconfigured values.
### Future of the Icon
- GNOME3 may eventually remove the status icon functionality. If this happens, a workaround will be investigated.
---
## Short and Very Technical Overview
> **Warning:** This section is highly technical and not necessary for regular users.
Timekpr-nExT relies heavily on **DBUS** for system interaction:
- Uses DBUS interfaces provided by the system.
- Exposes its own DBUS interfaces.
- Can integrate with any client application or control panel that supports DBUS.
This architecture allows Timekpr-nExT to be highly extensible for technical users or administrators seeking deeper integration.
### Interfaces Used
Timekpr-nExT relies on several system interfaces for its functionality:
1. **systemd login1 interface**
- Used for session management.
- Mandatory for Timekpr-nExT; without it, the software will not function.
- Documentation: [systemd-logind](https://www.freedesktop.org/wiki/Software/systemd/logind/)
2. **FreeDesktop screensaver interfaces**
- Used to detect idle time and inhibit screensavers.
- Documentation: [Idle Inhibition Spec](https://people.freedesktop.org/~hadess/idle-inhibition-spec/ch05.html)
3. **FreeDesktop notifications interface**
- Used to display notifications about time left.
- Documentation: [Notification Spec](https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
4. **GNOME screensaver interface (fallback)**
- Used if FreeDesktop screensaver interfaces are unavailable.
- Documentation: [GNOME Screensaver](https://people.gnome.org/~mccann/gnome-screensaver/docs/gnome-screensaver.html)
> Timekpr-nExT connects to these DBUS objects once, caches them, and mainly calls them during execution for efficiency.
---
### Additional Configuration Possibilities
Timekpr-nExT can be configured through:
1. **Graphical interface (GUI)**
2. **Command-line interface (CLI)**
3. **Direct configuration file editing**
---
#### CLI (Command Line Interface) Mode
- The **Administration application** can be run in a terminal to manage users and configure limits, with functionality equivalent to the GUI.
- CLI usage is restricted to **user-level configuration only**.
- To explore CLI options, run `sudo timekpra --help`
**Security Note for CLI Usage**
CLI usage of Timekpr-nExT follows the same security rules as the graphical interface:
- You must be in the `timekpr` group,
- or execute the CLI as `root`,
- or use `sudo` to gain access.
Changes made in CLI or GUI are applied **immediately**, in real-time.
> **Note**: before version 0.5.9 CLI returned and accepted time in seconds only, since version 0.5.9 it accepts and can return (optionally) time as time string.
---
### Configuration by Files
Timekpr-nExT can also read configuration from files directly, which are applied at **save intervals** (default every 30 seconds).
**Important Notes:**
- Direct file editing is **not recommended**; prefer GUI or CLI.
- Configuration files have a **specific internal structure**. Improper edits can break functionality.
- If files are corrupted or unrecognizable, Timekpr-nExT will attempt to **salvage valid options** and **recreate defaults** for damaged entries.
### Configuration Files (Handle with Care)
| Purpose | File Location |
| --: | :-- |
| Main configuration file | `/etc/timekpr/timekpr.conf` |
| User configuration files (per user) | `/var/lib/timekpr/config/timekpr.*.conf` |
| User control files (per user) | `/var/lib/timekpr/work/timekpr.*.conf` |
| Client configuration file | `$HOME/.config/timekpr/timekpr.conf` |
---
### Log Files
Timekpr-nExT writes logs to track execution and performance. These files are generally **not meant for regular inspection** by users.
They mainly serve as a record in case something goes wrong and can be helpful for debugging.
- Log files contain mostly **technical details**; nothing sensitive except usernames (which can be obfuscated if necessary).
- From version `0.5.1`, log level changes take effect immediately—no restart required.
- Log size can be reduced by lowering log level in the administration app, though level 2 (default) is preferred for troubleshooting.
**Log File Locations**
| Logging Area | File Location |
| -- | :-- |
| Daemon log | `/var/log/timekpr.log` |
| Administration application logs | `/tmp/timekpra.*.log` |
| Client application logs | `/tmp/timekprc.*.log` |
---
**Note:** Since version 0.5.9, log entries may also be written to standard OS logging facilities if the right conditions are met.
Specifically, if the `python3-systemd` package is installed, logs are sent to `journald`; otherwise, they remain in the usual log file locations.
**Accessing Logs via System Journal**
To gather logs from `journalctl`, use the following commands:
| Logging Area | Description | Command (current boot) | Command (all time) |
| -- | -- | -- | :-- |
| Daemon log | main log file, needed for bug reports | `sudo journalctl -b -t timekprd > timekpr.log` | `sudo journalctl -t timekprd > timekpr.log` |
| Administration application logs | for issues involving admin app | `sudo journalctl -b -t timekpra > timekpra.log` | `sudo journalctl -t timekpra > timekpra.log` |
| Client application logs | for issues with user client app | `sudo journalctl -b -t timekprc > timekprc.log` | `sudo journalctl -t timekprc > timekprc.log` |
After gathering the log files, **compress them** (e.g., using `tar.gz` or `zip`) and attach them to bug reports for faster troubleshooting.
Without logs, I **cannot investigate issues** — there’s no way to magically see into your system without this information! In other
words - **my crystal ball is broken**!
---
### Quirks
Linux distributions vary widely in how they implement standards, so certain **quirks** may appear in Timekpr-nExT:
- Desktop Environments (DEs) may implement specifications partially or differently.
- Features might behave differently depending on the DE or distribution version.
- Quirks can affect **appearance, functionality, or notifications**.
> **Note:** These quirks may change as distributions and DEs evolve. Timekpr-nExT aims to support as many versions as possible, so some issues may persist on older versions.
#### Sound "bell" notifications
- If a user has enabled **Use sound "bell" for notifications**, critical notifications might not appear on the desktop.
- They are still registered in the notification system and can be reviewed there.
---
#### Blinking cursor / black screen after session termination
- Timekpr-nExT relies heavily on `login1` DBUS interfaces to terminate user sessions.
- On some systems, this can result in a **black screen or blinking cursor**, giving the impression that the system has frozen.
- This is often due to older installations or when the session does not switch to the login manager automatically.
- Workaround: Timekpr-nExT tries to detect and switch to the login manager after session termination.
- Using the **"kill sessions"** option in lockout/restriction types usually resolves the issue.
---
#### Idle time accounting while screen is locked
- Different Desktop Environments handle screen locking differently:
- **Gnome3-based DEs:** Screen must be locked and turned off for session to be considered inactive.
- **KDE, XFCE:** Locking the screen is sufficient.
- **Deepin:** Screen locking is incompatible due to deviations from FreeDesktop / Gnome standards.
---
#### Technical nuances
- These discrepancies can affect **time accounting** and session handling.
- Often, these issues cannot be fixed by Timekpr-nExT alone and depend on the DE or distribution version.
##### Time not accounted while user is active
- Rapid lid open/close can make `org.freedesktop.login1` report the session as inactive.
- Mitigation: Enable **inactive session tracking**, but results may vary.
##### Time not accounted when computer is locked
- Some DEs expose `org.freedesktop.ScreenSaver` incorrectly (`GetActive` always false or method not implemented).
- Some DEs implement `IdleHint` or `LockedHint` differently or not at all.
- If these interfaces are unreliable, idle time tracking may fail. Workarounds are limited.
##### Empty screen after logout
- Occasionally, logging out users via `org.freedesktop.login1.Manager` may result in a black screen if the DE does not handle session termination properly.
- Using **"kill sessions"** generally resolves this.
---
> **Summary:** Timekpr-nExT implements numerous workarounds to support popular DEs, but inconsistencies in Linux distributions can affect functionality. Many quirks are DE-specific and may require configuration adjustments or distribution updates.
## How You Can Help
A lot of effort went into developing Timekpr-nExT. If you appreciate the work and would like to contribute, you can do so via **PayPal** or **Bitcoin**:
- **PayPal (no account required):** [https://tinyurl.com/yc9x85v2](https://tinyurl.com/yc9x85v2)
- **Bitcoin address:** `bc1q57wapz6lxfyxex725x3gs7nntm3tazw2n96nk3`
You can also help by **translating Timekpr-nExT** into your language: [https://translations.launchpad.net/timekpr-next](https://translations.launchpad.net/timekpr-next)
---
## Disclaimer
Timekpr-nExT originated from the need to improve **timekpr-revived**, focusing on precise time accounting and proper session termination. Initially, it was developed for personal use (to monitor my kid’s computer usage), but that need no longer exists.
Key points:
- **Proactive feature development is limited:** Expect feature additions mainly from user suggestions or requests.
- **Testing on new distributions is limited:** Timekpr-nExT may not be actively tested on the latest OS versions unless bugs are reported.
- **Configurability:** Timekpr-nExT is flexible; simple needs can be met without complexity, but advanced configurations are possible.
If the application meets your needs, consider showing support [here](#support). Otherwise, suggest features or improvements—you may see them implemented in future updates.
---
## Suggestions and Bug Reporting
- **Register suggestions and bugs:** [https://bugs.launchpad.net/timekpr-next](https://bugs.launchpad.net/timekpr-next)
- **Ask questions:** [https://answers.launchpad.net/timekpr-next](https://answers.launchpad.net/timekpr-next)
- **Prefix beta bugs:** Include `BETA` in the bug title if the issue was found in a beta version.
You can also email suggestions/bugs to `edzis"replace this with at symbol"inbox"place a dot here"lv`
**REMEMBER: bug reporting without [log files](#logfiles) is useless!
Always provide log files to bug reports and questions section, if the issue you are having is related to unexpected behaviour!**
**Bug report tips:**
1. Describe the issue precisely and include steps to reproduce it.
2. Mention your OS distribution, version, and Desktop Environment.
3. Be ready to provide configuration and log files (no sensitive data except usernames).
Submitting bugs through Launchpad is preferred, but email reports are also accepted, all this may be addressed as time permits.
---
Thanks for choosing Timekpr-nExT!
timekpr-next/resource/ 000775 001750 001750 00000000000 13716566163 017042 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/resource/client/ 000775 001750 001750 00000000000 13476006650 020311 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/resource/client/forms/ 000775 001750 001750 00000000000 15122253261 021427 5 ustar 00bezvfedu bezvfedu 000000 000000 timekpr-next/resource/client/forms/admin.glade 000644 001750 001750 00001004644 15122253261 023524 0 ustar 00bezvfedu bezvfedu 000000 000000
timekpr-next/resource/client/forms/client.glade 000644 001750 001750 00000365704 15122253204 023715 0 ustar 00bezvfedu bezvfedu 000000 000000
TrueFalse3gtk-newTrueFalse3gtk-new