More versioning
[pipeglade.git] / pipeglade.c
blobc932b8139fded69bd67ee4be54f345ece6a8f15d
1 /*
2 * Copyright (c) 2014 Bert Burgemeister <trebbu@googlemail.com>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <gtk/gtk.h>
27 #include <inttypes.h>
28 #include <locale.h>
29 #include <pthread.h>
30 #include <stdio.h>
31 #include <stdarg.h>
32 #include <stdbool.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <time.h>
37 #include <unistd.h>
39 #define VERSION "0.1.1"
40 #define BUFLEN 256
41 #define WHITESPACE " \t\n"
43 FILE *in;
44 FILE *out;
45 struct ui_data {
46 GtkBuilder *builder;
47 size_t msg_size;
48 char *msg;
49 bool msg_digested;
52 static void
53 usage(char **argv)
55 fprintf(stderr,
56 "usage: %s "
57 "[-h] "
58 "[-i in-fifo] "
59 "[-o out-fifo] "
60 "[-u glade-builder-file.ui] "
61 "[-V]\n",
62 argv[0]);
63 exit(EXIT_SUCCESS);
66 static void
67 version(void)
69 printf(VERSION "\n");
70 exit(EXIT_SUCCESS);
73 static bool
74 eql(const char *s1, const char *s2)
76 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
80 * Send GUI feedback to global stream "out". The message format is
81 * "<origin>:<section> <data ...>". The variadic arguments are
82 * strings; last argument must be NULL. We're being patient with
83 * receivers which may intermittently close their end of the fifo, and
84 * make a couple of retries if an error occurs.
86 static void
87 send_msg(GtkBuildable *obj, const char *section, ...)
89 va_list ap;
90 char *data;
91 long nsec;
93 for (nsec = 1e6; nsec < 1e9; nsec <<= 3) {
94 va_start(ap, section);
95 fprintf(out, "%s:%s ", gtk_buildable_get_name(obj), section);
96 while ((data = va_arg(ap, char *)) != NULL) {
97 size_t i = 0;
98 char c;
100 while ((c = data[i++]) != '\0')
101 if (c == '\\')
102 fprintf(out, "\\\\");
103 else if (c == '\n')
104 fprintf(out, "\\n");
105 else
106 putc(c, out);
108 va_end(ap);
109 putc('\n', out);
110 if (ferror(out)) {
111 fprintf(stderr, "Send error; retrying\n");
112 clearerr(out);
113 nanosleep(&(struct timespec){0, nsec}, NULL);
114 putc('\n', out);
115 } else
116 break;
121 * Callback that hides the window the originator is in
123 void
124 cb_hide_toplevel(GtkBuildable *obj, gpointer user_data)
126 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(obj));
128 if (gtk_widget_is_toplevel(toplevel))
129 gtk_widget_hide(toplevel);
133 * Callback that sends a message describing the user selection of a
134 * dialog
136 void
137 cb_send_dialog_selection(GtkBuildable *obj, gpointer user_data)
139 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(obj));
141 if (gtk_widget_is_toplevel(toplevel))
143 if (GTK_IS_FILE_CHOOSER(toplevel)) {
144 send_msg(GTK_BUILDABLE(toplevel),
145 "file",
146 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(toplevel)), NULL);
147 send_msg(GTK_BUILDABLE(toplevel),
148 "folder",
149 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(toplevel)), NULL);
150 } /* responses of other dialogs go here */
155 * Callback that sends in a message the content of the text buffer
156 * passed in user_data
158 void
159 cb_send_text(GtkBuildable *obj, gpointer user_data)
161 GtkTextIter a, b;
163 gtk_text_buffer_get_bounds(user_data, &a, &b);
164 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
168 * Callback that sends in a message the highlighted text from the text
169 * buffer which was passed in user_data
171 void
172 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
174 GtkTextIter a, b;
176 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
177 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
181 * cb_0(), cb_1(), ... call this function to do their work: Send
182 * message(s) whose nature depends on the arguments passed
184 static void
185 do_callback(GtkBuildable *obj, gpointer user_data, const char *section)
187 if (GTK_IS_ENTRY(obj)) {
188 send_msg(obj, section, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
189 } else if (GTK_IS_MENU_ITEM(obj)) {
190 const char *item_name;
191 char dialog_name[BUFLEN];
193 item_name = gtk_buildable_get_name(obj);
194 strncpy(dialog_name, item_name, BUFLEN - 1);
195 strncpy(dialog_name + strlen(item_name), "_dialog", BUFLEN - 1 - strlen(item_name));
196 if (gtk_builder_get_object(user_data, dialog_name) != NULL)
197 gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(user_data, dialog_name)));
198 else
199 send_msg(obj, section, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
200 } else if (GTK_IS_COMBO_BOX_TEXT(obj))
201 send_msg(obj, section, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(obj)), NULL);
202 else if (GTK_IS_RANGE(obj)) {
203 char str[BUFLEN];
205 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
206 send_msg(obj, section, str, NULL);
207 } else if (GTK_IS_TOGGLE_BUTTON(obj))
208 send_msg(obj, section, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj))?"on":"off", NULL);
209 else if (GTK_IS_BUTTON(obj))
210 send_msg(obj, section, "clicked", NULL);
211 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
212 send_msg(obj, section, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
213 else if (GTK_IS_CALENDAR(obj)) {
214 unsigned int year = 0, month = 0, day = 0;
215 char date[11];
217 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
218 snprintf(date, 11, "%04u-%02u-%02u", year, ++month, day);
219 send_msg(obj, section, date, NULL);
220 } else if (GTK_IS_TREE_VIEW_COLUMN(obj))
221 send_msg(obj, section, "clicked", NULL);
222 else if (GTK_IS_TREE_VIEW(obj)) {
223 GtkTreeIter iter;
224 GtkTreePath *path;
225 GtkTreeModel *model;
226 char *path_s;
227 int col;
229 gtk_tree_view_get_cursor(GTK_TREE_VIEW(obj), &path, NULL);
230 model = gtk_tree_view_get_model(GTK_TREE_VIEW(obj));
231 gtk_tree_model_get_iter(model, &iter, path);
232 path_s = gtk_tree_path_to_string(path);
233 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++) {
234 GValue value = G_VALUE_INIT;
235 GType col_type;
236 char str[BUFLEN];
238 gtk_tree_model_get_value(model, &iter, col, &value);
239 col_type = gtk_tree_model_get_column_type(model, col);
240 switch (col_type) {
241 case G_TYPE_INT:
242 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
243 send_msg(obj, section, path_s, str, NULL);
244 break;
245 case G_TYPE_LONG:
246 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
247 send_msg(obj, section, path_s, str, NULL);
248 break;
249 case G_TYPE_INT64:
250 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
251 send_msg(obj, section, path_s, str, NULL);
252 break;
253 case G_TYPE_UINT:
254 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
255 send_msg(obj, section, path_s, str, NULL);
256 break;
257 case G_TYPE_ULONG:
258 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
259 send_msg(obj, section, path_s, str, NULL);
260 break;
261 case G_TYPE_UINT64:
262 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
263 send_msg(obj, section, path_s, str, NULL);
264 break;
265 case G_TYPE_BOOLEAN:
266 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
267 send_msg(obj, section, path_s, str, NULL);
268 break;
269 case G_TYPE_FLOAT:
270 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
271 send_msg(obj, section, path_s, str, NULL);
272 break;
273 case G_TYPE_DOUBLE:
274 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
275 send_msg(obj, section, path_s, str, NULL);
276 break;
277 case G_TYPE_STRING:
278 snprintf(str, BUFLEN, " %d ", col);
279 send_msg(obj, section, path_s, str, g_value_get_string(&value), NULL);
280 break;
281 default:
282 fprintf(stderr, "Column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
283 break;
285 g_value_unset(&value);
287 g_free(path_s);
288 gtk_tree_path_free(path);
290 } else if (GTK_IS_WINDOW(obj))
291 gtk_widget_hide_on_delete(GTK_WIDGET(obj));
292 else
293 fprintf(stderr, "Ignoring callback %s from %s\n", section, gtk_buildable_get_name(obj));
297 * Callback functions cb_0(), cb_1(), cb_2(), and cb_3() for use in
298 * simple widgets. They're all identical except for sending different
299 * section strings.
301 #define MAKE_CB_N(n) \
302 void cb_##n(GtkBuildable *obj, gpointer user_data) \
303 {do_callback(obj, user_data, #n);} \
305 MAKE_CB_N(0)
306 MAKE_CB_N(1)
307 MAKE_CB_N(2)
308 MAKE_CB_N(3)
310 #undef MAKE_CB_N
313 * Store a line from stream into buf, which should have been malloc'd
314 * to bufsize. Enlarge buf and bufsize if necessary.
316 static size_t
317 read_buf(FILE *stream, char **buf, size_t *bufsize)
319 size_t i = 0;
320 int c;
322 for (;;) {
323 c = getc(stream);
324 if (c == EOF) {
325 i = 0;
326 nanosleep(&(struct timespec){0, 1e7}, NULL);
327 continue;
329 if (c == '\n')
330 break;
331 if (i >= *bufsize - 1)
332 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL) {
333 fprintf(stderr, "Out of memory (%s in %s)\n", __func__, __FILE__);
334 abort();
336 if (c == '\\')
337 switch (c = getc(stream)) {
338 case 'n': (*buf)[i++] = '\n'; break;
339 case 'r': (*buf)[i++] = '\r'; break;
340 default: (*buf)[i++] = c; break;
342 else
343 (*buf)[i++] = c;
345 (*buf)[i] = '\0';
346 return i;
350 * Error message
352 static void
353 ignoring_command(const char *msg)
355 fprintf(stderr, "Ignoring command \"%s\"\n", msg);
359 * Parse command pointed to by ud, and act on ui accordingly. Sets
360 * ud->digested = true if done. This will run once per command inside
361 * gtk_main_loop()
363 static gboolean
364 update_ui(struct ui_data *ud)
366 char name[ud->msg_size], type[ud->msg_size], action[ud->msg_size];
367 char *data;
368 int data_start;
369 GObject *obj;
371 data_start = strlen(ud->msg);
372 name[0] = type[0] = action[0] = '\0';
373 sscanf(ud->msg,
374 " %[0-9a-zA-Z_]:%[0-9a-zA-Z_]:%[0-9a-zA-Z_]%*[ \t] %n",
375 name, type, action, &data_start);
376 data = ud->msg + data_start;
377 obj = (gtk_builder_get_object(ud->builder, name));
378 if (eql(action, "main_quit")) {
379 gtk_main_quit();
380 return G_SOURCE_REMOVE;
382 if (obj == NULL)
383 ignoring_command(ud->msg);
384 else if (eql(type, "label")) {
385 if (eql(action, "set_text"))
386 gtk_label_set_text(GTK_LABEL(obj), data);
387 else
388 ignoring_command(ud->msg);
389 } else if (eql(type, "image")) {
390 GtkIconSize size;
392 gtk_image_get_icon_name(GTK_IMAGE(obj), NULL, &size);
393 if (eql(action, "set_from_file"))
394 gtk_image_set_from_file(GTK_IMAGE(obj), data);
395 else if (eql(action, "set_from_icon_name"))
396 gtk_image_set_from_icon_name(GTK_IMAGE(obj), data, size);
397 else
398 ignoring_command(ud->msg);
399 } else if (eql(type, "text_view")) {
400 GtkTextBuffer *textbuf;
401 GtkTextIter iter, a, b;
403 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj));
404 if (eql(action, "set_text"))
405 gtk_text_buffer_set_text(textbuf, data, -1);
406 else if (eql(action, "delete")) {
407 gtk_text_buffer_get_bounds(textbuf, &a, &b);
408 gtk_text_buffer_delete(textbuf, &a, &b);
409 } else if (eql(action, "insert_at_cursor"))
410 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
411 else if (eql(action, "place_cursor")) {
412 if (eql(data, "end"))
413 gtk_text_buffer_get_end_iter(textbuf, &iter);
414 else /* numeric offset */
415 gtk_text_buffer_get_iter_at_offset(textbuf, &iter, strtol(data, NULL, 10));
416 gtk_text_buffer_place_cursor(textbuf, &iter);
417 } else if (eql(action, "place_cursor_at_line")) {
418 gtk_text_buffer_get_iter_at_line(textbuf, &iter, strtol(data, NULL, 10));
419 gtk_text_buffer_place_cursor(textbuf, &iter);
420 } else if (eql(action, "scroll_to_cursor"))
421 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(obj), gtk_text_buffer_get_insert(textbuf), 0.0, 0, 0.0, 0.0);
422 else
423 ignoring_command(ud->msg);
424 } else if (eql(type, "button")) {
425 if (eql(action, "set_label"))
426 gtk_button_set_label(GTK_BUTTON(obj), data);
427 else
428 ignoring_command(ud->msg);
429 } else if (eql(type, "toggle_button") || eql(type, "radio_button") || eql(type, "check_button")) {
430 if (eql(action, "set_label"))
431 gtk_button_set_label(GTK_BUTTON(obj), data);
432 else if (eql(action, "set_active"))
433 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
434 else
435 ignoring_command(ud->msg);
436 } else if (eql(type, "spin_button") || eql(type, "entry")) {
437 if (eql(action, "set_text"))
438 gtk_entry_set_text(GTK_ENTRY(obj), data);
439 else
440 ignoring_command(ud->msg);
441 } else if (eql(type, "scale")) {
442 if (eql(action, "set_value"))
443 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
444 else
445 ignoring_command(ud->msg);
446 } else if (eql(type, "progress_bar")) {
447 if (eql(action, "set_text"))
448 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(obj), data);
449 else if (eql(action, "set_fraction"))
450 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(obj), strtod(data, NULL));
451 else
452 ignoring_command(ud->msg);
453 } else if (eql(type, "spinner")) {
454 if (eql(action, "start"))
455 gtk_spinner_start(GTK_SPINNER(obj));
456 else if (eql(action, "stop"))
457 gtk_spinner_stop(GTK_SPINNER(obj));
458 else
459 ignoring_command(ud->msg);
460 } else if (eql(type, "combo_box_text")) {
461 if (eql(action, "prepend_text"))
462 gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(obj), data);
463 else if (eql(action, "append_text"))
464 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(obj), data);
465 else if (eql(action, "remove"))
466 gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(obj), strtol(data, NULL, 10));
467 else if (eql(action, "insert_text")) {
468 char *position, *text;
470 position = strtok(data, WHITESPACE);
471 text = strtok(NULL, WHITESPACE);
472 gtk_combo_box_text_insert_text(GTK_COMBO_BOX_TEXT(obj), strtol(position, NULL, 10), text);
473 } else
474 ignoring_command(ud->msg);
475 } else if (eql(type, "statusbar")) {
476 if (eql(action, "push"))
477 gtk_statusbar_push(GTK_STATUSBAR(obj), 0, data);
478 else if (eql(action, "pop"))
479 gtk_statusbar_pop(GTK_STATUSBAR(obj), 0);
480 else
481 ignoring_command(ud->msg);
482 } else if (eql(type, "calendar")) {
483 int year = 0, month = 0, day = 0;
485 if (eql(action, "select_date")) {
486 sscanf(data, "%d-%d-%d", &year, &month, &day);
487 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
488 gtk_calendar_select_month(GTK_CALENDAR(obj), --month, year);
489 gtk_calendar_select_day(GTK_CALENDAR(obj), day);
490 } else
491 ignoring_command(ud->msg);
492 } else if (eql(action, "mark_day")) {
493 day = strtol(data, NULL, 10);
494 if (day > 0 && day <= 31)
495 gtk_calendar_mark_day(GTK_CALENDAR(obj), day);
496 else
497 ignoring_command(ud->msg);
498 } else if (eql(action, "clear_marks"))
499 gtk_calendar_clear_marks(GTK_CALENDAR(obj));
500 else
501 ignoring_command(ud->msg);
502 } else if (eql(type, "tree_view")) {
503 GtkTreeIter iter0, iter1;
504 GtkTreeView *view;
505 GtkListStore *store;
506 GtkTreeModel *model;
507 char *tokens, *arg0_s, *arg1_s, *arg2_s, *endptr;
508 int arg0_n = 0, arg1_n = 0;
509 bool arg0_n_valid = false, arg1_n_valid = false;
510 bool iter0_valid = false, iter1_valid = false;
512 view = GTK_TREE_VIEW(obj);
513 model = gtk_tree_view_get_model(view);
514 store = GTK_LIST_STORE(model);
515 if ((tokens = malloc(strlen(data) + 1)) == NULL) {
516 fprintf(stderr, "Out of memory (%s in %s)\n", __func__, __FILE__);
517 abort();
519 strcpy(tokens, data);
520 arg0_s = strtok(tokens, WHITESPACE);
521 arg1_s = strtok(NULL, WHITESPACE);
522 arg2_s = strtok(NULL, "\n");
523 errno = 0;
524 endptr = NULL;
525 iter0_valid =
526 arg0_s != NULL &&
527 (arg0_n_valid = (arg0_n = strtol(arg0_s, &endptr, 10)) >= 0 &&
528 errno == 0 && endptr != arg0_s) &&
529 gtk_tree_model_get_iter_from_string(model, &iter0, arg0_s);
530 errno = 0;
531 endptr = NULL;
532 iter1_valid =
533 arg1_s != NULL &&
534 (arg1_n_valid = (arg1_n = strtol(arg1_s, &endptr, 10)) >= 0 &&
535 errno == 0 && endptr != arg1_s) &&
536 gtk_tree_model_get_iter_from_string(model, &iter1, arg1_s);
537 if (eql(action, "set") && iter0_valid && arg1_n_valid &&
538 arg1_n < gtk_tree_model_get_n_columns(model)) {
539 GType col_type;
540 long long int n;
541 double d;
543 col_type = gtk_tree_model_get_column_type(model, arg1_n);
544 switch (col_type) {
545 case G_TYPE_BOOLEAN:
546 case G_TYPE_INT:
547 case G_TYPE_LONG:
548 case G_TYPE_INT64:
549 case G_TYPE_UINT:
550 case G_TYPE_ULONG:
551 case G_TYPE_UINT64:
552 errno = 0;
553 endptr = NULL;
554 n = strtoll(arg2_s, &endptr, 10);
555 if (!errno && endptr != arg2_s)
556 gtk_list_store_set(store, &iter0, arg1_n, n, -1);
557 else
558 ignoring_command(ud->msg);
559 break;
560 case G_TYPE_FLOAT:
561 case G_TYPE_DOUBLE:
562 errno = 0;
563 endptr = NULL;
564 d = strtod(arg2_s, &endptr);
565 if (!errno && endptr != arg2_s)
566 gtk_list_store_set(store, &iter0, arg1_n, d, -1);
567 else
568 ignoring_command(ud->msg);
569 gtk_list_store_set(store, &iter0, arg1_n, strtod(arg2_s, NULL), -1);
570 break;
571 case G_TYPE_STRING:
572 gtk_list_store_set(store, &iter0, arg1_n, arg2_s, -1);
573 break;
574 default:
575 fprintf(stderr, "Column %d: %s not implemented\n", arg1_n, g_type_name(col_type));
576 break;
578 } else if (eql(action, "scroll") && arg0_n_valid && arg1_n_valid)
579 gtk_tree_view_scroll_to_cell (view,
580 gtk_tree_path_new_from_string(arg0_s),
581 gtk_tree_view_get_column(view, arg1_n),
582 0, 0.0, 0.0);
583 else if (eql(action, "insert_row"))
584 if (eql(arg0_s, "end"))
585 gtk_list_store_insert_before(store, &iter1, NULL);
586 else if (iter0_valid)
587 gtk_list_store_insert_before(store, &iter1, &iter0);
588 else
589 ignoring_command(ud->msg);
590 else if (eql(action, "move_row") && iter0_valid)
591 if (eql(arg1_s, "end"))
592 gtk_list_store_move_before(store, &iter0, NULL);
593 else if (iter1_valid)
594 gtk_list_store_move_before(store, &iter0, &iter1);
595 else
596 ignoring_command(ud->msg);
597 else if (eql(action, "remove_row") && iter0_valid)
598 gtk_list_store_remove(store, &iter0);
599 else
600 ignoring_command(ud->msg);
601 free(tokens);
602 } else if (eql(type, "window")) {
603 if (eql(action, "set_title"))
604 gtk_window_set_title(GTK_WINDOW(obj), data);
605 else
606 ignoring_command(ud->msg);
607 } else
608 ignoring_command(ud->msg);
609 ud->msg_digested = true;
610 return G_SOURCE_REMOVE;
614 * Read lines from global stream "in" and perform the appropriate
615 * actions on the GUI
617 static void *
618 digest_msg(void *builder)
620 for (;;) {
621 char first_char;
622 struct ui_data ud;
624 if ((ud.msg = malloc(ud.msg_size = 32)) == NULL ) {
625 fprintf(stderr, "Out of memory (%s in %s)\n", __func__, __FILE__);
626 abort();
628 read_buf(in, &ud.msg, &ud.msg_size);
629 sscanf(ud.msg, " %c", &first_char);
630 ud.builder = builder;
631 if (first_char != '#') {
632 ud.msg_digested = false;
633 gdk_threads_add_timeout(1, (GSourceFunc)update_ui, &ud);
634 while (!ud.msg_digested)
635 nanosleep(&(struct timespec){0, 1e6}, NULL);
637 free(ud.msg);
639 return NULL;
643 * Create a fifo if necessary, and open it. Give up if the file
644 * exists but is not a fifo
646 static FILE *
647 fifo(const char *name, const char *mode)
649 struct stat sb;
650 int fd;
651 FILE *stream;
653 stat(name, &sb);
654 if (!S_ISFIFO(sb.st_mode))
655 if (name != NULL && mkfifo(name, 0666) != 0) {
656 perror("making fifo");
657 exit(EXIT_FAILURE);
659 switch (mode[0]) {
660 case 'r':
661 if (name == NULL)
662 stream = stdin;
663 else {
664 fd = open(name, O_RDWR | O_NONBLOCK);
665 if (fd < 0) {
666 perror("opening fifo");
667 exit(EXIT_FAILURE);
669 stream = fdopen(fd, "r");
671 break;
672 case 'w':
673 if (name == NULL)
674 stream = stdout;
675 else {
676 /* fopen blocks if there is no reader so here is one */
677 fd = open(name, O_RDONLY | O_NONBLOCK);
678 if (fd < 0) {
679 perror("opening fifo");
680 exit(EXIT_FAILURE);
682 stream = fopen(name, "w");
683 /* unblocking done */
684 close(fd);
686 break;
687 default:
688 abort();
689 break;
691 if (stream == NULL) {
692 perror("opening fifo");
693 exit(EXIT_FAILURE);
694 } else {
695 setvbuf(stream, NULL, _IOLBF, 0);
696 return stream;
701 main(int argc, char *argv[])
703 char opt, *ui_file = NULL;
704 char *in_fifo = NULL, *out_fifo = NULL;
705 GtkBuilder *builder;
706 pthread_t receiver;
707 GError *error = NULL;
708 GObject *window = NULL;
710 in = NULL;
711 out = NULL;
712 while ((opt = getopt(argc, argv, "hi:o:u:V")) != -1) {
713 switch (opt) {
714 case 'i': in_fifo = optarg; break;
715 case 'o': out_fifo = optarg; break;
716 case 'u': ui_file = optarg; break;
717 case 'V': version(); break;
718 case '?':
719 case 'h':
720 default: usage(argv); break;
723 if (ui_file == NULL)
724 ui_file = "pipeglade.ui";
725 gtk_init(0, NULL);
726 builder = gtk_builder_new();
727 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0) {
728 fprintf(stderr, "%s\n", error->message);
729 exit(EXIT_FAILURE);
731 in = fifo(in_fifo, "r");
732 out = fifo(out_fifo, "w");
733 gtk_builder_connect_signals(builder, builder);
734 pthread_create(&receiver, NULL, digest_msg, (void*) builder);
735 window = (gtk_builder_get_object(builder, "window"));
736 if (!GTK_IS_WIDGET(window)) {
737 fprintf(stderr, "No toplevel window named \'window\'\n");
738 exit(EXIT_FAILURE);
740 gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "window")));
741 gtk_main();
742 if (in != stdin) {
743 fclose(in);
744 unlink(in_fifo);
746 if (out != stdout) {
747 fclose(out);
748 unlink(out_fifo);
750 pthread_cancel(receiver);
751 pthread_join(receiver, NULL);
752 exit(EXIT_SUCCESS);