pax_global_header00006660000000000000000000000064133274127370014523gustar00rootroot0000000000000052 comment=6067fd5c5e34f7648f29ab81c3f93bfe62c676b8 xwrited-3/000077500000000000000000000000001332741273700127155ustar00rootroot00000000000000xwrited-3/Makefile000066400000000000000000000135661332741273700143700ustar00rootroot00000000000000# # Copyright (C) 2018 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 APPLICATION_ID = org.guido-berhoerster.code.xwrited VERSION = 3 DISTNAME := $(PACKAGE)-$(VERSION) BUG_ADDRESS = guido+xwrited@berhoerster.name # gcc, clang, icc, Sun/Solaris Studio CC := $(CC) -std=c99 COMPILE.c = $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(TARGET_ARCH) -c # gcc, clang, icc MAKEDEPEND.c = $(CC) -MM $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) # Sun/Solaris Studio #MAKEDEPEND.c = $(CC) -xM1 $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) # X makedepend #MAKEDEPEND.c = makedepend -f- -Y -- $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) -- LINK.c = $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH) LINK.o = $(CC) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH) CP := cp INSTALL := install INSTALL.exec := $(INSTALL) -D -m 0755 INSTALL.data := $(INSTALL) -D -m 0644 INSTALL.link := $(CP) -f -P PAX := pax GZIP := gzip SED := sed XSLTPROC := xsltproc DOCBOOK5_MANPAGES_STYLESHEET = http://docbook.sourceforge.net/release/xsl-ns/current/manpages/docbook.xsl DOCBOOK5_MANPAGES_FLAGS = --stringparam man.authors.section.enabled 0 \ --stringparam man.copyright.section.enabled 0 MSGFMT = msgfmt MSGMERGE = msgmerge XGETTEXT = xgettext XGETTEXT_OPTIONS = --copyright-holder "$(AUTHOR)" \ --package-name '$(PACKAGE)' \ --package-version '$(VERSION)' \ --msgid-bugs-address '$(BUG_ADDRESS)' \ --default-domain '$(PACKAGE)' \ --from-code UTF-8 \ --keyword=_ \ --keyword=N_ \ --keyword=C_:1c,2 \ --keyword=NC_:1c,2 \ --keyword=g_dngettext:2,3 \ --add-comments define generate-manpage-rule = %.$(1): %.$(1).xml $$(XSLTPROC) \ --xinclude \ --stringparam package $$(PACKAGE) \ --stringparam version $$(VERSION)\ docbook-update-source-data.xsl $$< | \ $$(XSLTPROC) \ --xinclude \ $$(DOCBOOK5_MANPAGES_FLAGS) \ --output $$@ \ $$(DOCBOOK5_MANPAGES_STYLESHEET) \ - endef DESTDIR ?= prefix ?= /usr/local bindir ?= $(prefix)/bin datadir ?= $(prefix)/share mandir ?= $(datadir)/man localedir ?= $(datadir)/locale sysconfdir ?= /etc xdgautostartdir ?= $(sysconfdir)/xdg/autostart OS_NAME := $(shell uname -s) OS_RELEASE := $(shell uname -r) OBJS = xwrited.o \ xwd-application.o ifneq ($(findstring $(OS_NAME),Linux FreeBSD),) OBJS += xwd-utmp-utempter.o else OBJS += xwd-utmp-utmpx.o endif MANPAGES = $(PACKAGE).1 AUTOSTART_FILE = $(APPLICATION_ID).desktop LINGUAS := $(shell sed 's/\#.*//' po/LINGUAS) MOFILES := $(patsubst %,po/%.mo,$(LINGUAS)) POTFILES_IN := $(shell sed 's/\#.*//' po/POTFILES.in) POTFILE = po/$(PACKAGE).pot .DEFAULT_TARGET = all .PHONY: all pot update-po clean clobber dist install all: $(PACKAGE) $(AUTOSTART_FILE) $(MOFILES) $(MANPAGES) $(PACKAGE): XCFLAGS = $(shell pkg-config --cflags gobject-2.0 gio-2.0 \ glib-2.0) \ -DPACKAGE=\"$(PACKAGE)\" \ -DAPPLICATION_ID=\"$(APPLICATION_ID)\" \ -DVERSION=\"$(VERSION)\" \ -DG_LOG_DOMAIN=\"$(PACKAGE)\" \ -DPACKAGE_LOCALE_DIR="\"$(localedir)\"" \ -DGETTEXT_PACKAGE=\"$(PACKAGE)\" $(PACKAGE): LDLIBS = $(shell pkg-config --libs gobject-2.0 gio-2.0 \ glib-2.0) ifeq ($(OS_NAME),Linux) $(PACKAGE): XCPPFLAGS += -D_XOPEN_SOURCE=600 $(PACKAGE): LDLIBS += -lutempter else ifeq ($(OS_NAME),FreeBSD) $(PACKAGE): LDLIBS += -lulog else ifeq ($(OS_NAME),SunOS) $(PACKAGE): XCPPFLAGS += -D_XOPEN_SOURCE=600 -I/usr/xpg4/include $(PACKAGE): XLDFLAGS += -L/usr/xpg4/lib -R/usr/xpg4/lib endif $(PACKAGE): $(OBJS) $(LINK.o) $^ $(LDLIBS) -o $@ $(POTFILE): po/POTFILES.in $(POTFILES_IN) $(XGETTEXT) $(XGETTEXT_OPTIONS) --files-from $< --output $@ pot: $(POTFILE) update-po: $(POTFILE) for pofile in $(patsubst %.mo,%.po,$(MOFILES)); do \ $(MSGMERGE) --update --backup off $$pofile $<; \ done %.mo: %.po $(MSGFMT) --output $@ $< %.desktop: %.desktop.in $(MSGFMT) --desktop --template $< -d po --output $@ $(foreach section,1 2 3 4 5 6 7 8 9,$(eval $(call generate-manpage-rule,$(section)))) %.mo: %.po $(MSGFMT) -o $@ $< %.o: %.c $(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d $(COMPILE.c) -o $@ $< install: all $(INSTALL.exec) $(PACKAGE) "$(DESTDIR)$(bindir)/$(PACKAGE)" $(INSTALL.data) $(AUTOSTART_FILE) \ $(DESTDIR)$(xdgautostartdir)/$(AUTOSTART_FILE) for lang in $(LINGUAS); do \ $(INSTALL.data) po/$${lang}.mo \ $(DESTDIR)$(localedir)/$${lang}/LC_MESSAGES/$(PACKAGE).mo; \ done for manpage in $(MANPAGES); do \ $(INSTALL.data) $${manpage} \ "$(DESTDIR)$(mandir)/man$${manpage##*.}/$${manpage##*/}"; \ done clean: rm -f $(PACKAGE) $(OBJS) $(AUTOSTART_FILE) $(POTFILE) $(MOFILES) \ $(MANPAGES) clobber: clean rm -f $(patsubst %.o,%.d,$(OBJS)) dist: clobber $(PAX) -w -x ustar -s ',.*/\..*,,' -s ',./[^/]*\.tar\.gz,,' \ -s ',^\.$$,,' -s ',\./,$(DISTNAME)/,' . | \ $(GZIP) > $(DISTNAME).tar.gz -include local.mk -include $(patsubst %.o,%.d,$(OBJS)) xwrited-3/NEWS000066400000000000000000000015721332741273700134210ustar00rootroot00000000000000News ==== xwrited 3 (2018-06-30T23:07:36+02:00) ------------------------------------- - Add English translations - Try to decode octal and "Meta-ASCII" escapes - Port to GLib version 2.48 or later - Modernize and improve build system - Exclude "." from tarballs - Fix wrong command name in the manpage synopsis xwrited 2 (2015-03-17T00:00:41+01:00) ------------------------------------- - Try to prevent messages from being chopped up into multiple notifications - Do not print a warning if an empty notification is skipped - Display error messages and free errors - Improve README file - Add NEWS file xwrited 1 (2014-06-26T13:00:26+02:00) ------------------------------------- - Add missing prototype - Add Indonesian translation - Add manpage - Add README file - Support FreeBSD > 9.0 - Make use of GDBus for glib >= 2.25.5 - Add support for newer glib versions - Initial revision xwrited-3/README000066400000000000000000000107531332741273700136030ustar00rootroot00000000000000xwrited ======= The xwrited utility displays write(1) and wall(1) messages as desktop notifications. A notification daemon compliant to the freedesktop.org Desktop Notification Specification draft needs to be running in order to display the notifications. Build Instructions ------------------ xwrited requires a POSIX:2004 compatible operating system, it has been tested to work on Linux distributions, FreeBSD, DragonFly BSD, NetBSD, and Solaris and Illumos-derived distributions. The following tools and shared libraries are required to build xwrited: - GNU make version 3.81 or later - GNU or BSD install - GNU gettext 0.19 or later - GLib version 2.48 or later - libutempter on Linux - the xsltproc tool from libxml2 Before building xwrited check the commented macros in the Makefile for any macros you may need to override depending on the used toolchain and operating system. By default, all files will be installed under the "/usr/local" directory, a different installation path prefix can be set via the `prefix` macro. In addition, a second path prefix can be specified via the `DESTDIR` macro which will be prepended to any path, incuding the `prefix` macro path prefix. In contrast to `prefix`, the path specified via the `DESTDIR` macro will only be prepended to paths during installation and not be used for constructing internal paths. The following instructions assume that `make` is GNU make, on some platforms it may be installed under a different name or a non-default path. In order to start the build process run `make all`. After a successful build, run `make install` to install the program, any associated data files and the documentation. Previously built binaries, object files, generated data files and documentation can be removed by running `make clean`, any additional, generated files which are not removed by the `clean` target can be removed by running `make clobber`. Contact ------- Please send any feedback, translations or bug reports via email to . Bug Reports ----------- When sending bug reports, please always mention the exact version of xwrited with which the issue occurs as well as the version of the operating system you are using and make sure that you provide sufficient information to reproduce the issue and include any input, output, any error messages. In case of build issues, please also specify the implementations and versions of the tools and shared libraries used to build the program, in particular the compiler. In case of crashes, please generate a stack trace with a suitable debugger such as gdb, lldb, dbx, or debug after a crash has occurred either by examining the resulting core file or by running the program from the debugger and attach it to the bug report. In order to generate a meaningful stack trace the program as well as any dynamically linked libraries need to be built with debugging information, see the documentation of the used compiler for the required compiler flags. If any of the dynamically linked shared libraries do not contain debugging information, please either install debugging information for these libraries using mechanisms provided by your operating system or rebuild the libraries accordingly. Please refer to the documentation of the debugger for detailed instructions on generating backtraces. License ------- Except otherwise noted, all files are Copyright (C) 2018 Guido Berhoerster and distributed under the following license terms: Copyright (C) 2018 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. xwrited-3/deps.sed000066400000000000000000000012451332741273700143470ustar00rootroot00000000000000/^[^:]\{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)\ &/ } xwrited-3/docbook-update-source-data.xsl000066400000000000000000000015111332741273700205500ustar00rootroot00000000000000 xwrited-3/org.guido-berhoerster.code.xwrited.desktop.in000066400000000000000000000002651332741273700235350ustar00rootroot00000000000000[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; xwrited-3/po/000077500000000000000000000000001332741273700133335ustar00rootroot00000000000000xwrited-3/po/LINGUAS000066400000000000000000000000111332741273700143500ustar00rootroot00000000000000de en id xwrited-3/po/POTFILES.in000066400000000000000000000001571332741273700151130ustar00rootroot00000000000000org.guido-berhoerster.code.xwrited.desktop.in xwd-application.c xwd-utmp-utempter.c xwd-utmp-utmpx.c xwrited.c xwrited-3/po/de.po000066400000000000000000000024601332741273700142650ustar00rootroot00000000000000# German translations for xwrited package # German messages for xwrited. # Copyright (C) 2018 Guido Berhoerster. # This file is distributed under the same license as the xwrited package. # Guido Berhoerster , 2018. # msgid "" msgstr "" "Project-Id-Version: xwrited 1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-06-29 10:46+0000\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" #: org.guido-berhoerster.code.xwrited.desktop.in:4 msgid "xwrited" msgstr "xwrited" #: org.guido-berhoerster.code.xwrited.desktop.in:5 msgid "Display write and wall messages as desktop notifications" msgstr "Zeigt write und wall Nachrichten as Desktop-Benachrichtigungen an" #: xwd-application.c:52 msgid "Enable debugging messages" msgstr "" #: xwd-application.c:54 msgid "Quit running instance of xwrited" msgstr "Beende laufende Instanz von xwrited" #: xwd-application.c:56 msgid "Print the version number and quit" msgstr "Aktuelle Version zeigen und beenden" #: xwd-application.c:252 msgid "Message received" msgstr "Nachricht erhalten" xwrited-3/po/en.po000066400000000000000000000023641332741273700143020ustar00rootroot00000000000000# English translations for xwrited package. # This file is put in the public domain. # Guido Berhoerster , 2018. # msgid "" msgstr "" "Project-Id-Version: xwrited 2\n" "Report-Msgid-Bugs-To: guido+xwrited@berhoerster.name\n" "POT-Creation-Date: 2018-06-30 21:02+0000\n" "PO-Revision-Date: 2018-06-30 21:03+0000\n" "Last-Translator: Guido Berhoerster \n" "Language-Team: English\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ASCII\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: org.guido-berhoerster.code.xwrited.desktop.in:4 msgid "xwrited" msgstr "xwrited" #: org.guido-berhoerster.code.xwrited.desktop.in:5 msgid "Display write and wall messages as desktop notifications" msgstr "Display write and wall messages as desktop notifications" #: xwd-application.c:52 msgid "Enable debugging messages" msgstr "Enable debugging messages" #: xwd-application.c:54 msgid "Quit running instance of xwrited" msgstr "Quit running instance of xwrited" #: xwd-application.c:56 msgid "Print the version number and quit" msgstr "Print the version number and quit" #: xwd-application.c:332 msgid "Message received" msgstr "Message received" xwrited-3/po/id.po000066400000000000000000000022751332741273700142750ustar00rootroot00000000000000# Indonesian translations for xwrited package. # Copyright (C) 2018 Guido Berhoerster # This file is distributed under the same license as the xwrited package. # Guido Berhoerster , 2018. # msgid "" msgstr "" "Project-Id-Version: xwrited 1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-06-29 10:46+0000\n" "PO-Revision-Date: 2014-04-27 21:01+0200\n" "Last-Translator: Guido Berhoerster \n" "Language-Team: Indonesian\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ASCII\n" "Content-Transfer-Encoding: 8bit\n" #: org.guido-berhoerster.code.xwrited.desktop.in:4 msgid "xwrited" msgstr "xwrited" #: org.guido-berhoerster.code.xwrited.desktop.in:5 msgid "Display write and wall messages as desktop notifications" msgstr "Tampilkan pesanan write dan wall sebagai notifikasi desktop" #: xwd-application.c:52 msgid "Enable debugging messages" msgstr "" #: xwd-application.c:54 msgid "Quit running instance of xwrited" msgstr "" #: xwd-application.c:56 msgid "Print the version number and quit" msgstr "Tampilkan versi kini dan keluar" #: xwd-application.c:252 msgid "Message received" msgstr "Pesanan diterima" xwrited-3/xwd-application.c000066400000000000000000000407241332741273700161730ustar00rootroot00000000000000/* * Copyright (C) 2018 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. */ #include #include #include #include #include #include #include #include "xwd-application.h" #include "xwd-utmp.h" #define BUFFER_TIMEOUT 500 /* ms */ struct _XwdApplication { GApplication parent_instance; gint masterfd; gint slavefd; GIOChannel *master_pty_chan; guint buffer_timeout_id; GString *message_buf; }; G_DEFINE_TYPE(XwdApplication, xwd_application, G_TYPE_APPLICATION) static void xwd_application_quit(GSimpleAction *, GVariant *, gpointer); static const GOptionEntry cmd_options[] = { { "debug", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Enable debugging messages"), NULL }, { "quit", 'q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Quit running instance of xwrited"), NULL }, { "version", 'V', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Print the version number and quit"), NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, NULL, NULL }, { NULL } }; static const GActionEntry xwd_application_actions[] = { { "quit", xwd_application_quit } }; static void xwd_application_quit(GSimpleAction *simple, GVariant *parameter, gpointer user_data) { XwdApplication *self = user_data; g_application_quit(G_APPLICATION(self)); } static gboolean on_signal(gpointer user_data) { XwdApplication *self = user_data; g_debug("received signal, exiting"); g_action_group_activate_action(G_ACTION_GROUP(G_APPLICATION(self)), "quit", NULL); return (TRUE); } static GString * string_copy(GString *dest, GString *src) { g_string_truncate(dest, 0); g_string_append(dest, src->str); return (dest); } static void string_decode_octal(GString *string) { GString *result; gsize remaining = string->len; gchar *p = string->str; gchar oct[4] = { '\0' }; guint64 num; char *end; result = g_string_sized_new(string->len); while (remaining > 0) { if ((remaining >= 4) && (*p == '\\')) { /* put octal number in NUL-terminated buffer */ memcpy(oct, p + 1, 3); /* convert valid octal number to byte */ num = g_ascii_strtoull(oct, &end, 8); if ((*end == '\0') && (num <= 0xff)) { /* conversion succeeded */ p += 4; remaining -= 4; g_string_append_c(result, num); continue; } } g_string_append_c(result, *p++); remaining--; } string_copy(string, result); g_string_free(result, TRUE); } static void string_decode_meta_ascii(GString *string) { GString *result; gchar *p = string->str; gsize remaining = string->len; result = g_string_sized_new(string->len); while (remaining > 0) { if ((remaining >= 3) && (*p == 'M') && (*(p + 1) == '-') && ((*(p + 2) & (1 << 7)) == 0)) { /* restore 8th bit */ g_string_append_c(result, *(p + 2) | (1 << 7)); remaining -= 3; p += 3; continue; } g_string_append_c(result, *p++); remaining--; } string_copy(string, result); g_string_free(result, TRUE); } static void string_to_valid_utf8(GString *string) { GString *result; gchar *start = string->str; gchar *end; gsize remaining = string->len; result = g_string_sized_new(string->len); while (remaining > 0) { if (g_utf8_validate(start, remaining, (const gchar **)&end)) { /* remaining part is valid */ g_string_append_len(result, start, remaining); break; } /* append valid part */ g_string_append_len(result, start, end - start); /* * replace first invalid byte with Unicode "REPLACEMENT * CHARACTER" (U+FFFD) */ g_string_append(result, "\357\277\275"); remaining -= (end - start) + 1; start = end + 1; } string_copy(string, result); g_string_free(result, TRUE); } static void string_trim_lines(GString *string) { GString *result; gchar *p = string->str; gchar *q; gsize line_len; gsize remaining = string->len; result = g_string_sized_new(string->len); while (remaining > 0) { q = memchr(p, '\n', remaining); if (q == NULL) { g_string_append_len(result, p, remaining); break; } line_len = q - p - 1; /* convert \r\n to \n */ if ((line_len > 0) && (p[line_len - 1] == '\r')) { line_len--; } /* trim spaces on the right */ while ((line_len > 0) && (p[line_len - 1] == ' ')) { line_len--; } g_string_append_len(result, p, line_len); g_string_append_c(result, '\n'); remaining -= q + 1 - p; p = q + 1; } string_copy(string, result); g_string_free(result, TRUE); } static void string_filter_nonprintable(GString *string) { GString *result; const gchar *p; gunichar c; result = g_string_sized_new(string->len); for (p = string->str; *p != '\0'; p = g_utf8_next_char(p)) { c = g_utf8_get_char(p); if (g_unichar_isprint(c) || g_unichar_isspace(c)) { g_string_append_unichar(result, c); } } string_copy(string, result); g_string_free(result, TRUE); } static gchar * hexdumpa(const void *mem, gsize n) { const guchar *bytes = mem; GString *string; gsize i; gsize j; string = g_string_sized_new((n / 16 + (n % 16 > 0)) * 76); for (i = 0; i < n; i += 16) { g_string_append_printf(string, "%08zx ", i); for (j = 0; (i + j < n) && (j < 16); j++) { g_string_append_printf(string, " %02x", bytes[i + j]); } for (; j < 16; j++) { g_string_append(string, " "); } g_string_append(string, " "); for (j = 0; (i + j < n) && (j < 16); j++) { g_string_append_printf(string, "%c", g_ascii_isprint(bytes[i + j]) ? bytes[i + j] : '.'); } g_string_append_c(string, '\n'); } return (g_string_free(string, FALSE)); } static void display_message(XwdApplication *self) { gboolean enable_debug_logging; gchar *message_dump; GString *message; GIcon *icon; GNotification *notification; if (self->message_buf->len == 0) { return; } enable_debug_logging = (g_getenv("G_MESSAGES_DEBUG") != NULL); if (enable_debug_logging) { message_dump = hexdumpa(self->message_buf->str, self->message_buf->len); g_debug("raw message:\n%s", message_dump); g_free(message_dump); } /* * There is no reliable way to determine the character encoding of the * received message which, depending on the locale of the sender, may * even differ for different messages. A user could even send binary * data. It is thus assumed that messages are in UTF-8 encoding which * should be the most common case on modern systems. Any invalid * sequences are replaced with the Unicode "REPLACEMENT CHARACTER" * (U+FFFD) and non-printable characters are removed. Additionally, * padding typically added by wall(1) implementations is removed in * order to improve readability. * Some write(1) and wall(1) implementations encode non-ASCII * characters, in particular UTF-8 sequences, by prefixing them with * "M-" and clearing the 8th bit while others (e.g. util-linux) use * octal escape sequences. These encodings are reversed before messages * are processed further. However some implementations such as NetBSD * write(1) uncoditionally process each byte with toascii(3) which * makes it impossible to restore the original value. */ message = g_string_new_len(self->message_buf->str, self->message_buf->len); string_decode_octal(message); string_decode_meta_ascii(message); string_to_valid_utf8(message); string_filter_nonprintable(message); string_trim_lines(message); if (enable_debug_logging) { message_dump = hexdumpa(message->str, message->len); g_debug("message:\n%s", message_dump); g_free(message_dump); } icon = g_themed_icon_new("utilities-terminal"); notification = g_notification_new(_("Message received")); g_notification_set_icon(notification, icon); g_notification_set_body(notification, message->str); g_application_send_notification(G_APPLICATION(self), NULL, notification); g_object_unref(notification); g_object_unref(icon); g_string_free(message, TRUE); g_string_truncate(self->message_buf, 0); } static gboolean on_buffer_timeout(gpointer user_data) { XwdApplication *self = user_data; display_message(self); self->buffer_timeout_id = 0; return (FALSE); } static gboolean on_master_pty_readable(GIOChannel *source, GIOCondition cond, gpointer user_data) { XwdApplication *self = user_data; GIOStatus status; gchar buf[BUFSIZ]; gsize buf_len = 0; GError *error = NULL; if (cond & G_IO_IN) { /* read message data from master pty */ memset(buf, 0, sizeof (buf)); while ((status = g_io_channel_read_chars(source, (gchar *)&buf, sizeof (buf), &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(self->message_buf, buf, buf_len); } } if (error != NULL) { g_critical("failed to read from master pty: %s", error->message); g_error_free(error); return (FALSE); } /* * a message might be read in several parts and it is not * possible to reliably detect the beginning or end of a * message, so buffer read data until a short timeout is * reached as this works well for a single message which should * be the most common case */ if (self->buffer_timeout_id == 0) { self->buffer_timeout_id = g_timeout_add(BUFFER_TIMEOUT, on_buffer_timeout, self); } } if (cond & (G_IO_HUP | G_IO_ERR)) { g_critical("connection to master pty broken"); return (FALSE); } return (TRUE); } static void xwd_application_startup(GApplication *application) { XwdApplication *self = XWD_APPLICATION(application); gchar *slave_name; GIOFlags flags; GError *error = NULL; G_APPLICATION_CLASS(xwd_application_parent_class)->startup(application); /* create actions */ g_action_map_add_action_entries(G_ACTION_MAP(self), xwd_application_actions, G_N_ELEMENTS(xwd_application_actions), self); /* create signal watchers */ g_unix_signal_add(SIGINT, on_signal, self); g_unix_signal_add(SIGTERM, on_signal, self); g_unix_signal_add(SIGHUP, on_signal, self); /* open master pty */ self->masterfd = posix_openpt(O_RDWR | O_NOCTTY); if (self->masterfd == -1) { g_critical("failed to open master pty: %s", g_strerror(errno)); return; } /* create slave pty */ if ((grantpt(self->masterfd) == -1) || (unlockpt(self->masterfd) == -1)) { g_critical("failed to create slave pty: %s", g_strerror(errno)); return; } slave_name = ptsname(self->masterfd); if (slave_name == NULL) { g_critical("failed to obtain name of slave pty"); return; } /* * keep an open fd around order to prevent closing the master fd when * receiving an EOF */ self->slavefd = open(slave_name, O_RDWR); if (self->slavefd == -1) { g_critical("failed to open slave pty: %s", g_strerror(errno)); return; } /* create a GIOChannel for monitoring the master pty for messages */ self->master_pty_chan = g_io_channel_unix_new(self->masterfd); /* make it non-blocking */ flags = g_io_channel_get_flags(self->master_pty_chan); if (g_io_channel_set_flags(self->master_pty_chan, flags | G_IO_FLAG_NONBLOCK, &error) != G_IO_STATUS_NORMAL) { g_critical("failed set flags on the master pty channel: %s", error->message); g_error_free(error); return; } /* make the channel safe for encodings other than UTF-8 */ if (g_io_channel_set_encoding(self->master_pty_chan, NULL, &error) != G_IO_STATUS_NORMAL) { g_critical("failed set encoding on the master pty channel: %s", error->message); g_error_free(error); return; } if (!g_io_add_watch(self->master_pty_chan, G_IO_IN | G_IO_HUP | G_IO_ERR, on_master_pty_readable, self)) { g_critical("failed to add watch on master pty channel"); return; } xwd_utmp_add_entry(self->masterfd); /* keep GApplication running */ g_application_hold(application); } static void xwd_application_shutdown(GApplication *application) { XwdApplication *self = XWD_APPLICATION(application); GApplicationClass *application_class = G_APPLICATION_CLASS(xwd_application_parent_class); /* display any buffered data before exiting */ display_message(self); if (self->master_pty_chan != NULL) { g_io_channel_shutdown(self->master_pty_chan, FALSE, NULL); g_clear_pointer(&self->master_pty_chan, (GDestroyNotify)g_io_channel_unref); } if (self->slavefd != -1) { close(self->slavefd); self->slavefd = -1; } if (self->masterfd != -1) { close(self->masterfd); self->masterfd = -1; } xwd_utmp_remove_entry(self->masterfd); /* remove signal watches and buffer timeout */ while (g_source_remove_by_user_data(self)) { continue; } self->buffer_timeout_id = 0; application_class->shutdown(application); } static gint xwd_application_handle_local_options(GApplication *application, GVariantDict *options) { gchar **args = NULL; gchar *messages_debug; GError *error = NULL; /* filename arguments are not allowed */ if (g_variant_dict_lookup(options, G_OPTION_REMAINING, "^a&ay", &args)) { g_printerr("invalid argument: \"%s\"\n", args[0]); g_free(args); return (1); } if (g_variant_dict_contains(options, "version")) { g_print("%s %s\n", PACKAGE, VERSION); /* quit */ return (0); } if (g_variant_dict_contains(options, "debug")) { /* enable debug logging */ messages_debug = g_strjoin(":", G_LOG_DOMAIN, g_getenv("G_MESSAGES_DEBUG"), NULL); g_setenv("G_MESSAGES_DEBUG", messages_debug, TRUE); g_free(messages_debug); } /* * register with the session bus so that it is possible to discern * between remote and primary instance and that remote actions can be * invoked, this causes the startup signal to be emitted which, in case * of the primary instance, starts to instantiate the * backend with the given values */ if (!g_application_register(application, NULL, &error)) { g_critical("g_application_register: %s", error->message); g_error_free(error); return (1); } if (g_variant_dict_contains(options, "quit")) { /* only valid if a remote instance is running */ if (!g_application_get_is_remote(application)) { g_printerr("%s is not running\n", g_get_prgname()); return (1); } /* signal remote instance to quit */ g_action_group_activate_action(G_ACTION_GROUP(application), "quit", NULL); /* quit local instance */ return (0); } /* proceed with default command line processing */ return (-1); } static void xwd_application_activate(GApplication *application) { GApplicationClass *application_class = G_APPLICATION_CLASS(xwd_application_parent_class); /* do nothing, implementation required by GApplication */ application_class->activate(application); } static void xwd_application_finalize(GObject *object) { XwdApplication *self = XWD_APPLICATION(object); g_string_free(self->message_buf, TRUE); G_OBJECT_CLASS(xwd_application_parent_class)->finalize(object); } static void xwd_application_class_init(XwdApplicationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GApplicationClass *application_class = G_APPLICATION_CLASS(klass); object_class->finalize = xwd_application_finalize; application_class->startup = xwd_application_startup; application_class->shutdown = xwd_application_shutdown; application_class->handle_local_options = xwd_application_handle_local_options; application_class->activate = xwd_application_activate; } static void xwd_application_init(XwdApplication *self) { g_application_add_main_option_entries(G_APPLICATION(self), cmd_options); self->masterfd = -1; self->slavefd = -1; self->message_buf = g_string_sized_new(BUFSIZ); } XwdApplication * xwd_application_new(void) { return (g_object_new(XWD_TYPE_APPLICATION, "application-id", APPLICATION_ID, NULL)); } xwrited-3/xwd-application.h000066400000000000000000000027461332741273700162020ustar00rootroot00000000000000/* * Copyright (C) 2018 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 XWD_APPLICATION_H #define XWD_APPLICATION_H #include #include G_BEGIN_DECLS #define XWD_TYPE_APPLICATION (xwd_application_get_type()) G_DECLARE_FINAL_TYPE(XwdApplication, xwd_application, XWD, APPLICATION, GApplication) XwdApplication * xwd_application_new(void); G_END_DECLS #endif /* !XWD_APPLICATION_H */ xwrited-3/xwd-utmp-utempter.c000066400000000000000000000026231332741273700165140ustar00rootroot00000000000000/* * 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. */ #include #include void xwd_utmp_add_entry(int fd) { char *pty_name; pty_name = ptsname(fd); addToUtmp(pty_name, NULL, fd); } void xwd_utmp_remove_entry(int fd) { char *pty_name; pty_name = ptsname(fd); removeLineFromUtmp(pty_name, fd); } xwrited-3/xwd-utmp-utmpx.c000066400000000000000000000050221332741273700160200ustar00rootroot00000000000000/* * 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. */ #include #include #include #include #include #include #include #include #ifndef DEV_PREFIX #define DEV_PREFIX "/dev/" #endif /* !DEV_PREFIX */ 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 xwd_utmp_add_entry(int fd) { utmp_write_entry(fd, TRUE); } void xwd_utmp_remove_entry(int fd) { utmp_write_entry(fd, FALSE); } xwrited-3/xwd-utmp.h000066400000000000000000000024651332741273700146620ustar00rootroot00000000000000/* * 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 XWD_UTMP_H #define XWD_UTMP_H #include G_BEGIN_DECLS void xwd_utmp_add_entry(int); void xwd_utmp_remove_entry(int); G_END_DECLS #endif /* XWD_UTMP_H */ xwrited-3/xwrited.1.xml000066400000000000000000000106371332741273700152730ustar00rootroot00000000000000 Guido Berhoerster guido+xwrited@berhoerster.name 29 June, 2018 xwrited 1 User Commands xwrited display write and wall messages as desktop notifications xwrited Description The xwrited utility displays write1 and wall1 messages as desktop notifications. A notification daemon compliant to the freedesktop.org Desktop Notification Specification draft needs to be running in order to display the notifications. Options The following options are supported: Quit the running instance of xwrited. Print the version number and exit. Exit Status The following exit values are returned: 0 Command successfully executed. > 0 An error has occured. See Also write 1, wall1 Notes xwrited assumes that messages are encoded in UTF-8 because there is no way for it to determine the character set encoding of the received data. xwrited-3/xwrited.c000066400000000000000000000032061332741273700145500ustar00rootroot00000000000000/* * Copyright (C) 2018 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. */ #include #include #include #include #include "xwd-application.h" int main(int argc, char *argv[]) { int status; XwdApplication *application; bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); setlocale(LC_ALL, ""); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); application = xwd_application_new(); status = g_application_run(G_APPLICATION(application), argc, argv); g_object_unref(application); exit(status); }