示例#1
0
    def show_notify(self, dbus_id, tray, nid, app_name, replaces_nid, app_icon,
                    summary, body, actions, hints, expire_timeout, icon):
        if not self.dbus_check(dbus_id):
            return
        self.may_retry = True
        try:
            icon_string = self.get_icon_string(nid, app_icon, icon)
            log("get_icon_string%s=%s", (nid, app_icon, ellipsizer(icon)),
                icon_string)
            try:
                app_str = self.app_name_format % app_name
            except TypeError:
                app_str = app_name or "Xpra"
            self.last_notification = (dbus_id, tray, nid, app_name,
                                      replaces_nid, app_icon, summary, body,
                                      expire_timeout, icon)

            def NotifyReply(notification_id):
                log("NotifyReply(%s) for nid=%i", notification_id, nid)
                self.actual_notification_id[nid] = int(notification_id)

            dbus_hints = self.parse_hints(hints)
            self.dbusnotify.Notify(app_str,
                                   0,
                                   icon_string,
                                   summary,
                                   body,
                                   actions,
                                   dbus_hints,
                                   expire_timeout,
                                   reply_handler=NotifyReply,
                                   error_handler=self.NotifyError)
        except Exception:
            log.error("Error: dbus notify failed", exc_info=True)
示例#2
0
 def get_gtk_notifier(self):
     if self.gtk_notifier is None:
         try:
             self.gtk_notifier = GTK_Notifier(self.closed_cb, self.action_cb)
         except:
             log("failed to load GTK Notifier fallback", exc_info=True)
     return self.gtk_notifier
示例#3
0
 def parse_hints(self, h) -> dict:
     hints = {}
     for x in ("action-icons", "category", "desktop-entry", "resident",
               "transient", "x", "y", "urgency"):
         v = h.get(x)
         if v is not None:
             hints[x] = native_to_dbus(v)
     image_data = h.get("image-data")
     if image_data and bytestostr(image_data[0]) == "png":
         try:
             from xpra.codecs.pillow.decoder import open_only
             img_data = image_data[3]
             img = open_only(img_data, ("png", ))
             w, h = img.size
             channels = len(img.mode)
             rowstride = w * channels
             has_alpha = img.mode == "RGBA"
             pixel_data = bytearray(img.tobytes("raw", img.mode))
             log.info("pixel_data=%s", type(pixel_data))
             args = w, h, rowstride, has_alpha, 8, channels, pixel_data
             hints["image-data"] = tuple(native_to_dbus(x) for x in args)
             #hints["image-data"] = args
         except Exception as e:
             log("parse_hints(%s) error on image-data=%s",
                 h,
                 image_data,
                 exc_info=True)
             log.error("Error parsing notification image:")
             log.error(" %s", e)
     log("parse_hints(%s)=%s", h, hints)
     #return dbus.types.Dictionary(hints, signature="sv")
     return hints
示例#4
0
 def show_notify(self, dbus_id, tray, nid, app_name, replaces_nid, app_icon,
                 summary, body, actions, hints, expire_timeout, icon):
     getHWND = getattr(tray, "getHWND", None)
     if GTK_NOTIFIER or tray is None or getHWND is None or actions:
         log(
             "show_notify(..) using gtk fallback, GTK_NOTIFIER=%s, tray=%s, getHWND=%s, actions=%s",
             GTK_NOTIFIER, tray, getHWND, actions)
         gtk_notifier = self.get_gtk_notifier()
         if gtk_notifier:
             gtk_notifier.show_notify(dbus_id, tray, nid, app_name,
                                      replaces_nid, app_icon, summary, body,
                                      actions, hints, expire_timeout, icon)
             self.gtk_notifications.add(nid)
             return
     if tray is None:
         log.warn("Warning: no system tray - cannot show notification!")
         return
     hwnd = getHWND()
     app_id = tray.app_id
     log("show_notify%s hwnd=%#x, app_id=%i",
         (dbus_id, tray, nid, app_name, replaces_nid, app_icon, summary,
          body, actions, hints, expire_timeout, icon), hwnd, app_id)
     #FIXME: remove handles when notification is closed
     self.notification_handles[nid] = (hwnd, app_id)
     do_notify(hwnd, app_id, summary, body, expire_timeout, icon)
示例#5
0
    def NotifyError(self, dbus_error, *_args):
        try:
            if isinstance(dbus_error, dbus.exceptions.DBusException):
                message = dbus_error.get_dbus_message()
                dbus_error_name = dbus_error.get_dbus_name()
                if dbus_error_name != "org.freedesktop.DBus.Error.ServiceUnknown":
                    log.error("unhandled dbus exception: %s, %s", message,
                              dbus_error_name)
                    return False

                if not self.may_retry:
                    log.error("Error: cannot send notification via dbus,")
                    log.error(
                        " check that you notification service is operating properly"
                    )
                    return False
                self.may_retry = False

                log.info("trying to re-connect to the notification service")
                #try to connect to the notification again (just once):
                self.setup_dbusnotify()
                #and retry:
                self.show_notify(*self.last_notification)
        except Exception:
            log("cannot filter error", exc_info=True)
        log.error("Error processing notification:")
        log.error(" %s", dbus_error)
        return False
示例#6
0
 def get_x(self, w):
     x = self.stack.get_origin_x() - w//2
     if (x + w) >= self.stack.max_width:    #dont overflow on the right
         x = self.stack.max_width - w
     if x <= 0:                                #or on the left
         x = 0
     log("get_x(%s)=%s", w, x)
     return    x
示例#7
0
 def close_notify(self, nid: int):
     actual_id = self.actual_notification_id.get(nid)
     if actual_id is None:
         log(
             "close_notify(%i) actual notification not found, already closed?",
             nid)
         return
     log("close_notify(%i) actual id=%s", nid, actual_id)
     self.do_close(nid, actual_id)
示例#8
0
 def get_y(self, h):
     y = self.stack.get_origin_y()
     if y >= (self.stack.max_height//2):        #if near bottom, substract window height
         y = y - h
     if (y + h) >= self.stack.max_height:
         y = self.stack.max_height - h
     if y<= 0:
         y = 0
     log("get_y(%s)=%s", h, y)
     return    y
示例#9
0
 def hide_notification(self):
     """Destroys the notification and tells the stack to move the
     remaining notification windows"""
     log("hide_notification()")
     for timer in ("fade_in_timer", "fade_out_timer", "wait_timer"):
         v = getattr(self, timer)
         if v:
             setattr(self, timer, None)
             glib.source_remove(v)
     self.destroy()
     self.destroy_cb(self)
示例#10
0
 def close_notify(self, nid):
     try:
         self.gtk_notifications.remove(nid)
     except KeyError:
         try:
             hwnd, app_id = self.notification_handles.pop(nid)
         except KeyError:
             return
         log("close_notify(%i) hwnd=%i, app_id=%i", nid, hwnd, app_id)
         notify(hwnd, app_id, "", "", 0, None)
     else:
         self.get_gtk_notifier().close_notify(nid)
示例#11
0
    def do_close(self, nid: int, actual_id: int):
        log("do_close_notify(%i)", actual_id)

        def CloseNotificationReply():
            self.actual_notification_id.pop(nid, None)

        def CloseNotificationError(dbus_error, *_args):
            log.warn("Error: error closing notification:")
            log.warn(" %s", dbus_error)

        self.dbusnotify.CloseNotification(actual_id,
                                          reply_handler=CloseNotificationReply,
                                          error_handler=CloseNotificationError)
示例#12
0
 def NotificationClosed(self, actual_id: int, reason):
     nid = self._find_nid(actual_id)
     reason_str = {
         1: "expired",
         2: "dismissed by the user",
         3: "closed by a call to CloseNotification",
         4: "Undefined/reserved reasons",
     }.get(int(reason), str(reason))
     log("NotificationClosed(%s, %s) nid=%s, reason=%s", actual_id, reason,
         nid, reason_str)
     if nid:
         self.actual_notification_id.pop(nid, None)
         self.clean_notification(nid)
         if self.closed_cb:
             self.closed_cb(nid, int(reason), reason_str)
示例#13
0
    def __init__(self,
                 closed_cb=None,
                 action_cb=None,
                 size_x=DEFAULT_WIDTH,
                 size_y=DEFAULT_HEIGHT,
                 timeout=5):
        NotifierBase.__init__(self, closed_cb, action_cb)
        self.handles_actions = True
        """
        Create a new notification stack.  The recommended way to create Popup instances.
          Parameters:
            `size_x` : The desired width of the notifications.
            `size_y` : The desired minimum height of the notifications. If the text is
            longer it will be expanded to fit.
            `timeout` : Popup instance will disappear after this timeout if there
            is no human intervention. This can be overridden temporarily by passing
            a new timout to the new_popup method.
        """
        self.size_x = size_x
        self.size_y = size_y
        self.timeout = timeout
        """
        Other parameters:
        These will take effect for every popup created after the change.
            `max_popups` : The maximum number of popups to be shown on the screen
            at one time.
            `bg_color` : if None default is used (usually grey). set with a gdk.Color.
            `fg_color` : if None default is used (usually black). set with a gdk.Color.
            `show_timeout : if True, a countdown till destruction will be displayed.

        """
        self.max_popups = 5
        self.fg_color = DEFAULT_FG_COLOUR
        self.bg_color = DEFAULT_BG_COLOUR
        self.show_timeout = False

        self._notify_stack = []
        self._offset = 0

        display = display_get_default()
        screen = display.get_default_screen()
        n = screen.get_n_monitors()
        log("screen=%s, monitors=%s", screen, n)
        if n < 2:
            self.max_width = screen.get_width()
            self.max_height = screen.get_height()
            log("screen dimensions: %dx%d", self.max_width, self.max_height)
        else:
            rect = screen.get_monitor_geometry(0)
            self.max_width = rect.width
            self.max_height = rect.height
            log("first monitor dimensions: %dx%d", self.max_width,
                self.max_height)
        self.x = self.max_width - 20  #keep away from the edge
        self.y = self.max_height - 64  #space for a panel
        log("our reduced dimensions: %dx%d", self.x, self.y)
示例#14
0
    def setup_dbusnotify(self):
        self.dbus_session = dbus.SessionBus()
        FD_NOTIFICATIONS = 'org.freedesktop.Notifications'
        self.org_fd_notifications = self.dbus_session.get_object(
            FD_NOTIFICATIONS, '/org/freedesktop/Notifications')
        self.org_fd_notifications.connect_to_signal("NotificationClosed",
                                                    self.NotificationClosed)
        self.org_fd_notifications.connect_to_signal("ActionInvoked",
                                                    self.ActionInvoked)

        #connect_to_signal("HelloSignal", hello_signal_handler, dbus_interface="com.example.TestService", arg0="Hello")
        self.dbusnotify = dbus.Interface(self.org_fd_notifications,
                                         FD_NOTIFICATIONS)
        log("using dbusnotify: %s(%s)", type(self.dbusnotify),
            FD_NOTIFICATIONS)
        caps = tuple(str(x) for x in self.dbusnotify.GetCapabilities())
        log("capabilities=%s", csv(caps))
        self.handles_actions = "actions" in caps
        log("dbus.get_default_main_loop()=%s", dbus.get_default_main_loop())
示例#15
0
 def popup_cb_clicked(*args):
     self.hide_notification()
     log("popup_cb_clicked%s for action_id=%s, action_text=%s", args, action_id, action_text)
     self.action_cb(self.nid, action_id)
示例#16
0
    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
        super().__init__()

        self.set_accept_focus(False)
        self.set_focus_on_map(False)
        self.set_size_request(stack.size_x, -1)
        self.set_decorated(False)
        self.set_deletable(False)
        self.set_property("skip-pager-hint", True)
        self.set_property("skip-taskbar-hint", True)
        self.connect("enter-notify-event", self.on_hover, True)
        self.connect("leave-notify-event", self.on_hover, False)
        self.set_opacity(0.2)
        self.set_keep_above(True)
        self.destroy_cb = stack.destroy_popup_cb
        self.popup_closed = stack.popup_closed
        self.action_cb = stack.popup_action

        main_box = Gtk.VBox()
        header_box = Gtk.HBox()
        self.header = Gtk.Label()
        self.header.set_padding(3, 3)
        self.header.set_alignment(0, 0)
        header_box.pack_start(self.header, True, True, 5)
        icon = get_icon_pixbuf("close.png")
        if icon:
            close_button = Gtk.Image()
            close_button.set_from_pixbuf(icon)
            close_button.set_padding(3, 3)
            close_window = Gtk.EventBox()
            close_window.set_visible_window(False)
            close_window.connect("button-press-event", self.user_closed)
            close_window.add(close_button)
            close_window.set_size_request(icon.get_width(), icon.get_height())
            header_box.pack_end(close_window, False, False, 0)
        main_box.pack_start(header_box)

        body_box = Gtk.HBox()
        self.image = Gtk.Image()
        self.image.set_size_request(70, 70)
        self.image.set_alignment(0, 0)
        body_box.pack_start(self.image, False, False, 5)
        self.message = Gtk.Label()
        self.message.set_max_width_chars(80)
        self.message.set_size_request(stack.size_x - 90, -1)
        self.message.set_line_wrap(True)
        self.message.set_alignment(0, 0)
        self.message.set_padding(5, 10)
        self.counter = Gtk.Label()
        self.counter.set_alignment(1, 1)
        self.counter.set_padding(3, 3)
        self.timeout = timeout

        body_box.pack_start(self.message, True, False, 5)
        body_box.pack_end(self.counter, False, False, 5)
        main_box.pack_start(body_box, False, False, 5)

        self.buttons_box = Gtk.HBox(homogeneous=True)
        alignment = Gtk.Alignment(xalign=1.0, yalign=0.5, xscale=0.0, yscale=0.0)
        alignment.add(self.buttons_box)
        main_box.pack_start(alignment)
        self.add(main_box)
        if stack.bg_color is not None:
            self.modify_bg(Gtk.StateType.NORMAL, stack.bg_color)
        if stack.fg_color is not None:
            self.message.modify_fg(Gtk.StateType.NORMAL, stack.fg_color)
            self.header.modify_fg(Gtk.StateType.NORMAL, stack.fg_color)
            self.counter.modify_fg(Gtk.StateType.NORMAL, stack.fg_color)
        self.show_timeout = show_timeout
        self.hover = False
        self.show_all()
        self.w = self.get_preferred_width()[0]
        self.h = self.get_preferred_height()[0]
        self.move(self.get_x(self.w), self.get_y(self.h))
        self.wait_timer = None
        self.fade_out_timer = None
        self.fade_in_timer = GLib.timeout_add(100, self.fade_in)
        #populate the window:
        self.set_content(title, message, actions, image)
        #ensure we dont show it in the taskbar:
        self.realize()
        self.get_window().set_skip_taskbar_hint(True)
        self.get_window().set_skip_pager_hint(True)
        add_close_accel(self, self.user_closed)
示例#17
0
 def ActionInvoked(self, actual_id: int, action):
     nid = self._find_nid(actual_id)
     log("ActionInvoked(%s, %s) nid=%s", actual_id, action, nid)
     if nid:
         if self.action_cb:
             self.action_cb(nid, str(action))
示例#18
0
 def NotifyReply(notification_id):
     log("NotifyReply(%s) for nid=%i", notification_id, nid)
     self.actual_notification_id[nid] = int(notification_id)
示例#19
0
 def reposition(self, offset, stack):
     """Move the notification window down, when an older notification is removed"""
     log("reposition(%s, %s)", offset, stack)
     new_offset = self.h + offset
     GLib.idle_add(self.move, self.get_x(self.w), self.get_y(new_offset))
     return new_offset
示例#20
0
    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)

        self.set_size_request(stack.size_x, -1)
        self.set_decorated(False)
        self.set_deletable(False)
        self.set_property("skip-pager-hint", True)
        self.set_property("skip-taskbar-hint", True)
        self.connect("enter-notify-event", self.on_hover, True)
        self.connect("leave-notify-event", self.on_hover, False)
        self.set_opacity(0.2)
        self.set_keep_above(True)
        self.destroy_cb = stack.destroy_popup_cb
        self.popup_closed = stack.popup_closed
        self.action_cb = stack.popup_action

        main_box = gtk.VBox()
        header_box = gtk.HBox()
        self.header = gtk.Label()
        self.header.set_markup("<b>%s</b>" % title)
        self.header.set_padding(3, 3)
        self.header.set_alignment(0, 0)
        header_box.pack_start(self.header, True, True, 5)
        icon = get_pixbuf("close.png")
        if icon:
            close_button = gtk.Image()
            close_button.set_from_pixbuf(icon)
            close_button.set_padding(3, 3)
            close_window = gtk.EventBox()
            close_window.set_visible_window(False)
            close_window.connect("button-press-event", self.user_closed)
            close_window.add(close_button)
            close_window.set_size_request(icon.get_width(), icon.get_height())
            header_box.pack_end(close_window, False, False)
        main_box.pack_start(header_box)

        body_box = gtk.HBox()
        if image is not None:
            self.image = gtk.Image()
            self.image.set_size_request(70, 70)
            self.image.set_alignment(0, 0)
            self.image.set_from_pixbuf(image)
            body_box.pack_start(self.image, False, False, 5)
        self.message = gtk.Label()
        self.message.set_max_width_chars(80)
        self.message.set_size_request(stack.size_x - 90, -1)
        self.message.set_line_wrap(True)
        self.message.set_alignment(0, 0)
        self.message.set_padding(5, 10)
        self.message.set_text(message)
        self.counter = gtk.Label()
        self.counter.set_alignment(1, 1)
        self.counter.set_padding(3, 3)
        self.timeout = timeout

        body_box.pack_start(self.message, True, False, 5)
        body_box.pack_end(self.counter, False, False, 5)
        main_box.pack_start(body_box, False, False, 5)

        if len(actions)>=2:
            buttons_box = gtk.HBox(True)
            while len(actions)>=2:
                action_id, action_text = actions[:2]
                actions = actions[2:]
                button = self.action_button(action_id, action_text)
                buttons_box.add(button)
            alignment = gtk.Alignment(xalign=1.0, yalign=0.5, xscale=0.0, yscale=0.0)
            alignment.add(buttons_box)
            main_box.pack_start(alignment)
        self.add(main_box)
        if stack.bg_color is not None:
            self.modify_bg(STATE_NORMAL, stack.bg_color)
        if stack.fg_color is not None:
            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 = show_timeout
        self.hover = False
        self.show_all()
        self.w, self.h = get_preferred_size(self)
        self.move(self.get_x(self.w), self.get_y(self.h))
        self.wait_timer = None
        self.fade_out_timer = None
        self.fade_in_timer = glib.timeout_add(100, self.fade_in)
        #ensure we dont show it in the taskbar:
        self.realize()
        self.get_window().set_skip_taskbar_hint(True)
        self.get_window().set_skip_pager_hint(True)
        add_close_accel(self, self.user_closed)