# HG changeset patch # User Guido Berhoerster # Date 1398632871 -7200 # Node ID 52694b49dcc4095e46a393d286076fdff171a418 Initial revision diff -r 000000000000 -r 52694b49dcc4 Makefile --- /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 +# +# 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)) diff -r 000000000000 -r 52694b49dcc4 deps.sed --- /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)\ +&/ +} diff -r 000000000000 -r 52694b49dcc4 main.c --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} diff -r 000000000000 -r 52694b49dcc4 po/POTFILES.in --- /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 diff -r 000000000000 -r 52694b49dcc4 po/de.po --- /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 , 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 \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" diff -r 000000000000 -r 52694b49dcc4 xwrited-debug.c --- /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 + * + * 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 +#include +#include + +#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); + } +} diff -r 000000000000 -r 52694b49dcc4 xwrited-debug.h --- /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 + * + * 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 + +G_BEGIN_DECLS + +void xwrited_debug_init(gboolean); + +G_END_DECLS + +#endif /* XWRITED_DEBUG_H */ diff -r 000000000000 -r 52694b49dcc4 xwrited-unique.c --- /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 + * + * 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 +#include +#include + +#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); +} diff -r 000000000000 -r 52694b49dcc4 xwrited-unique.h --- /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 + * + * 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 +#include + +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 */ diff -r 000000000000 -r 52694b49dcc4 xwrited-utmp-utempter.c --- /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 + * + * 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 +#include + +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); +} diff -r 000000000000 -r 52694b49dcc4 xwrited-utmp-utmpx.c --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff -r 000000000000 -r 52694b49dcc4 xwrited-utmp.h --- /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 + * + * 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 + +G_BEGIN_DECLS + +void xwrited_utmp_add_entry(int); +void xwrited_utmp_remove_entry(int); + +G_END_DECLS + +#endif /* XWRITED_UTMP_H */ diff -r 000000000000 -r 52694b49dcc4 xwrited.desktop.in --- /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;