Mercurial > projects > xwrited
view main.c @ 14:129f316b99aa version-2
Release version 2
author | Guido Berhoerster <guido+xwrited@berhoerster.name> |
---|---|
date | Tue, 17 Mar 2015 00:00:41 +0100 |
parents | eb97cafe34e5 |
children | 4a5330979433 |
line wrap: on
line source
/* * Copyright (C) 2015 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" #define BUFFER_TIMEOUT (250) enum { PIPE_R_FD = 0, PIPE_W_FD }; static int signal_pipe_fd[2] = { -1, -1 }; static guint notify_timeout_id; static GMainLoop *loop; static GString *buffer; 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) { 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(void) { gboolean retval = FALSE; GString *utf8_str = NULL; gchar *startp = buffer->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(buffer->len); while (!g_utf8_validate(startp, buffer->str + buffer->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 < buffer->str + buffer->len) ? 1 : 0); } g_string_append_len(utf8_str, startp, buffer->str + buffer->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) { g_critical("failed to create regex object: %s", error->message); g_error_free(error); goto out; } body = g_regex_replace_literal(regex, utf8_str->str, -1, 0, "", 0, &error); if (error != NULL) { g_critical("failed to replace control and space characters: " "%s", error->message); g_error_free(error); 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)) { retval = TRUE; 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); } /* prevent a permanently large buffer */ g_string_free(buffer, FALSE); buffer = g_string_sized_new(BUFSIZ); return (retval); } static gboolean notify_timeout_cb(gpointer user_data) { if (!send_notification()) { g_warning("failed to send notification"); } notify_timeout_id = 0; return (FALSE); } static gboolean master_pty_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data) { gchar buf[BUFSIZ]; GIOStatus status; gsize buf_len; GError *error = NULL; if ((cond & G_IO_IN) || (cond & G_IO_PRI)) { /* 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(buffer, 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); return (FALSE); } /* * schedule a timeout for sending a notification with the * buffered message */ if (notify_timeout_id <= 0) { notify_timeout_id = g_timeout_add(BUFFER_TIMEOUT, notify_timeout_cb, NULL); } } if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) { g_critical("connection to master pty broken"); g_main_loop_quit(loop); return (FALSE); } return (TRUE); } int main(int argc, char *argv[]) { int status = EXIT_FAILURE; 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); #if !GLIB_CHECK_VERSION(2, 35, 0) /* deprecated in glib >= 2.35 */ g_type_init(); #endif 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; } buffer = g_string_sized_new(BUFSIZ); /* 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, NULL)) { 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, NULL) == 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 (buffer != NULL) { g_string_free(buffer, FALSE); } if (app != NULL) { g_object_unref(app); } if (loop != NULL) { g_main_loop_unref(loop); } if (notify_is_initted()) { notify_uninit(); } exit(status); }