Skip to content

Instantly share code, notes, and snippets.

@JasonLG1979
Created November 20, 2017 21:42
Show Gist options
  • Save JasonLG1979/935a66eff58eb72ab25f876ce5cc05d9 to your computer and use it in GitHub Desktop.
Save JasonLG1979/935a66eff58eb72ab25f876ce5cc05d9 to your computer and use it in GitHub Desktop.
#
# 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