changeset 0:52694b49dcc4

Initial revision
author Guido Berhoerster <guido+xwrited@berhoerster.name>
date Sun, 27 Apr 2014 23:07:51 +0200
parents
children 0907cc7064d4
files Makefile deps.sed main.c po/POTFILES.in po/de.po xwrited-debug.c xwrited-debug.h xwrited-unique.c xwrited-unique.h xwrited-utmp-utempter.c xwrited-utmp-utmpx.c xwrited-utmp.h xwrited.desktop.in
diffstat 13 files changed, 1252 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,135 @@
+#
+# 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.
+#
+
+PACKAGE =	xwrited
+APP_NAME =	org.guido-berhoerster.code.xwrited
+VERSION =	1
+DISTNAME :=	$(PACKAGE)-$(VERSION)
+
+# gcc, clang, icc
+MAKEDEPEND.c =	$(CC) -MM $(CFLAGS) $(CPPFLAGS)
+# Sun/Solaris Studio
+#MAKEDEPEND.c =	$(CC) -xM1 $(CFLAGS) $(CPPFLAGS)
+# X makedepend
+#MAKEDEPEND.c =	makedepend -f- -Y -- $(CFLAGS) $(CPPFLAGS) --
+INSTALL :=	install
+INSTALL.exec :=	$(INSTALL) -D -m 0755
+INSTALL.data :=	$(INSTALL) -D -m 0644
+PAX :=		pax
+GZIP :=		gzip
+SED :=		sed
+PASTE :=	paste
+MSGFMT :=	msgfmt
+INTLTOOL_UPDATE := intltool-update
+INTLTOOL_MERGE := intltool-merge
+
+DESTDIR ?=
+prefix ?=	/usr/local
+bindir ?=	$(prefix)/bin
+datadir ?=	$(prefix)/share
+localedir ?=	$(datadir)/locale
+sysconfdir ?=	/etc
+xdgautostartdir ?= $(sysconfdir)/xdg/autostart
+
+OS_NAME :=	$(shell uname -s)
+
+PKGCONFIG_LIBS :=	dbus-1 glib-2.0 dbus-glib-1 libnotify
+EXTRA_LIBS :=
+
+ifeq ($(OS_NAME),Linux)
+    OBJS_UTMP :=	xwrited-utmp-utempter.o
+    EXTRA_LIBS +=	-lutempter
+else ifeq ($(OS_NAME),FreeBSD)
+    OBJS_UTMP :=	xwrited-utmp-utempter.o
+    EXTRA_LIBS +=	-lutempter
+else
+    OBJS_UTMP :=	xwrited-utmp-utmpx.o
+endif
+
+OBJS =		main.o xwrited-debug.o xwrited-unique.o $(OBJS_UTMP)
+AUTOSTART_FILE = $(PACKAGE).desktop
+MOFILES :=	$(patsubst %.po,%.mo,$(wildcard po/*.po))
+POTFILE =	po/$(PACKAGE).pot
+POSRCS :=	$(shell $(SED) -e 's/\#.*//' -e '/^[ \t]*$$/d' \
+		-e 's/^\[[^]]*\]//' po/POTFILES.in | $(PASTE) -s -d ' ')
+CPPFLAGS := 	$(CPPFLAGS_EXTRA) \
+		$(CPPFLAGS_LIBUTEMPTER) \
+		$(shell pkg-config --cflags $(PKGCONFIG_LIBS)) \
+		-DPACKAGE="\"$(PACKAGE)\"" \
+		-DAPP_NAME=\"$(APP_NAME)\" \
+		-DVERSION=\"$(VERSION)\" \
+		-DLOCALEDIR="\"$(localedir)\"" \
+		-DG_LOG_DOMAIN=\"$(PACKAGE)\"
+LDLIBS :=	$(EXTRA_LIBS) \
+		$(shell pkg-config --libs $(PKGCONFIG_LIBS))
+
+.DEFAULT_TARGET = all
+
+.PHONY: all clean clobber dist install
+
+all: $(PACKAGE) $(MOFILES) $(AUTOSTART_FILE)
+
+$(PACKAGE): $(OBJS)
+	$(LINK.o) $^ $(LDLIBS) -o $@
+
+$(POTFILE): po/POTFILES.in $(POSRCS)
+	cd po/ && $(INTLTOOL_UPDATE) --pot --gettext-package="$(PACKAGE)"
+
+pot: $(POTFILE)
+
+update-po: $(POTFILE)
+	cd po/ && for lang in $(patsubst po/%.mo,%,$(MOFILES)); do \
+	    $(INTLTOOL_UPDATE) --dist --gettext-package="$(PACKAGE)" \
+	    $${lang}; \
+	done
+
+%.o: %.c
+	$(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d
+	$(COMPILE.c) -o $@ $<
+
+%.desktop: %.desktop.in $(MOFILES)
+	$(INTLTOOL_MERGE) --desktop-style --utf8 po $< $@
+
+%.mo: %.po
+	$(MSGFMT) -o $@ $<
+
+install:
+	$(INSTALL.exec) $(PACKAGE) "$(DESTDIR)$(bindir)/$(PACKAGE)"
+	for lang in $(patsubst po/%.mo,%,$(MOFILES)); do \
+	    $(INSTALL.data) po/$${lang}.mo \
+	        "$(DESTDIR)$(localedir)/$${lang}/LC_MESSAGES/$(PACKAGE).mo"; \
+	done
+	$(INSTALL.data) $(AUTOSTART_FILE) \
+	        "$(DESTDIR)$(xdgautostartdir)/$(notdir $(AUTOSTART_FILE))"
+
+clean:
+	rm -f $(PACKAGE) $(OBJS) $(POTFILE) $(MOFILES) $(AUTOSTART_FILE)
+
+clobber: clean
+	rm -f $(patsubst %.o,%.d,$(OBJS))
+
+dist: clobber
+	$(PAX) -w -x ustar -s ',.*/\..*,,' -s ',./[^/]*\.tar\.gz,,' \
+	    -s ',\./,$(DISTNAME)/,' . | $(GZIP) > $(DISTNAME).tar.gz
+
+-include $(patsubst %.o,%.d,$(OBJS))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deps.sed	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,26 @@
+/^[^:]\{1,\}:.*\\$/{
+    h
+    s/\([^:]\{1,\}:\).*/\1/
+    x
+    s/[^:]\{1,\}://
+}
+/\\$/,/^$/bgen
+/\\$/,/[^\\]$/{
+:gen
+    s/[[:blank:]]*\\$//
+    s/^[[:blank:]]*//
+    G
+    s/\(.*\)\n\(.*\)/\2 \1/
+}
+/^[^:]\{1,\}:[[:blank:]]*$/d
+/^[^:]\{1,\}\.o:/{
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC][[:blank:]]*/ /g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC]$//g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc[[:blank:]]*/ /g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc$//g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp[[:blank:]]*/ /g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp$//g
+    /^[^:]\{1,\}:[[:blank:]]*$/d
+    s/^\([^:]\{1,\}\)\.o[[:blank:]]*:[[:blank:]]*\(.*\)/\1.d: $(wildcard \2)\
+&/
+}
--- /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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/POTFILES.in	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,6 @@
+xwrited.desktop.in
+main.c
+xwrited-debug.c
+xwrited-unique.c
+xwrited-utmp-utempter.c
+xwrited-utmp-utmpx.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/de.po	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,45 @@
+# German translations for xwrited package
+# German messages for xwrited.
+# Copyright (C) 2011 Guido Berhoerster.
+# This file is distributed under the same license as the xwrited package.
+# Guido Berhoerster <guido+xwrited@berhoerster.name>, 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: xwrited 1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-05-20 16:48+0200\n"
+"PO-Revision-Date: 2010-07-28 13:00+0200\n"
+"Last-Translator: Guido Berhoerster <guido+xwrited@berhoerster.name>\n"
+"Language-Team: German\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../xwrited.desktop.in.h:1
+msgid "xwrited"
+msgstr "xwrited"
+
+#: ../xwrited.desktop.in.h:2
+msgid "Display write and wall messages as desktop notifications"
+msgstr "Zeigt write und wall Nachrichten as Desktop-Benachrichtigungen an"
+
+#. show notification
+#: ../main.c:179
+msgid "Message received"
+msgstr "Nachricht erhalten"
+
+#: ../main.c:280
+msgid "Show extra debugging information"
+msgstr "Zusätzliche Debugging-Informationen anzeigen"
+
+#: ../main.c:282
+msgid "Print the current version and exit"
+msgstr "Aktuelle Version zeigen und beenden"
+
+#: ../main.c:321
+#, c-format
+msgid "xwrited is already running in this session.\n"
+msgstr "xwrited läuft bereits in dieser Sitzung.\n"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-debug.c	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,46 @@
+/*
+ * 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 <string.h>
+#include <stdarg.h>
+#include <glib.h>
+
+#include "xwrited-debug.h"
+
+static void
+dummy_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
+    const gchar *message, gpointer data)
+{
+	/* drop all messages */
+}
+
+void
+xwrited_debug_init(gboolean debug_mode)
+{
+	if (!debug_mode) {
+		g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
+		    dummy_log_handler, NULL);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-debug.h	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#ifndef	XWRITED_DEBUG_H
+#define	XWRITED_DEBUG_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void	xwrited_debug_init(gboolean);
+
+G_END_DECLS
+
+#endif /* XWRITED_DEBUG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-unique.c	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,236 @@
+/*
+ * 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 <glib.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus.h>
+
+#include "xwrited-unique.h"
+
+G_DEFINE_TYPE(XWritedUnique, xwrited_unique, G_TYPE_OBJECT)
+
+#define	XWRITED_UNIQUE_GET_PRIVATE(obj)	(G_TYPE_INSTANCE_GET_PRIVATE((obj), \
+    XWRITED_TYPE_UNIQUE, XWritedUniquePrivate))
+
+struct	_XWritedUniquePrivate {
+	DBusGConnection	*session_bus;
+	DBusGProxy	*session_bus_proxy;
+	gchar		*name;
+	gboolean	is_unique;
+};
+
+enum {
+	PROP_0,
+	PROP_NAME,
+	PROP_IS_XWRITED_UNIQUE
+};
+
+static gboolean
+request_name(XWritedUnique *self)
+{
+	guint32	request_name_response;
+	GError	*error = NULL;
+
+	g_return_val_if_fail(self->priv->session_bus != NULL, FALSE);
+	g_return_val_if_fail(self->priv->session_bus_proxy != NULL, FALSE);
+
+	if (dbus_g_proxy_call(self->priv->session_bus_proxy, "RequestName",
+	    &error, G_TYPE_STRING, self->priv->name, G_TYPE_UINT,
+	    DBUS_NAME_FLAG_DO_NOT_QUEUE, G_TYPE_INVALID, G_TYPE_UINT,
+	    &request_name_response, G_TYPE_INVALID) == 0) {
+		g_warning("failed to acquire service name \"%s\": %s",
+		    self->priv->name, error->message);
+		g_error_free(error);
+		return (FALSE);
+	}
+
+	switch (request_name_response) {
+	case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
+		return (TRUE);
+	case DBUS_REQUEST_NAME_REPLY_EXISTS:
+		break;
+	default:
+		g_warning("failed to acquire service name \"%s\"",
+		    self->priv->name);
+	}
+
+	return (FALSE);
+}
+
+static void
+xwrited_unique_get_property(GObject *gobject, guint property_id, GValue *value,
+    GParamSpec *pspec)
+{
+	XWritedUnique	*app = XWRITED_UNIQUE(gobject);
+
+	switch (property_id) {
+	case PROP_NAME:
+		g_value_set_string(value, app->priv->name);
+		break;
+	case PROP_IS_XWRITED_UNIQUE:
+		g_value_set_boolean(value, app->priv->is_unique);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id,
+		    pspec);
+	}
+}
+
+static void
+xwrited_unique_set_property(GObject *gobject, guint property_id,
+    const GValue *value, GParamSpec *pspec)
+{
+	XWritedUnique	*app = XWRITED_UNIQUE(gobject);
+
+	switch (property_id) {
+	case PROP_NAME:
+		app->priv->name = g_strdup(g_value_get_string(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id,
+		    pspec);
+	}
+}
+
+static GObject *
+xwrited_unique_constructor(GType gtype, guint n_params,
+    GObjectConstructParam *params)
+{
+	GObjectClass	*parent_class;
+	GObject		*gobject;
+	XWritedUnique	*app;
+	GError		*error = NULL;
+
+	parent_class = G_OBJECT_CLASS(xwrited_unique_parent_class);
+	gobject = parent_class->constructor(gtype, n_params, params);
+	app = XWRITED_UNIQUE(gobject);
+
+	app->priv->session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (app->priv->session_bus == NULL) {
+		g_warning("failed to connect to DBus session bus: %s",
+		    error->message);
+		g_error_free(error);
+		goto out;
+	}
+
+	app->priv->session_bus_proxy =
+	    dbus_g_proxy_new_for_name(app->priv->session_bus,
+	    DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+	if (app->priv->session_bus_proxy == NULL) {
+		g_warning("failed to create DBus proxy");
+		goto out;
+	}
+
+	if (request_name(app)) {
+		app->priv->is_unique = TRUE;
+	}
+
+out:
+	return (gobject);
+}
+
+static void
+xwrited_unique_dispose(GObject *gobject)
+{
+	XWritedUnique	*self = XWRITED_UNIQUE(gobject);
+
+	if (self->priv->session_bus_proxy != NULL) {
+		g_object_unref(self->priv->session_bus_proxy);
+		self->priv->session_bus_proxy = NULL;
+	}
+
+	G_OBJECT_CLASS(xwrited_unique_parent_class)->dispose(gobject);
+}
+
+static void
+xwrited_unique_finalize(GObject *gobject)
+{
+	XWritedUnique	*self = XWRITED_UNIQUE(gobject);
+
+	g_free(self->priv->name);
+
+	G_OBJECT_CLASS(xwrited_unique_parent_class)->finalize(gobject);
+}
+
+static void
+xwrited_unique_class_init(XWritedUniqueClass *klass)
+{
+	GObjectClass	*gobject_class = G_OBJECT_CLASS(klass);
+	GParamSpec	*pspec;
+
+	gobject_class->constructor = xwrited_unique_constructor;
+	gobject_class->get_property = xwrited_unique_get_property;
+	gobject_class->set_property = xwrited_unique_set_property;
+	gobject_class->dispose = xwrited_unique_dispose;
+	gobject_class->finalize = xwrited_unique_finalize;
+
+	pspec = g_param_spec_string("name", "Name",
+	    "The unique name of the application", NULL, G_PARAM_READABLE |
+	    G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME |
+	    G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+	g_object_class_install_property(gobject_class, PROP_NAME, pspec);
+
+	pspec = g_param_spec_boolean("is-unique", "Is unique",
+	    "Whether the current application instance is unique", FALSE,
+	    G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+	    G_PARAM_STATIC_BLURB);
+	g_object_class_install_property(gobject_class, PROP_IS_XWRITED_UNIQUE,
+	    pspec);
+
+	g_type_class_add_private(klass, sizeof (XWritedUniquePrivate));
+}
+
+static void
+xwrited_unique_init(XWritedUnique *self)
+{
+	self->priv = XWRITED_UNIQUE_GET_PRIVATE(self);
+
+	self->priv->is_unique = FALSE;
+	self->priv->session_bus_proxy = NULL;
+}
+
+XWritedUnique *
+xwrited_unique_new(const gchar *name)
+{
+	XWritedUnique	*app;
+
+	g_return_val_if_fail(name != NULL, NULL);
+
+	app = g_object_new(XWRITED_TYPE_UNIQUE, "name", name, NULL);
+	if (app->priv->session_bus_proxy == NULL) {
+		g_object_unref(app);
+		return (NULL);
+	}
+
+	return (app);
+}
+
+gboolean
+xwrited_unique_is_unique(XWritedUnique *self)
+{
+	g_return_val_if_fail(XWRITED_IS_UNIQUE(self), FALSE);
+
+	return (self->priv->is_unique);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-unique.h	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef	XWRITED_UNIQUE_H
+#define	XWRITED_UNIQUE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define	XWRITED_TYPE_UNIQUE		(xwrited_unique_get_type())
+#define	XWRITED_UNIQUE(obj)		(G_TYPE_CHECK_INSTANCE_CAST((obj), \
+    XWRITED_TYPE_UNIQUE, XWritedUnique))
+#define	XWRITED_IS_UNIQUE(obj)		(G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+    XWRITED_TYPE_UNIQUE))
+#define	XWRITED_UNIQUE_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST((klass), \
+    XWRITED_TYPE_UNIQUE, XWritedUniqueClass))
+#define	XWRITED_IS_UNIQUE_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), \
+    XWRITED_TYPE_UNIQUE))
+#define	XWRITED_UNIQUE_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), \
+    XWRITED_TYPE_UNIQUE, XWritedUniqueClass))
+
+typedef struct _XWritedUnique		XWritedUnique;
+typedef struct _XWritedUniqueClass	XWritedUniqueClass;
+typedef struct _XWritedUniquePrivate	XWritedUniquePrivate;
+
+struct _XWritedUnique {
+	GObject		parent_instance;
+	XWritedUniquePrivate *priv;
+};
+
+struct _XWritedUniqueClass {
+	GObjectClass	parent_class;
+};
+
+gboolean	xwrited_unique_is_unique(XWritedUnique *);
+XWritedUnique *	xwrited_unique_new(const gchar *);
+
+G_END_DECLS
+
+#endif /* XWRITED_UNIQUE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-utmp-utempter.c	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 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 <stdlib.h>
+#include <utempter.h>
+
+void
+xwrited_utmp_add_entry(int fd)
+{
+	char	*pty_name;
+
+	pty_name = ptsname(fd);
+	addToUtmp(pty_name, NULL, fd);
+}
+
+void
+xwrited_utmp_remove_entry(int fd)
+{
+	char	*pty_name;
+
+	pty_name = ptsname(fd);
+	removeLineFromUtmp(pty_name, fd);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-utmp-utmpx.c	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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 <glib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <utmpx.h>
+#include <errno.h>
+#include <sys/time.h>
+
+#define	DEV_PREFIX	"/dev/"
+
+static void
+utmp_write_entry(int fd, gboolean add)
+{
+	struct utmpx	utmpx;
+	char		*line = NULL;
+	size_t		line_len;
+	char		*id;
+	struct passwd	*pwd;
+
+	line = ptsname(fd);
+	if (line == NULL) {
+		g_critical("failed to obtain slave pty name");
+		return;
+	}
+	if (g_str_has_prefix(line, DEV_PREFIX)) {
+		line += strlen(DEV_PREFIX);
+	}
+
+	line_len = strlen(line);
+	id = (line_len >= sizeof (utmpx.ut_pid)) ?
+	    line + (line_len - sizeof (utmpx.ut_pid)) :
+	    line;
+
+	pwd = getpwuid(getuid());
+	if (pwd == NULL) {
+		g_critical("failed to get username: %s", g_strerror(errno));
+		return;
+	}
+
+	memset(&utmpx, 0, sizeof (utmpx));
+	strncpy(utmpx.ut_name, pwd->pw_name, sizeof (utmpx.ut_name));
+	strncpy(utmpx.ut_id, id, sizeof (utmpx.ut_id));
+	strncpy(utmpx.ut_line, line, sizeof (utmpx.ut_line));
+	utmpx.ut_pid = getpid();
+	utmpx.ut_type = add ? USER_PROCESS : DEAD_PROCESS;
+	gettimeofday(&utmpx.ut_tv, NULL);
+
+	setutxent();
+	if (pututxline(&utmpx) == NULL) {
+		g_critical("failed to write to utmpx database: %s",
+		    g_strerror(errno));
+		return;
+	}
+	endutxent();
+}
+
+void
+xwrited_utmp_add_entry(int fd)
+{
+	utmp_write_entry(fd, TRUE);
+}
+
+void
+xwrited_utmp_remove_entry(int fd)
+{
+	utmp_write_entry(fd, FALSE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited-utmp.h	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef	XWRITED_UTMP_H
+#define	XWRITED_UTMP_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void	xwrited_utmp_add_entry(int);
+void	xwrited_utmp_remove_entry(int);
+
+G_END_DECLS
+
+#endif /* XWRITED_UTMP_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xwrited.desktop.in	Sun Apr 27 23:07:51 2014 +0200
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Encoding=UTF-8
+_Name=xwrited
+_Comment=Display write and wall messages as desktop notifications
+Exec=xwrited
+Terminal=false
+Type=Application
+Categories=System;Monitor;