Mercurial > projects > xwrited
comparison main.c @ 13:eb97cafe34e5
Try to prevent messages from being chopped up into multiple notifications
Read data into a buffer and only send a notification when 250ms have passed
since the first read instead of sending a notification immediately after each
read. This is an attempt to prevent messages from being chopped up into
multiple notifications. A (rather negligible) downside of this approach is
that multiple messages rapidly following each other are coalesced into a
single notification.
author | Guido Berhoerster <guido+xwrited@berhoerster.name> |
---|---|
date | Mon, 16 Mar 2015 23:54:20 +0100 |
parents | 6440ba6e3466 |
children | 4a5330979433 |
comparison
equal
deleted
inserted
replaced
12:6440ba6e3466 | 13:eb97cafe34e5 |
---|---|
1 /* | 1 /* |
2 * Copyright (C) 2014 Guido Berhoerster <guido+xwrited@berhoerster.name> | 2 * Copyright (C) 2015 Guido Berhoerster <guido+xwrited@berhoerster.name> |
3 * | 3 * |
4 * Permission is hereby granted, free of charge, to any person obtaining | 4 * Permission is hereby granted, free of charge, to any person obtaining |
5 * a copy of this software and associated documentation files (the | 5 * a copy of this software and associated documentation files (the |
6 * "Software"), to deal in the Software without restriction, including | 6 * "Software"), to deal in the Software without restriction, including |
7 * without limitation the rights to use, copy, modify, merge, publish, | 7 * without limitation the rights to use, copy, modify, merge, publish, |
38 #include <libnotify/notify.h> | 38 #include <libnotify/notify.h> |
39 #include "xwrited-debug.h" | 39 #include "xwrited-debug.h" |
40 #include "xwrited-unique.h" | 40 #include "xwrited-unique.h" |
41 #include "xwrited-utmp.h" | 41 #include "xwrited-utmp.h" |
42 | 42 |
43 #define BUFFER_TIMEOUT (250) | |
44 | |
43 enum { | 45 enum { |
44 PIPE_R_FD = 0, | 46 PIPE_R_FD = 0, |
45 PIPE_W_FD | 47 PIPE_W_FD |
46 }; | 48 }; |
47 | 49 |
48 static int signal_pipe_fd[2] = { -1, -1 }; | 50 static int signal_pipe_fd[2] = { -1, -1 }; |
51 static guint notify_timeout_id; | |
52 static GMainLoop *loop; | |
53 static GString *buffer; | |
49 | 54 |
50 static void | 55 static void |
51 on_signal(int signo) | 56 on_signal(int signo) |
52 { | 57 { |
53 int old_errno = errno; | 58 int old_errno = errno; |
66 } | 71 } |
67 | 72 |
68 static gboolean | 73 static gboolean |
69 signal_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data) | 74 signal_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data) |
70 { | 75 { |
71 GMainLoop *loop = (GMainLoop *)user_data; | |
72 sigset_t sigset; | 76 sigset_t sigset; |
73 sigset_t old_sigset; | 77 sigset_t old_sigset; |
74 GIOStatus status; | 78 GIOStatus status; |
75 gsize n; | 79 gsize n; |
76 GError *error = NULL; | 80 GError *error = NULL; |
112 | 116 |
113 return (TRUE); | 117 return (TRUE); |
114 } | 118 } |
115 | 119 |
116 static gboolean | 120 static gboolean |
117 send_notification(GString *raw_str, GMainLoop *loop) | 121 send_notification(void) |
118 { | 122 { |
119 gboolean retval = FALSE; | 123 gboolean retval = FALSE; |
120 GString *utf8_str = NULL; | 124 GString *utf8_str = NULL; |
121 gchar *startp = raw_str->str; | 125 gchar *startp = buffer->str; |
122 gchar *endp; | 126 gchar *endp; |
123 GRegex *regex = NULL; | 127 GRegex *regex = NULL; |
124 GError *error = NULL; | 128 GError *error = NULL; |
125 gchar *body = NULL; | 129 gchar *body = NULL; |
126 GList *capabilities = NULL; | 130 GList *capabilities = NULL; |
127 gchar *tmp; | 131 gchar *tmp; |
128 NotifyNotification *notification = NULL; | 132 NotifyNotification *notification = NULL; |
129 | 133 |
130 utf8_str = g_string_sized_new(raw_str->len); | 134 utf8_str = g_string_sized_new(buffer->len); |
131 while (!g_utf8_validate(startp, raw_str->str + raw_str->len - | 135 while (!g_utf8_validate(startp, buffer->str + buffer->len - |
132 startp, (const gchar **)&endp)) { | 136 startp, (const gchar **)&endp)) { |
133 g_string_append_len(utf8_str, startp, endp - startp); | 137 g_string_append_len(utf8_str, startp, endp - startp); |
134 /* | 138 /* |
135 * replace each byte that does not belong to a UTF-8-encoded | 139 * replace each byte that does not belong to a UTF-8-encoded |
136 * character with the Unicode REPLACEMENT CHARACTER (U+FFFD) | 140 * character with the Unicode REPLACEMENT CHARACTER (U+FFFD) |
137 */ | 141 */ |
138 g_string_append(utf8_str, "\357\277\275"); | 142 g_string_append(utf8_str, "\357\277\275"); |
139 | 143 |
140 startp = endp + ((endp < raw_str->str + raw_str->len) ? 1 : 0); | 144 startp = endp + ((endp < buffer->str + buffer->len) ? 1 : 0); |
141 } | 145 } |
142 g_string_append_len(utf8_str, startp, raw_str->str + raw_str->len - | 146 g_string_append_len(utf8_str, startp, buffer->str + buffer->len - |
143 startp); | 147 startp); |
144 | 148 |
145 /* remove any CR, BEL and trailing space and tabs */ | 149 /* remove any CR, BEL and trailing space and tabs */ |
146 regex = g_regex_new("([\r\a]+|[ \t\r\a]+$)", G_REGEX_MULTILINE, 0, | 150 regex = g_regex_new("([\r\a]+|[ \t\r\a]+$)", G_REGEX_MULTILINE, 0, |
147 &error); | 151 &error); |
210 g_regex_unref(regex); | 214 g_regex_unref(regex); |
211 } | 215 } |
212 if (utf8_str != NULL) { | 216 if (utf8_str != NULL) { |
213 g_string_free(utf8_str, TRUE); | 217 g_string_free(utf8_str, TRUE); |
214 } | 218 } |
219 /* prevent a permanently large buffer */ | |
220 g_string_free(buffer, FALSE); | |
221 buffer = g_string_sized_new(BUFSIZ); | |
215 | 222 |
216 return (retval); | 223 return (retval); |
217 } | 224 } |
218 | 225 |
219 static gboolean | 226 static gboolean |
220 master_pty_read_cb(GIOChannel *source, GIOCondition cond, | 227 notify_timeout_cb(gpointer user_data) |
221 gpointer user_data) | |
222 { | 228 { |
223 GMainLoop *loop = (GMainLoop *)user_data; | 229 if (!send_notification()) { |
230 g_warning("failed to send notification"); | |
231 } | |
232 | |
233 notify_timeout_id = 0; | |
234 | |
235 return (FALSE); | |
236 } | |
237 | |
238 static gboolean | |
239 master_pty_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data) | |
240 { | |
224 gchar buf[BUFSIZ]; | 241 gchar buf[BUFSIZ]; |
225 GString *raw_str = NULL; | |
226 GIOStatus status; | 242 GIOStatus status; |
227 gsize buf_len; | 243 gsize buf_len; |
228 GError *error = NULL; | 244 GError *error = NULL; |
229 | 245 |
230 if ((cond & G_IO_IN) || (cond & G_IO_PRI)) { | 246 if ((cond & G_IO_IN) || (cond & G_IO_PRI)) { |
231 raw_str = g_string_sized_new(BUFSIZ); | |
232 /* read message from master pty */ | 247 /* read message from master pty */ |
233 while ((status = g_io_channel_read_chars(source, buf, BUFSIZ, | 248 while ((status = g_io_channel_read_chars(source, buf, BUFSIZ, |
234 &buf_len, &error)) == G_IO_STATUS_NORMAL) { | 249 &buf_len, &error)) == G_IO_STATUS_NORMAL) { |
235 if (buf_len > 0) { | 250 if (buf_len > 0) { |
236 g_debug("read %" G_GSSIZE_FORMAT " bytes from " | 251 g_debug("read %" G_GSSIZE_FORMAT " bytes from " |
237 "master pty", buf_len); | 252 "master pty", buf_len); |
238 g_string_append_len(raw_str, buf, | 253 g_string_append_len(buffer, buf, |
239 (gssize)buf_len); | 254 (gssize)buf_len); |
240 } | 255 } |
241 } | 256 } |
242 if (error != NULL) { | 257 if (error != NULL) { |
243 g_critical("failed to read from master pty: %s", | 258 g_critical("failed to read from master pty: %s", |
244 error->message); | 259 error->message); |
245 g_error_free(error); | 260 g_error_free(error); |
246 g_main_loop_quit(loop); | 261 g_main_loop_quit(loop); |
247 goto out; | 262 return (FALSE); |
248 } | 263 } |
249 | 264 |
250 if (!send_notification(raw_str, loop)) { | 265 /* |
251 g_warning("failed to send notification"); | 266 * schedule a timeout for sending a notification with the |
267 * buffered message | |
268 */ | |
269 if (notify_timeout_id <= 0) { | |
270 notify_timeout_id = g_timeout_add(BUFFER_TIMEOUT, | |
271 notify_timeout_cb, NULL); | |
252 } | 272 } |
253 } | 273 } |
254 | 274 |
255 if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) { | 275 if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) { |
256 g_critical("connection to master pty broken"); | 276 g_critical("connection to master pty broken"); |
257 g_main_loop_quit(loop); | 277 g_main_loop_quit(loop); |
258 goto out; | 278 return (FALSE); |
259 } | |
260 | |
261 out: | |
262 if (raw_str != NULL) { | |
263 g_string_free(raw_str, TRUE); | |
264 } | 279 } |
265 | 280 |
266 return (TRUE); | 281 return (TRUE); |
267 } | 282 } |
268 | 283 |
269 int | 284 int |
270 main(int argc, char *argv[]) | 285 main(int argc, char *argv[]) |
271 { | 286 { |
272 int status = EXIT_FAILURE; | 287 int status = EXIT_FAILURE; |
273 GMainLoop *loop = NULL; | |
274 GError *error = NULL; | 288 GError *error = NULL; |
275 XWritedUnique *app = NULL; | 289 XWritedUnique *app = NULL; |
276 GOptionContext *context = NULL; | 290 GOptionContext *context = NULL; |
277 struct sigaction sigact; | 291 struct sigaction sigact; |
278 GIOChannel *signal_channel = NULL; | 292 GIOChannel *signal_channel = NULL; |
338 if (loop == NULL) { | 352 if (loop == NULL) { |
339 g_critical("failed to create main loop"); | 353 g_critical("failed to create main loop"); |
340 goto out; | 354 goto out; |
341 } | 355 } |
342 | 356 |
357 buffer = g_string_sized_new(BUFSIZ); | |
358 | |
343 /* open master pty */ | 359 /* open master pty */ |
344 masterfd = posix_openpt(O_RDWR | O_NOCTTY); | 360 masterfd = posix_openpt(O_RDWR | O_NOCTTY); |
345 if (masterfd == -1) { | 361 if (masterfd == -1) { |
346 g_critical("failed to open master pty: %s", g_strerror(errno)); | 362 g_critical("failed to open master pty: %s", g_strerror(errno)); |
347 goto out; | 363 goto out; |
378 error->message); | 394 error->message); |
379 g_error_free(error); | 395 g_error_free(error); |
380 goto out; | 396 goto out; |
381 } | 397 } |
382 if (!g_io_add_watch(master_pty_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | | 398 if (!g_io_add_watch(master_pty_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | |
383 G_IO_ERR, master_pty_read_cb, loop)) { | 399 G_IO_ERR, master_pty_read_cb, NULL)) { |
384 g_critical("failed to add watch on signal channel"); | 400 g_critical("failed to add watch on signal channel"); |
385 goto out; | 401 goto out; |
386 } | 402 } |
387 | 403 |
388 /* create pipe for delivering signals to a listener in the main loop */ | 404 /* create pipe for delivering signals to a listener in the main loop */ |
415 error->message); | 431 error->message); |
416 g_error_free(error); | 432 g_error_free(error); |
417 goto out; | 433 goto out; |
418 } | 434 } |
419 if (g_io_add_watch(signal_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | | 435 if (g_io_add_watch(signal_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | |
420 G_IO_ERR, signal_read_cb, loop) == 0) { | 436 G_IO_ERR, signal_read_cb, NULL) == 0) { |
421 g_critical("failed to add watch on the signal channel"); | 437 g_critical("failed to add watch on the signal channel"); |
422 goto out; | 438 goto out; |
423 } | 439 } |
424 | 440 |
425 /* set up signal handler */ | 441 /* set up signal handler */ |
471 | 487 |
472 if (masterfd != -1) { | 488 if (masterfd != -1) { |
473 close(masterfd); | 489 close(masterfd); |
474 } | 490 } |
475 | 491 |
492 if (buffer != NULL) { | |
493 g_string_free(buffer, FALSE); | |
494 } | |
495 | |
476 if (app != NULL) { | 496 if (app != NULL) { |
477 g_object_unref(app); | 497 g_object_unref(app); |
478 } | 498 } |
479 | 499 |
480 if (loop != NULL) { | 500 if (loop != NULL) { |