diff --git a/src/xpra/gtk_common/gtk_notifier.py b/src/xpra/gtk_common/gtk_notifier.py index 76d234bc8f..cf2a906856 100644 --- a/src/xpra/gtk_common/gtk_notifier.py +++ b/src/xpra/gtk_common/gtk_notifier.py @@ -105,10 +105,10 @@ def get_origin_x(self): def get_origin_y(self): return self.y - def show_notify(self, dbus_id, tray, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout, icon): - self.new_popup(nid, summary, body, actions, icon) + def show_notify(self, dbus_id, tray, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, timeout, icon): + self.new_popup(nid, summary, body, actions, icon, timeout, timeout>0 and timeout<=600) - def new_popup(self, nid, summary, body, actions, icon): + def new_popup(self, nid, summary, body, actions, icon, timeout=10, show_timeout=False): """Create a new Popup instance.""" if len(self._notify_stack) == self.max_popups: oldest = self._notify_stack[0] @@ -121,7 +121,7 @@ def new_popup(self, nid, summary, body, actions, icon): loader.write(img_data, len(img_data)) loader.close() image = loader.get_pixbuf() - popup = Popup(self, nid, summary, body, actions, image=image) + popup = Popup(self, nid, summary, body, actions, image=image, timeout=timeout, show_timeout=show_timeout) self._notify_stack.append(popup) self._offset += self._notify_stack[-1].h return popup @@ -146,8 +146,8 @@ def popup_action(self, nid, action_id): class Popup(gtk.Window): - def __init__(self, stack, nid, title, message, actions, image): - log("Popup%s", (stack, nid, title, message, actions, image)) + def __init__(self, stack, nid, title, message, actions, image, timeout=5, show_timeout=False): + log("Popup%s", (stack, nid, title, message, actions, image, timeout, show_timeout)) self.stack = stack self.nid = nid gtk.Window.__init__(self) @@ -201,7 +201,7 @@ def __init__(self, stack, nid, title, message, actions, image): self.counter = gtk.Label() self.counter.set_alignment(1, 1) self.counter.set_padding(3, 3) - self.timeout = stack.timeout + self.timeout = timeout body_box.pack_start(self.message, True, False, 5) body_box.pack_end(self.counter, False, False, 5) @@ -224,7 +224,7 @@ def __init__(self, stack, nid, title, message, actions, image): self.message.modify_fg(STATE_NORMAL, stack.fg_color) self.header.modify_fg(STATE_NORMAL, stack.fg_color) self.counter.modify_fg(STATE_NORMAL, stack.fg_color) - self.show_timeout = stack.show_timeout + self.show_timeout = show_timeout self.hover = False self.show_all() self.w, self.h = get_preferred_size(self) diff --git a/src/xpra/notifications/common.py b/src/xpra/notifications/common.py index cb5eaa07d0..13f0caa51d 100755 --- a/src/xpra/notifications/common.py +++ b/src/xpra/notifications/common.py @@ -3,10 +3,15 @@ # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. +import os.path from xpra.os_util import BytesIOClass from xpra.log import Logger log = Logger("dbus", "notify") +XPRA_NOTIFICATIONS_OFFSET = 2**31 +XPRA_BANDWIDTH_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+1 +XPRA_IDLE_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+2 + def parse_image_data(data): try: @@ -29,13 +34,14 @@ def parse_image_data(data): return None def parse_image_path(path): - try: - from PIL import Image - img = Image.open(path) - return image_data(img) - except Exception as e: - log.error("Error parsing image path '%s' for notification:", path) - log.error(" %s", e) + if os.path.exists(path): + try: + from PIL import Image + img = Image.open(path) + return image_data(img) + except Exception as e: + log.error("Error parsing image path '%s' for notification:", path) + log.error(" %s", e) return None def image_data(img): diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index 7dbd966e5d..5ec70474c4 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -33,6 +33,7 @@ notifylog = Logger("notify") httplog = Logger("http") bandwidthlog = Logger("bandwidth") +timeoutlog = Logger("timeout") from xpra.platform.features import COMMAND_SIGNALS, CLIPBOARDS from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS @@ -46,7 +47,7 @@ from xpra.net.bytestreams import set_socket_timeout from xpra.platform import get_username from xpra.platform.paths import get_icon_filename, get_icon_dir -from xpra.notifications.common import parse_image_path +from xpra.notifications.common import parse_image_path, XPRA_IDLE_NOTIFICATION_ID from xpra.child_reaper import reaper_cleanup from xpra.scripts.config import parse_bool_or_int, parse_bool, FALSE_OPTIONS, TRUE_OPTIONS from xpra.scripts.main import sound_option, parse_env @@ -517,6 +518,7 @@ def _process_notification_action(self, proto, packet): nid, action_key = packet[1:3] ss = self._server_sources.get(proto) assert ss + ss.user_event() try: #special client callback notification: client_callback = ss.notification_callbacks.pop(nid) @@ -1193,17 +1195,33 @@ def is_timedout(self, protocol): def idle_timeout_cb(self, source): - log("idle_timeout_cb(%s)", source) + timeoutlog("idle_timeout_cb(%s)", source) p = source.protocol if p: self.disconnect_client(p, IDLE_TIMEOUT) def idle_grace_timeout_cb(self, source): - log("idle_grace_timeout_cb(%s)", source) - timeout_nid = 2**16 + 2**8 + 1 - source.notify(0, timeout_nid, "xpra", 0, "", "This Xpra session will timeout soon", "Activate one of the windows to avoid this timeout", [], {}, 10, "") + timeoutlog("idle_grace_timeout_cb(%s)", source) + nid = XPRA_IDLE_NOTIFICATION_ID + actions = ["cancel", "Cancel Timeout"] + user_icon = os.path.join(get_icon_dir(), "timer.png") + icon = parse_image_path(user_icon) or () + def idle_notification_action(nid, action_id): + timeoutlog("idle_notification_action(%i, %s)", nid, action_id) + if action_id=="cancel": + source.user_event() + source.no_idle() + if self.session_name!="Xpra": + summary = "The Xpra session %s" % self.session_name + else: + summary = "Xpra session" + summary += " is about to timeout" + body = "Unless this session sees some activity,\n" + \ + "it will be terminated soon." + source.notify("", nid, "Xpra", 0, "", summary, body, actions, {}, source.idle_grace_duration, icon, user_callback=idle_notification_action) source.go_idle() + def _log_disconnect(self, proto, *args): #skip logging of disconnection events for server sources #we have tagged during hello ("info_request", "exit_request", etc..) @@ -1398,8 +1416,7 @@ def notify_new_user(self, ss): if self.notifications_forwarder: dbus_id = self.notifications_forwarder.dbus_id user_icon = os.path.join(get_icon_dir(), "user.png") - if os.path.exists(user_icon): - icon = parse_image_path(user_icon) + icon = parse_image_path(user_icon) or () title = "User '%s' connected to the session" % (ss.name or ss.username or ss.uuid) body = "\n".join(ss.get_connect_info()) for s in self._server_sources.values(): diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index a5fc721cd7..58fcdac54c 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -46,6 +46,7 @@ from xpra.make_thread import start_thread from xpra.os_util import platform_name, Queue, get_machine_id, get_user_uuid, monotonic_time, BytesIOClass, strtobytes, bytestostr, WIN32, POSIX from xpra.server.background_worker import add_work_item +from xpra.notifications.common import XPRA_BANDWIDTH_NOTIFICATION_ID from xpra.platform.paths import get_icon_dir from xpra.util import csv, std, typedict, updict, flatten_dict, notypedict, get_screen_info, envint, envbool, AtomicInteger, \ CLIENT_PING_TIMEOUT, WORKSPACE_UNSET, DEFAULT_METADATA_SUPPORTED @@ -282,6 +283,8 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove self.idle_timeout = idle_timeout self.idle_timeout_cb = idle_timeout_cb self.idle_grace_timeout_cb = idle_grace_timeout_cb + #grace duration is at least 10 seconds: + self.idle_grace_duration = max(10, int(self.idle_timeout*(100-GRACE_PERCENT)//100)) self.idle_timer = None self.idle_grace_timer = None self.schedule_idle_grace_timeout() @@ -714,9 +717,8 @@ def schedule_idle_grace_timeout(self): self.source_remove(self.idle_grace_timer) self.idle_grace_timer = None if self.idle_timeout>0 and not self.is_closed(): - #grace timer is 90% of real timer: - grace = int(self.idle_timeout*1000*GRACE_PERCENT/100) - self.idle_grace_timer = self.timeout_add(grace, self.idle_grace_timedout) + grace = self.idle_timeout - self.idle_grace_duration + self.idle_grace_timer = self.timeout_add(max(0, int(grace*1000)), self.idle_grace_timedout) def idle_grace_timedout(self): self.idle_grace_timer = None @@ -2524,7 +2526,7 @@ def record_congestion_event(self, source, late_pct=0, send_speed=0): if count>CONGESTION_WARNING_EVENT_COUNT: self.bandwidth_warning_time = now dbus_id = "" - nid = 2**31 + nid = XPRA_BANDWIDTH_NOTIFICATION_ID summary = "Network Performance Issue" body = "Your network connection is struggling to keep up,\n" + \ "consider lowering the bandwidth limit,\n" + \