Created
November 20, 2017 21:42
-
-
Save JasonLG1979/935a66eff58eb72ab25f876ce5cc05d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Copyright (C) 2017 Jason Gray <jasonlevigray3@gmail.com> | |
# | |
# This program is free software: you can redistribute it and/or modify it | |
# under the terms of the GNU General Public License version 3, as published | |
# by the Free Software Foundation. | |
# | |
# This program is distributed in the hope that it will be useful, but | |
# WITHOUT ANY WARRANTY; without even the implied warranties of | |
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | |
# PURPOSE. See the GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License along | |
# with this program. If not, see <http://www.gnu.org/licenses/>. | |
# END LICENSE | |
# See <https://developer.gnome.org/notification-spec/> and | |
# <https://github.com/JasonLG1979/possibly-useful-scraps/wiki/GioNotify> | |
# for documentation. | |
from enum import Enum | |
import gi | |
gi.require_version('Gtk', '3.0') | |
from gi.repository import GLib, GObject, Gtk, Gio | |
class GioNotify(Gio.DBusProxy): | |
# Notification Closed Reason Constants. | |
class Closed(Enum): | |
REASON_EXPIRED = 1 | |
REASON_DISMISSED = 2 | |
REASON_CLOSEMETHOD = 3 | |
REASON_UNDEFINED = 4 | |
@property | |
def explanation(self): | |
value = self.value | |
if value == 1: | |
return 'The notification expired.' | |
elif value == 2: | |
return 'The notification was dismissed by the user.' | |
elif value == 3: | |
return 'The notification was closed by a call to CloseNotification.' | |
elif value == 4: | |
return 'The notification was closed by undefined/reserved reasons.' | |
__gtype_name__ = 'GioNotify' | |
__gsignals__ = { | |
'action-invoked': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)), | |
'closed': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), | |
} | |
def __init__(self, **kwargs): | |
super().__init__( | |
g_bus_type=Gio.BusType.SESSION, | |
g_interface_name='org.freedesktop.Notifications', | |
g_name='org.freedesktop.Notifications', | |
g_object_path='/org/freedesktop/Notifications', | |
**kwargs | |
) | |
self.time_out = -1 | |
self._caps = None | |
self._server_info = None | |
self._replace_id = 0 | |
self._actions = [] | |
self._callbacks = {} | |
self._hints = {} | |
@classmethod | |
def async_init(cls, app_name, callback): | |
def on_init_finish(self, result, *ignore): | |
self.init_finish(result) | |
self.call( | |
'GetCapabilities', | |
None, | |
Gio.DBusCallFlags.NONE, | |
-1, | |
None, | |
on_GetCapabilities_finish, | |
None, | |
) | |
def on_GetCapabilities_finish(self, result, *ignore): | |
self._caps = self.call_finish(result).unpack()[0] | |
self.call( | |
'GetServerInformation', | |
None, | |
Gio.DBusCallFlags.NONE, | |
-1, | |
None, | |
on_GetServerInformation_finish, | |
None, | |
) | |
def on_GetServerInformation_finish(self, result, *ignore): | |
info = self.call_finish(result).unpack() | |
self._server_info = { | |
'name': info[0], | |
'vendor': info[1], | |
'version': info[2], | |
'spec_version': info[3], | |
} | |
callback(self._server_info, self._caps) | |
self = cls() | |
self._app_name = app_name | |
self.init_async(GLib.PRIORITY_DEFAULT, None, on_init_finish, None) | |
return self | |
@property | |
def capabilities(self): | |
return self._caps | |
@property | |
def server_information(self): | |
return self._server_info | |
def show_new(self, summary, body, icon): | |
def on_Notify_finish(self, result): | |
self._replace_id = self.call_finish(result).unpack()[0] | |
args = GLib.Variant('(susssasa{sv}i)', (self._app_name, self._replace_id, | |
icon, summary, body, | |
self._actions, self._hints, self.time_out)) | |
self.call( | |
'Notify', | |
args, | |
Gio.DBusCallFlags.NONE, | |
-1, | |
None, | |
on_Notify_finish, | |
) | |
def close(self): | |
if self._replace_id == 0: | |
return | |
self.call( | |
'CloseNotification', | |
GLib.Variant('(u)', (self._replace_id,)), | |
Gio.DBusCallFlags.NONE, | |
-1, | |
None, | |
None, | |
) | |
def add_action(self, action_id, label, callback): | |
self._actions += [action_id, label] | |
self._callbacks[action_id] = callback | |
def clear_actions(self): | |
self._actions.clear() | |
self._callbacks.clear() | |
def set_hint(self, key, value): | |
if value is None: | |
if key in self._hints: | |
del self._hints[key] | |
else: | |
self._hints[key] = value | |
def do_g_signal(self, sender_name, signal_name, parameters): | |
notification_id, signal_value = parameters.unpack() | |
# We only care about our notifications. | |
if notification_id != self._replace_id: | |
return | |
if signal_name == 'ActionInvoked': | |
self.emit('action-invoked', signal_value) | |
self._callbacks[signal_value]() | |
else: | |
self.emit('closed', GioNotify.Closed(signal_value)) | |
def __getattr__(self, name): | |
# PyGObject ships an override that breaks our usage. | |
return object.__getattr__(self, name) | |
class GioNotifyTest(Gtk.Window): | |
def __init__(self): | |
Gtk.Window.__init__(self, title='GioNotify Test') | |
self.notification = GioNotify.async_init('GioNotify Test', self.on_init_finish) | |
self.supports_actions = False | |
self.image_uri = None | |
self.signals_message = [] | |
self.set_default_size(600, -1) | |
self.set_resizable(False) | |
self.set_border_width(10) | |
self.headerbar = Gtk.HeaderBar() | |
self.headerbar.set_show_close_button(True) | |
self.headerbar.set_title('GioNotify Test') | |
self.set_titlebar(self.headerbar) | |
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) | |
self.add(vbox) | |
title = Gtk.Label() | |
title.set_label('Notification Title:') | |
vbox.pack_start(title, True, True, 0) | |
self.title_entry = Gtk.Entry() | |
vbox.pack_start(self.title_entry, True, True, 0) | |
body = Gtk.Label() | |
body.set_label('Notification body:') | |
vbox.pack_start(body, True, True, 0) | |
self.body_entry = Gtk.Entry() | |
vbox.pack_start(self.body_entry, True, True, 0) | |
self.action_label = Gtk.Label() | |
choose_image_button = Gtk.Button('Select a Notification Image') | |
choose_image_button.connect("clicked", self.on_file_clicked) | |
vbox.pack_start(choose_image_button, True, True, 0) | |
self.action_label.set_label('Action Button Label:') | |
vbox.pack_start(self.action_label, True, True, 0) | |
self.action_entry = Gtk.Entry() | |
vbox.pack_start(self.action_entry, True, True, 0) | |
show_notification_button = Gtk.Button('Show Notification') | |
show_notification_button.connect("clicked", self.on_show_notification) | |
vbox.pack_start(show_notification_button, True, True, 0) | |
close_notification_button = Gtk.Button('Close Notification') | |
close_notification_button.connect("clicked", lambda *ignore: self.notification.close()) | |
vbox.pack_start(close_notification_button, True, True, 0) | |
self.server_info_caps = Gtk.Label() | |
self.server_info_caps.set_use_markup(True) | |
vbox.pack_start(self.server_info_caps, True, True, 0) | |
signal = Gtk.Label() | |
signal.set_use_markup(True) | |
vbox.pack_start(signal, True, True, 0) | |
signal.set_label('<b><big>Signals:</big></b>') | |
self.signals_label = Gtk.Label() | |
vbox.pack_start(self.signals_label, True, True, 0) | |
def on_init_finish(self, server_info, capabilities): | |
self.notification.connect('action-invoked', self.on_action_invoked) | |
self.notification.connect('closed', self.on_closed) | |
self.supports_actions = 'actions' in capabilities | |
if not self.supports_actions: | |
self.action_label.set_label('Action Buttons not supported by Notification server') | |
self.action_entry.set_sensitive(False) | |
label_text = [] | |
label_text.append('<b><big>Server information:</big></b>') | |
for key, value in server_info.items(): | |
label_text.append('<small>{}: {}</small>'.format(key, value)) | |
label_text.append('\n<b><big>Server Capabilities:</big></b>') | |
for capability in capabilities: | |
label_text.append('<small>{}</small>'.format(capability)) | |
label_text = '\n'.join(label_text) | |
self.server_info_caps.set_label(label_text) | |
def on_show_notification(self, *ignore): | |
def dummy_action_callback(): | |
pass | |
if self.supports_actions: | |
self.notification.clear_actions() | |
action = self.action_entry.get_text() | |
if action: | |
self.notification.add_action( | |
action, | |
action, | |
dummy_action_callback, | |
) | |
summary = self.title_entry.get_text() | |
body = self.body_entry.get_text() | |
if self.image_uri: | |
icon = self.image_uri | |
else: | |
icon = 'dialog-information-symbolic' | |
self.signals_message = [] | |
self.signals_label.set_label('') | |
self.notification.show_new(summary, body, icon) | |
def on_file_clicked(self, widget): | |
dialog = Gtk.FileChooserDialog( | |
'Please Choose an Image', self, | |
Gtk.FileChooserAction.OPEN, | |
('_Cancel', Gtk.ResponseType.CANCEL, '_Open', Gtk.ResponseType.OK), | |
) | |
self.add_filters(dialog) | |
response = dialog.run() | |
if response == Gtk.ResponseType.OK: | |
self.image_uri = dialog.get_uri() | |
dialog.destroy() | |
def add_filters(self, dialog): | |
filter_text = Gtk.FileFilter() | |
filter_text.set_name('png') | |
filter_text.add_mime_type('image/png') | |
dialog.add_filter(filter_text) | |
filter_text = Gtk.FileFilter() | |
filter_text.set_name('jpg') | |
filter_text.add_mime_type('image/jpg') | |
dialog.add_filter(filter_text) | |
filter_text = Gtk.FileFilter() | |
filter_text.set_name('jpeg') | |
filter_text.add_mime_type('image/jpeg') | |
dialog.add_filter(filter_text) | |
def on_action_invoked(self, obj, action_id): | |
self.signals_message.append('ActionInvoked: {}'.format(action_id)) | |
def on_closed(self, obj, reason): | |
self.signals_message.append('NotificationClosed: {}'.format(reason.explanation)) | |
label_text = '\n'.join(self.signals_message) | |
self.signals_label.set_label(label_text) | |
if __name__ == '__main__': | |
win = GioNotifyTest() | |
win.connect('delete-event', Gtk.main_quit) | |
win.show_all() | |
Gtk.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment