diff main.c @ 0:52694b49dcc4

Initial revision
author Guido Berhoerster <guido+xwrited@berhoerster.name>
date Sun, 27 Apr 2014 23:07:51 +0200
parents
children 0907cc7064d4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2011 Guido Berhoerster <guido+xwrited@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#define	_XOPEN_SOURCE	600
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <errno.h>
+#include <locale.h>
+#include <libintl.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <libnotify/notify.h>
+#include "xwrited-debug.h"
+#include "xwrited-unique.h"
+#include "xwrited-utmp.h"
+
+enum {
+	PIPE_R_FD = 0,
+	PIPE_W_FD
+};
+
+static int	signal_pipe_fd[2] = { -1, -1 };
+
+static void
+on_signal(int signo)
+{
+	int		old_errno = errno;
+	ssize_t		n;
+	sigset_t	sigset;
+
+	/* try to read unread signals from the pipe and add the new one to it */
+	n = read(signal_pipe_fd[PIPE_R_FD], &sigset, sizeof (sigset));
+	if ((n == -1) || ((size_t)n < sizeof (sigset))) {
+		sigemptyset(&sigset);
+	}
+	sigaddset(&sigset, signo);
+	write(signal_pipe_fd[PIPE_W_FD], &sigset, sizeof (sigset));
+
+	errno = old_errno;
+}
+
+static gboolean
+signal_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data)
+{
+	GMainLoop	*loop = (GMainLoop *)user_data;
+	sigset_t	sigset;
+	sigset_t	old_sigset;
+	GIOStatus	status;
+	gsize		n;
+	GError		*error = NULL;
+
+	/*
+	 * deal with pending signals previously received in the signal handler,
+	 * try to read a sigset from the pipe, avoid partial reads by blocking
+	 * all signals during the read operation
+	 */
+	sigfillset(&sigset);
+	sigprocmask(SIG_BLOCK, &sigset, &old_sigset);
+	status = g_io_channel_read_chars(source, (gchar *)&sigset,
+	    sizeof (sigset), &n, &error);
+	sigprocmask(SIG_SETMASK, &old_sigset, NULL);
+	if (status != G_IO_STATUS_NORMAL) {
+		if (status != G_IO_STATUS_AGAIN) {
+			if (error != NULL) {
+				g_critical("failed to read from signal pipe: "
+				    "%s", error->message);
+				g_error_free(error);
+				g_main_loop_quit(loop);
+			} else {
+				g_critical("failed to read from signal pipe");
+				g_main_loop_quit(loop);
+			}
+		}
+	} else if (n != sizeof (sigset)) {
+		g_critical("short read from signal pipe");
+		g_main_loop_quit(loop);
+	} else {
+		if ((sigismember(&sigset, SIGINT) == 1) ||
+		    (sigismember(&sigset, SIGTERM) == 1) ||
+		    (sigismember(&sigset, SIGQUIT) == 1) ||
+		    (sigismember(&sigset, SIGHUP) == 1)) {
+			g_debug("received signal, exiting");
+			g_main_loop_quit(loop);
+		}
+	}
+
+	return (TRUE);
+}
+
+static gboolean
+send_notification(GString *raw_str, GMainLoop *loop)
+{
+	gboolean	retval = FALSE;
+	GString		*utf8_str = NULL;
+	gchar		*startp = raw_str->str;
+	gchar		*endp;
+	GRegex		*regex = NULL;
+	GError		*error = NULL;
+	gchar		*body = NULL;
+	GList		*capabilities = NULL;
+	gchar		*tmp;
+	NotifyNotification *notification = NULL;
+
+	utf8_str = g_string_sized_new(raw_str->len);
+	while (!g_utf8_validate(startp, raw_str->str + raw_str->len -
+	    startp, (const gchar **)&endp)) {
+		g_string_append_len(utf8_str, startp, endp - startp);
+		/*
+		 * replace each byte that does not belong to a UTF-8-encoded
+		 * character with the Unicode REPLACEMENT CHARACTER (U+FFFD)
+		 */
+		g_string_append(utf8_str, "\357\277\275");
+
+		startp = endp + ((endp < raw_str->str + raw_str->len) ? 1 : 0);
+	}
+	g_string_append_len(utf8_str, startp, raw_str->str + raw_str->len -
+	    startp);
+
+	/* remove any CR, BEL and trailing space and tabs */
+	regex = g_regex_new("([\r\a]+|[ \t\r\a]+$)", G_REGEX_MULTILINE, 0,
+	    &error);
+	if (error != NULL) {
+		goto out;
+	}
+	body = g_regex_replace_literal(regex, utf8_str->str, -1, 0, "", 0,
+	    &error);
+	if (error != NULL) {
+		goto out;
+	}
+
+	/*
+	 * skip empty messages or messages only consisting of whitespace and
+	 * control characters
+	 */
+	if ((strlen(body) == 0) ||
+	    !g_regex_match_simple("[^[:space:][:cntrl:]]", body, 0, 0)) {
+		goto out;
+	}
+
+	/*
+	 * if the notification daemon supports markup the message needs to be
+	 * escaped
+	 */
+	capabilities = notify_get_server_caps();
+	if (g_list_find_custom(capabilities, "body-markup",
+	    (GCompareFunc)strcmp) != NULL) {
+		tmp = g_markup_escape_text(body, -1);
+		g_free(body);
+		body = tmp;
+	}
+
+	/* show notification */
+	notification = notify_notification_new(_("Message received"),
+	    body, "utilities-terminal"
+#if !defined(NOTIFY_VERSION_MINOR) || \
+    (NOTIFY_VERSION_MAJOR == 0 && NOTIFY_VERSION_MINOR < 7)
+	    , NULL
+#endif
+	    );
+	if (notification == NULL) {
+		g_critical("failed to create a notification object");
+		g_main_loop_quit(loop);
+		goto out;
+	}
+	notify_notification_set_timeout(notification, NOTIFY_EXPIRES_NEVER);
+	retval = notify_notification_show(notification, NULL);
+
+out:
+	if (notification != NULL) {
+		g_object_unref(G_OBJECT(notification));
+	}
+	if (capabilities != NULL) {
+		g_list_free_full(capabilities, g_free);
+	}
+	g_free(body);
+	if (regex != NULL) {
+		g_regex_unref(regex);
+	}
+	if (utf8_str != NULL) {
+		g_string_free(utf8_str, TRUE);
+	}
+
+	return (retval);
+}
+
+static gboolean
+master_pty_read_cb(GIOChannel *source, GIOCondition cond,
+    gpointer user_data)
+{
+	GMainLoop	*loop = (GMainLoop *)user_data;
+	gchar		buf[BUFSIZ];
+	GString		*raw_str = NULL;
+	GIOStatus	status;
+	gsize		buf_len;
+	GError		*error = NULL;
+
+	if ((cond & G_IO_IN) || (cond & G_IO_PRI)) {
+		raw_str = g_string_sized_new(BUFSIZ);
+		/* read message from master pty */
+		while ((status = g_io_channel_read_chars(source, buf, BUFSIZ,
+		    &buf_len, &error)) == G_IO_STATUS_NORMAL) {
+			if (buf_len > 0) {
+				g_debug("read %" G_GSSIZE_FORMAT " bytes from "
+				    "master pty", buf_len);
+				g_string_append_len(raw_str, buf,
+				    (gssize)buf_len);
+			}
+		}
+		if (error != NULL) {
+			g_critical("failed to read from master pty: %s",
+			    error->message);
+			g_error_free(error);
+			g_main_loop_quit(loop);
+			goto out;
+		}
+
+		if (!send_notification(raw_str, loop)) {
+			g_warning("failed to send notification");
+		}
+	}
+
+	if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) {
+		g_critical("connection to master pty broken");
+		g_main_loop_quit(loop);
+			goto out;
+	}
+
+out:
+	if (raw_str != NULL) {
+		g_string_free(raw_str, TRUE);
+	}
+
+	return (TRUE);
+}
+
+int
+main(int argc, char *argv[])
+{
+	int		status = EXIT_FAILURE;
+	GMainLoop	*loop = NULL;
+	GError		*error = NULL;
+	XWritedUnique	*app = NULL;
+	GOptionContext	*context = NULL;
+	struct sigaction sigact;
+	GIOChannel	*signal_channel = NULL;
+	GIOChannel	*master_pty_channel = NULL;
+	int		masterfd = -1;
+	int		slavefd = -1;
+	char		*slave_name = NULL;
+	gboolean	vflag = FALSE;
+	gboolean	dflag = FALSE;
+	const GOptionEntry options[] = {
+	    { "debug", 'd', 0, G_OPTION_ARG_NONE, &dflag,
+	    N_("Show extra debugging information"), NULL },
+	    { "version", 'V', 0, G_OPTION_ARG_NONE, &vflag,
+	    N_("Print the current version and exit"), NULL },
+	    { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 }
+	};
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset(PACKAGE, "UTF-8");
+	textdomain(PACKAGE);
+
+	g_type_init();
+
+	context = g_option_context_new("- display write and wall messages as "
+	    "desktop notifications");
+	g_option_context_add_main_entries(context, options, PACKAGE);
+	g_option_context_set_translation_domain(context, PACKAGE);
+	g_option_context_parse(context, &argc, &argv, &error);
+	if (error != NULL) {
+		g_printerr("%s.\n", error->message);
+		g_error_free(error);
+		goto out;
+	}
+
+	xwrited_debug_init(dflag);
+
+	if (vflag) {
+		g_print("%s %s\n", PACKAGE, VERSION);
+		status = EXIT_SUCCESS;
+		goto out;
+	}
+
+	app = xwrited_unique_new("org.guido-berhoerster.code.xwrited");
+	if (app == NULL) {
+		g_critical("failed to initialize application");
+		goto out;
+	}
+	if (!xwrited_unique_is_unique(app)) {
+		g_printerr(_("xwrited is already running in this session.\n"));
+		goto out;
+	}
+
+	if (!notify_init(APP_NAME)) {
+		g_critical("failed to initialize libnotify");
+		goto out;
+	}
+
+	loop = g_main_loop_new(NULL, FALSE);
+	if (loop == NULL) {
+		g_critical("failed to create main loop");
+		goto out;
+	}
+
+	/* open master pty */
+	masterfd = posix_openpt(O_RDWR | O_NOCTTY);
+	if (masterfd == -1) {
+		g_critical("failed to open master pty: %s", g_strerror(errno));
+		goto out;
+	}
+
+	/* create slave pty */
+	if ((grantpt(masterfd) == -1) || (unlockpt(masterfd) == -1)) {
+		g_critical("failed to create slave pty: %s", g_strerror(errno));
+		goto out;
+	}
+	slave_name = ptsname(masterfd);
+	if (slave_name == NULL) {
+		g_critical("failed to obtain name of slave pty");
+		goto out;
+	}
+
+	/*
+	 * keep an open fd around order to prevent closing the master fd when
+	 * receiving an EOF
+	 */
+	slavefd = open(slave_name, O_RDWR);
+	if (slavefd == -1) {
+		g_critical("failed to open slave pty: %s", g_strerror(errno));
+		goto out;
+	}
+
+	/* create a GIOChannel for monitoring the master pty for messages */
+	master_pty_channel = g_io_channel_unix_new(masterfd);
+	g_io_channel_set_flags(master_pty_channel,
+	    g_io_channel_get_flags(master_pty_channel) | G_IO_FLAG_NONBLOCK,
+	    &error);
+	if (error != NULL) {
+		g_critical("failed set flags on the master pty channel: %s",
+		    error->message);
+		g_error_free(error);
+		goto out;
+	}
+	if (!g_io_add_watch(master_pty_channel, G_IO_IN | G_IO_PRI | G_IO_HUP |
+	    G_IO_ERR, master_pty_read_cb, loop)) {
+		g_critical("failed to add watch on signal channel");
+		goto out;
+	}
+
+	/* create pipe for delivering signals to a listener in the main loop */
+	if (pipe(signal_pipe_fd) == -1) {
+		g_critical("failed to create signal pipe: %s",
+		    g_strerror(errno));
+		goto out;
+	}
+	if (fcntl(signal_pipe_fd[PIPE_W_FD], F_SETFL, O_NONBLOCK) == -1) {
+		g_critical("failed to set flags on signal pipe: %s",
+		    g_strerror(errno));
+		goto out;
+	}
+
+	/* create GIO channel for reading from the signal_pipe */
+	signal_channel = g_io_channel_unix_new(signal_pipe_fd[PIPE_R_FD]);
+	g_io_channel_set_encoding(signal_channel, NULL, &error);
+	if (error != NULL) {
+		g_critical("failed to set binary encoding for signal channel: "
+		    "%s", error->message);
+		g_error_free(error);
+		goto out;
+	}
+	g_io_channel_set_buffered(signal_channel, FALSE);
+	g_io_channel_set_flags(signal_channel,
+	    g_io_channel_get_flags(signal_channel) | G_IO_FLAG_NONBLOCK,
+	    &error);
+	if (error != NULL) {
+		g_critical("failed set flags on signal channel: %s",
+		    error->message);
+		g_error_free(error);
+		goto out;
+	}
+	if (g_io_add_watch(signal_channel, G_IO_IN | G_IO_PRI | G_IO_HUP |
+	    G_IO_ERR, signal_read_cb, loop) == 0) {
+		g_critical("failed to add watch on the signal channel");
+		goto out;
+	}
+
+	/* set up signal handler */
+	sigact.sa_handler = on_signal;
+	sigact.sa_flags = SA_RESTART;
+	sigemptyset(&sigact.sa_mask);
+	if ((sigaction(SIGINT, &sigact, NULL) < 0) ||
+	    (sigaction(SIGTERM, &sigact, NULL) < 0) ||
+	    (sigaction(SIGQUIT, &sigact, NULL) < 0) ||
+	    (sigaction(SIGHUP, &sigact, NULL) < 0)) {
+		g_critical("failed to set up signal handler");
+		goto out;
+	}
+
+	xwrited_utmp_add_entry(masterfd);
+
+	/* main loop */
+	g_main_loop_run(loop);
+
+	xwrited_utmp_remove_entry(masterfd);
+
+	status = EXIT_SUCCESS;
+
+out:
+	if (context != NULL) {
+		g_option_context_free(context);
+	}
+
+	if (signal_channel != NULL) {
+		g_io_channel_shutdown(signal_channel, FALSE, NULL);
+		g_io_channel_unref(signal_channel);
+	}
+
+	if (signal_pipe_fd[PIPE_R_FD] != -1) {
+		close(signal_pipe_fd[PIPE_R_FD]);
+	}
+	if (signal_pipe_fd[PIPE_W_FD] != -1) {
+		close(signal_pipe_fd[PIPE_W_FD]);
+	}
+
+	if (master_pty_channel != NULL) {
+		g_io_channel_shutdown(master_pty_channel, FALSE, NULL);
+		g_io_channel_unref(master_pty_channel);
+	}
+
+	if (slavefd != -1) {
+		close(slavefd);
+	}
+
+	if (masterfd != -1) {
+		close(masterfd);
+	}
+
+	if (app != NULL) {
+		g_object_unref(app);
+	}
+
+	if (loop != NULL) {
+		g_main_loop_unref(loop);
+	}
+
+	if (notify_is_initted()) {
+		notify_uninit();
+	}
+
+	exit(status);
+}