Skip to content

Instantly share code, notes, and snippets.

@TingPing
Created June 19, 2014 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TingPing/f6597c85722c3db00f8c to your computer and use it in GitHub Desktop.
Save TingPing/f6597c85722c3db00f8c to your computer and use it in GitHub Desktop.
[PATCH] Add GNTP (Growl) backend to GLib's GNotification
From a15e1698e89aae090abf73cf9794d9d70aa87f67 Mon Sep 17 00:00:00 2001
From: TingPing <tingping@tingping.se>
Date: Thu, 19 Jun 2014 17:57:13 -0400
Subject: [PATCH] Add GNTP (Growl) backend to GNotification
This only supports the basic features but is cross-platform
---
gio/Makefile.am | 1 +
gio/ggntpnotificationbackend.c | 308 +++++++++++++++++++++++++++++++++++++++++
gio/giomodule.c | 2 +
3 files changed, 311 insertions(+)
create mode 100644 gio/ggntpnotificationbackend.c
diff --git a/gio/Makefile.am b/gio/Makefile.am
index e993e2f..95db676 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -270,6 +270,7 @@ unix_sources = \
gcontenttypeprivate.h \
gfdonotificationbackend.c \
ggtknotificationbackend.c \
+ ggntpnotificationbackend.c \
$(NULL)
diff --git a/gio/ggntpnotificationbackend.c b/gio/ggntpnotificationbackend.c
new file mode 100644
index 0000000..8cca5aa
--- /dev/null
+++ b/gio/ggntpnotificationbackend.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright © 2014 Patrick Griffis
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include "config.h"
+
+#include "gnotificationbackend.h"
+
+#include "gapplication.h"
+#include "giomodule-priv.h"
+#include "gnotification-private.h"
+#include "gsocketclient.h"
+#include "giostream.h"
+
+#define G_TYPE_GNTP_NOTIFICATION_BACKEND (g_gntp_notification_backend_get_type ())
+#define G_GNTP_NOTIFICATION_BACKEND(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_GNTP_NOTIFICATION_BACKEND, GGNTPNotificationBackend))
+
+#define GNTP_HOST "localhost"
+#define GNTP_PORT 23053
+
+/*
+ * GNTP (Growl Network Transport Protocol) backend
+ *
+ * Note that this backend does not support more advanced
+ * growl features such as sending notifications over the network
+ * or password support.
+ *
+ */
+
+typedef struct _GGNTPNotificationBackend GGNTPNotificationBackend;
+typedef GNotificationBackendClass GGNTPNotificationBackendClass;
+
+struct _GGNTPNotificationBackend
+{
+ GNotificationBackend parent;
+ gboolean is_registered;
+ gboolean is_registering;
+ GSList *notifications;
+};
+
+typedef struct
+{
+ GGNTPNotificationBackend *backend;
+ const gchar *title;
+ const gchar *text;
+ gboolean urgent;
+} GGNTPNotification;
+
+GType g_gntp_notification_backend_get_type (void);
+
+G_DEFINE_TYPE_WITH_CODE (GGNTPNotificationBackend, g_gntp_notification_backend, G_TYPE_NOTIFICATION_BACKEND,
+ _g_io_modules_ensure_extension_points_registered ();
+ g_io_extension_point_implement (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME,
+ g_define_type_id, "gntp", 0))
+
+static void
+gntp_notification_free (gpointer data)
+{
+ GGNTPNotification *notification = (GGNTPNotification*)data;
+
+ if (notification->title)
+ g_free ((gchar*)notification->title);
+ if (notification->text)
+ g_free ((gchar*)notification->text);
+ if (notification)
+ g_free (notification);
+}
+
+static GGNTPNotification *
+gntp_notification_new (GGNTPNotificationBackend *backend, GNotification *notification)
+{
+ GGNTPNotification *ret;
+
+ ret = (GGNTPNotification*)g_malloc0 (sizeof(GGNTPNotification));
+ ret->title = g_strdup (g_notification_get_title (notification));
+ ret->text = g_strdup (g_notification_get_body (notification));
+ ret->urgent = g_notification_get_urgent (notification);
+ ret->backend = backend;
+
+ return ret;
+}
+
+static inline void
+gntp_write (GOutputStream *output, gchar *text)
+{
+ gchar formatted_text[2048];
+
+ g_snprintf (formatted_text, sizeof(formatted_text), "%s\r\n", text);
+
+ g_output_stream_write (output, formatted_text, strlen(formatted_text), NULL, NULL);
+}
+
+static void
+gntp_notify_callback (GObject *source, GAsyncResult *res, gpointer userdata)
+{
+ GGNTPNotification *notification = (GGNTPNotification*)userdata;
+ GApplication *app = G_NOTIFICATION_BACKEND(notification->backend)->application;
+ GSocketConnection *conn;
+ GOutputStream *output;
+ GError *error = NULL;
+ gchar app_str[512];
+ gchar notify_title[512];
+ gchar notify_text[1024];
+
+ conn = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT(source), res, &error);
+ if (error)
+ {
+ g_warning ("Could not connect to GNTP service.");
+ g_error_free (error);
+ goto notify_cleanup;
+ }
+
+ output = g_io_stream_get_output_stream (G_IO_STREAM(conn));
+
+ g_snprintf (app_str, sizeof(app_str), "Application-Name: %s", g_application_get_application_id (app));
+ g_snprintf (notify_title, sizeof(notify_title), "Notification-Title: %s", notification->title);
+ g_snprintf (notify_text, sizeof(notify_text), "Notification-Text: %s", notification->text);
+ /*if (notification->icon)
+ g_snprintf (notify_text, sizeof(notify_text), "Notification-Text: %s", notification->text);*/
+
+ gntp_write (output, "GNTP/1.0 NOTIFY NONE");
+ gntp_write (output, app_str);
+ gntp_write (output, "Notification-Name: Notification");
+ gntp_write (output, notify_title);
+ gntp_write (output, notify_text);
+ if (notification->urgent)
+ gntp_write (output, "Notification-Priority: 2");
+ /* TODO: Icon */
+ gntp_write (output, "");
+
+ g_output_stream_close (output, NULL, NULL);
+ /* Don't currently check if the service doesn't like our message, we don't plan on resending */
+
+notify_cleanup:
+ g_object_unref (source);
+ gntp_notification_free (notification);
+}
+
+static void
+gntp_notify (GGNTPNotification *notification)
+{
+ GSocketClient *client;
+
+ client = g_socket_client_new ();
+ g_socket_client_connect_to_host_async (client, GNTP_HOST, GNTP_PORT, NULL,
+ gntp_notify_callback, notification);
+}
+
+static void
+gntp_register_callback (GObject *source, GAsyncResult *res, gpointer userdata)
+{
+ GGNTPNotificationBackend *backend = G_GNTP_NOTIFICATION_BACKEND(userdata);
+ GApplication *app = G_NOTIFICATION_BACKEND(userdata)->application;
+ GSocketConnection *conn;
+ GOutputStream *output;
+ GInputStream *input;
+ gchar input_buf[12]; /* Just enough to see its ok */
+ gchar app_str[512];
+ GError *error = NULL;
+
+ conn = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT(source), res, &error);
+ backend->is_registering = FALSE;
+ if (error)
+ {
+ g_warning ("Could not connect to GNTP service.");
+ g_error_free (error);
+ g_object_unref (source);
+ return;
+ }
+
+ output = g_io_stream_get_output_stream (G_IO_STREAM(conn));
+
+ g_snprintf (app_str, sizeof(app_str), "Application-Name: %s",
+ g_application_get_application_id (app));
+
+ gntp_write (output, "GNTP/1.0 REGISTER NONE");
+ gntp_write (output, app_str);
+ /* TODO: Application icon */
+ gntp_write (output, "Notifications-Count: 1");
+ gntp_write (output, "");
+ gntp_write (output, "Notification-Name: Notification");
+ gntp_write (output, "Notification-Enabled: True");
+ gntp_write (output, "");
+
+ g_output_stream_close (output, NULL, NULL);
+
+ /* Verify registration was OK */
+ input = g_io_stream_get_input_stream (G_IO_STREAM(conn));
+ g_input_stream_read (input, input_buf, sizeof(input_buf), NULL, &error);
+ if (error == NULL && g_strstr_len (input_buf, sizeof(input_buf), "-OK") != NULL)
+ {
+ backend->is_registered = TRUE;
+
+ /* Send any queued up notifications */
+ while (backend->notifications)
+ {
+ GGNTPNotification *notification = backend->notifications->data;
+
+ gntp_notify (notification); /* Notification is freed in the callback */
+
+ backend->notifications = g_slist_next (backend->notifications);
+ }
+ }
+ else
+ {
+ g_warning ("GNTP registration failed.");
+ g_warning (error->message);
+ g_error_free (error);
+ }
+
+ g_input_stream_close (input, NULL, NULL);
+ g_object_unref (source);
+}
+
+/* TODO: The default action can be supported but it requires
+ * running a server for the callback to connect to. */
+
+static void
+g_gntp_notification_backend_dispose (GObject *object)
+{
+ GGNTPNotificationBackend *backend = G_GNTP_NOTIFICATION_BACKEND (object);
+
+ if (backend->notifications)
+ g_slist_free_full (backend->notifications, gntp_notification_free);
+
+ G_OBJECT_CLASS (g_gntp_notification_backend_parent_class)->dispose (object);
+}
+
+static gboolean
+g_gntp_notification_backend_is_supported (void)
+{
+ /* To avoid an unnecessary synchronous call to check for
+ * the growl daemon, this function always succeeds. A
+ * warning will be printed when sending the first notification fails.
+ */
+ return TRUE;
+}
+
+static void
+g_gntp_notification_backend_send_notification (GNotificationBackend *backend,
+ const gchar *id,
+ GNotification *notification)
+{
+ GGNTPNotificationBackend *self = G_GNTP_NOTIFICATION_BACKEND(backend);
+ GGNTPNotification *gntp_notification = gntp_notification_new (self, notification);
+ GSocketClient *client;
+
+ if (!self->is_registered)
+ {
+ /* Queue for later */
+ self->notifications = g_slist_append (self->notifications, gntp_notification);
+
+ if (!self->is_registering)
+ {
+ self->is_registering = TRUE;
+ client = g_socket_client_new ();
+ g_socket_client_connect_to_host_async (client, GNTP_HOST, GNTP_PORT, NULL,
+ gntp_register_callback, self);
+ }
+ }
+ else
+ {
+ gntp_notify (gntp_notification);
+ }
+}
+
+static void
+g_gntp_notification_backend_withdraw_notification (GNotificationBackend *backend,
+ const gchar *id)
+{
+ /* Not implemented */
+}
+
+static void
+g_gntp_notification_backend_init (GGNTPNotificationBackend *backend)
+{
+ backend->is_registered = FALSE;
+ backend->is_registering = FALSE;
+ backend->notifications = NULL;
+}
+
+static void
+g_gntp_notification_backend_class_init (GGNTPNotificationBackendClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GNotificationBackendClass *backend_class = G_NOTIFICATION_BACKEND_CLASS (class);
+
+ object_class->dispose = g_gntp_notification_backend_dispose;
+
+ backend_class->is_supported = g_gntp_notification_backend_is_supported;
+ backend_class->send_notification = g_gntp_notification_backend_send_notification;
+ backend_class->withdraw_notification = g_gntp_notification_backend_withdraw_notification;
+}
diff --git a/gio/giomodule.c b/gio/giomodule.c
index 18b7933..86e38f8 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -897,6 +897,7 @@ extern GType _g_network_monitor_netlink_get_type (void);
extern GType g_fdo_notification_backend_get_type (void);
extern GType g_gtk_notification_backend_get_type (void);
#endif
+extern GType g_gntp_notification_backend_get_type (void);
#ifdef G_PLATFORM_WIN32
@@ -1080,6 +1081,7 @@ _g_io_modules_ensure_loaded (void)
g_type_ensure (g_fdo_notification_backend_get_type ());
g_type_ensure (g_gtk_notification_backend_get_type ());
#endif
+ g_type_ensure (g_gntp_notification_backend_get_type ());
#ifdef G_OS_WIN32
g_type_ensure (_g_winhttp_vfs_get_type ());
#endif
--
1.8.5.2 (Apple Git-48)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment