Makefile: simplify; fix typo
[pipeglade.git] / pipeglade.c
blob5d55e64b17e8265ef88240bd1e977d2f00d3ca62
1 /*
2 * Copyright (c) 2014, 2015 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 "1.2.0"
40 #define BUFLEN 256
41 #define WHITESPACE " \t\n"
43 static FILE *in;
44 static 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 "[-G] "
62 "[-V]\n",
63 argv[0]);
64 exit(EXIT_SUCCESS);
67 static void
68 version(void)
70 printf(VERSION "\n");
71 exit(EXIT_SUCCESS);
74 static void
75 gtk_versions(void)
77 printf("GTK+ v%d.%d.%d (running v%d.%d.%d)\n",
78 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
79 gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version());
80 exit(EXIT_SUCCESS);
83 static bool
84 eql(const char *s1, const char *s2)
86 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
90 * Send GUI feedback to global stream "out". The message format is
91 * "<origin>:<section> <data ...>". The variadic arguments are
92 * strings; last argument must be NULL. We're being patient with
93 * receivers which may intermittently close their end of the fifo, and
94 * make a couple of retries if an error occurs.
96 static void
97 send_msg(GtkBuildable *obj, const char *section, ...)
99 va_list ap;
100 char *data;
101 long nsec;
103 for (nsec = 1e6; nsec < 1e9; nsec <<= 3) {
104 va_start(ap, section);
105 fprintf(out, "%s:%s ", gtk_buildable_get_name(obj), section);
106 while ((data = va_arg(ap, char *)) != NULL) {
107 size_t i = 0;
108 char c;
110 while ((c = data[i++]) != '\0')
111 if (c == '\\')
112 fprintf(out, "\\\\");
113 else if (c == '\n')
114 fprintf(out, "\\n");
115 else
116 putc(c, out);
118 va_end(ap);
119 putc('\n', out);
120 if (ferror(out)) {
121 fprintf(stderr, "Send error; retrying\n");
122 clearerr(out);
123 nanosleep(&(struct timespec){0, nsec}, NULL);
124 putc('\n', out);
125 } else
126 break;
131 * Callback that hides the window the originator is in
133 void
134 cb_hide_toplevel(GtkBuildable *obj, gpointer user_data)
136 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(obj));
138 if (gtk_widget_is_toplevel(toplevel))
139 gtk_widget_hide(toplevel);
143 * Callback that sends a message describing the user selection of a
144 * dialog
146 void
147 cb_send_dialog_selection(GtkBuildable *obj, gpointer user_data)
149 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(obj));
151 if (gtk_widget_is_toplevel(toplevel))
153 if (GTK_IS_FILE_CHOOSER(toplevel)) {
154 send_msg(GTK_BUILDABLE(toplevel),
155 "file",
156 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(toplevel)), NULL);
157 send_msg(GTK_BUILDABLE(toplevel),
158 "folder",
159 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(toplevel)), NULL);
160 } /* responses of other dialogs go here */
165 * Callback that sends in a message the content of the text buffer
166 * passed in user_data
168 void
169 cb_send_text(GtkBuildable *obj, gpointer user_data)
171 GtkTextIter a, b;
173 gtk_text_buffer_get_bounds(user_data, &a, &b);
174 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
178 * Callback that sends in a message the highlighted text from the text
179 * buffer which was passed in user_data
181 void
182 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
184 GtkTextIter a, b;
186 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
187 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
190 struct selection_data {
191 GtkBuildable *buildable;
192 const char *section;
196 * send_tree_row_msg serves as an argument for
197 * gtk_tree_selection_selected_foreach()
199 static void
200 send_tree_row_msg(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, struct selection_data *data)
202 char *path_s;
203 const char *section;
204 int col;
205 GtkBuildable *obj;
207 obj = (data->buildable);
208 section = data->section;
209 path_s = gtk_tree_path_to_string(path);
210 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++) {
211 GValue value = G_VALUE_INIT;
212 GType col_type;
213 char str[BUFLEN];
215 gtk_tree_model_get_value(model, iter, col, &value);
216 col_type = gtk_tree_model_get_column_type(model, col);
217 switch (col_type) {
218 case G_TYPE_INT:
219 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
220 send_msg(obj, section, path_s, str, NULL);
221 break;
222 case G_TYPE_LONG:
223 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
224 send_msg(obj, section, path_s, str, NULL);
225 break;
226 case G_TYPE_INT64:
227 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
228 send_msg(obj, section, path_s, str, NULL);
229 break;
230 case G_TYPE_UINT:
231 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
232 send_msg(obj, section, path_s, str, NULL);
233 break;
234 case G_TYPE_ULONG:
235 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
236 send_msg(obj, section, path_s, str, NULL);
237 break;
238 case G_TYPE_UINT64:
239 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
240 send_msg(obj, section, path_s, str, NULL);
241 break;
242 case G_TYPE_BOOLEAN:
243 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
244 send_msg(obj, section, path_s, str, NULL);
245 break;
246 case G_TYPE_FLOAT:
247 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
248 send_msg(obj, section, path_s, str, NULL);
249 break;
250 case G_TYPE_DOUBLE:
251 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
252 send_msg(obj, section, path_s, str, NULL);
253 break;
254 case G_TYPE_STRING:
255 snprintf(str, BUFLEN, " %d ", col);
256 send_msg(obj, section, path_s, str, g_value_get_string(&value), NULL);
257 break;
258 default:
259 fprintf(stderr, "Column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
260 break;
262 g_value_unset(&value);
264 g_free(path_s);
268 * cb_0(), cb_1(), etc. call this function to do their work: Send
269 * message(s) whose nature depends on the arguments passed. A call to
270 * this function will also be initiated by the user command
271 * ...:force_cb.
273 static void
274 do_callback(GtkBuildable *obj, gpointer user_data, const char *section)
276 char str[BUFLEN];
277 const char *item_name;
278 GdkRGBA color;
279 GtkTreeView *tree_view;
280 unsigned int year = 0, month = 0, day = 0;
282 if (GTK_IS_ENTRY(obj)) {
283 send_msg(obj, section, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
284 } else if (GTK_IS_MENU_ITEM(obj)) {
285 item_name = gtk_buildable_get_name(obj);
286 strncpy(str, item_name, BUFLEN - 1);
287 strncpy(str + strlen(item_name), "_dialog", BUFLEN - 1 - strlen(item_name));
288 if (gtk_builder_get_object(user_data, str) != NULL)
289 gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(user_data, str)));
290 else
291 send_msg(obj, section, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
292 } else if (GTK_IS_COMBO_BOX_TEXT(obj))
293 send_msg(obj, section, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(obj)), NULL);
294 else if (GTK_IS_RANGE(obj)) {
295 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
296 send_msg(obj, section, str, NULL);
297 } else if (GTK_IS_TOGGLE_BUTTON(obj))
298 send_msg(obj, section, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj))?"on":"off", NULL);
299 else if (GTK_IS_COLOR_BUTTON(obj)) {
300 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
301 send_msg(obj, section, gdk_rgba_to_string(&color), NULL);
302 } else if (GTK_IS_FONT_BUTTON(obj))
303 send_msg(obj, section, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
304 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
305 send_msg(obj, section, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
306 else if (GTK_IS_BUTTON(obj))
307 send_msg(obj, section, "clicked", NULL);
308 else if (GTK_IS_CALENDAR(obj)) {
309 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
310 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
311 send_msg(obj, section, str, NULL);
312 } else if (GTK_IS_TREE_VIEW_COLUMN(obj))
313 send_msg(obj, section, "clicked", NULL);
314 else if (GTK_IS_TREE_SELECTION(obj)) {
315 tree_view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
316 send_msg(GTK_BUILDABLE(tree_view), section, "clicked", NULL);
317 gtk_tree_selection_selected_foreach(
318 GTK_TREE_SELECTION(obj),
319 (GtkTreeSelectionForeachFunc)send_tree_row_msg,
320 &(struct selection_data){GTK_BUILDABLE(tree_view),
321 section});
322 } else if (GTK_IS_WINDOW(obj))
323 gtk_widget_hide_on_delete(GTK_WIDGET(obj));
324 else
325 fprintf(stderr, "Ignoring callback %s from %s\n", section, gtk_buildable_get_name(obj));
329 * Callback functions cb_0(), cb_1(), cb_2(), and cb_3() for use in
330 * simple widgets. They're all identical except for sending different
331 * section strings.
333 #define MAKE_CB_N(n) \
334 void cb_##n(GtkBuildable *obj, gpointer user_data) \
335 {do_callback(obj, user_data, #n);} \
337 MAKE_CB_N(0)
338 MAKE_CB_N(1)
339 MAKE_CB_N(2)
340 MAKE_CB_N(3)
342 #undef MAKE_CB_N
345 * Store a line from stream into buf, which should have been malloc'd
346 * to bufsize. Enlarge buf and bufsize if necessary.
348 static size_t
349 read_buf(FILE *stream, char **buf, size_t *bufsize)
351 size_t i = 0;
352 int c;
354 for (;;) {
355 c = getc(stream);
356 if (c == EOF) {
357 i = 0;
358 nanosleep(&(struct timespec){0, 1e7}, NULL);
359 continue;
361 if (c == '\n')
362 break;
363 if (i >= *bufsize - 1)
364 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL) {
365 fprintf(stderr, "Out of memory (%s in %s)\n", __func__, __FILE__);
366 abort();
368 if (c == '\\')
369 switch (c = getc(stream)) {
370 case 'n': (*buf)[i++] = '\n'; break;
371 case 'r': (*buf)[i++] = '\r'; break;
372 default: (*buf)[i++] = c; break;
374 else
375 (*buf)[i++] = c;
377 (*buf)[i] = '\0';
378 return i;
382 * Error message
384 static void
385 ign_cmd(GType type, const char *msg)
387 const char *name, *pad = " ";
389 if (type == G_TYPE_INVALID) {
390 name = "";
391 pad = "";
393 else
394 name = g_type_name(type);
395 fprintf(stderr, "Ignoring %s%scommand \"%s\"\n", name, pad, msg);
399 * Parse command pointed to by ud, and act on ui accordingly. Sets
400 * ud->digested = true if done. Runs once per command inside
401 * gtk_main_loop()
403 static gboolean
404 update_ui(struct ui_data *ud)
406 char name[ud->msg_size], action[ud->msg_size];
407 char *data;
408 int data_start;
409 GObject *obj = NULL;
410 GType type = G_TYPE_INVALID;
412 data_start = strlen(ud->msg);
413 name[0] = action[0] = '\0';
414 sscanf(ud->msg,
415 " %[0-9a-zA-Z_]:%[0-9a-zA-Z_]%*[ \t] %n",
416 name, action, &data_start);
417 if (eql(action, "main_quit")) {
418 gtk_main_quit();
419 ud->msg_digested = true;
420 return G_SOURCE_REMOVE;
422 obj = (gtk_builder_get_object(ud->builder, name));
423 if (obj == NULL) {
424 ign_cmd(type, ud->msg);
425 ud->msg_digested = true;
426 return G_SOURCE_REMOVE;
428 if (eql(action, "force_cb")) {
429 do_callback(GTK_BUILDABLE(obj), NULL, "forced");
430 ud->msg_digested = true;
431 return G_SOURCE_REMOVE;
433 data = ud->msg + data_start;
434 if (eql(action, "set_sensitive")) {
435 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
436 ud->msg_digested = true;
437 return G_SOURCE_REMOVE;
438 } else if (eql(action, "set_visible")) {
439 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
440 ud->msg_digested = true;
441 return G_SOURCE_REMOVE;
443 type = G_TYPE_FROM_INSTANCE(obj);
444 if (type == GTK_TYPE_LABEL) {
445 if (eql(action, "set_text"))
446 gtk_label_set_text(GTK_LABEL(obj), data);
447 else
448 ign_cmd(type, ud->msg);
449 } else if (type == GTK_TYPE_IMAGE) {
450 GtkIconSize size;
452 gtk_image_get_icon_name(GTK_IMAGE(obj), NULL, &size);
453 if (eql(action, "set_from_file"))
454 gtk_image_set_from_file(GTK_IMAGE(obj), data);
455 else if (eql(action, "set_from_icon_name"))
456 gtk_image_set_from_icon_name(GTK_IMAGE(obj), data, size);
457 else
458 ign_cmd(type, ud->msg);
459 } else if (type == GTK_TYPE_TEXT_VIEW) {
460 GtkTextBuffer *textbuf;
461 GtkTextIter iter, a, b;
463 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj));
464 if (eql(action, "set_text"))
465 gtk_text_buffer_set_text(textbuf, data, -1);
466 else if (eql(action, "delete")) {
467 gtk_text_buffer_get_bounds(textbuf, &a, &b);
468 gtk_text_buffer_delete(textbuf, &a, &b);
469 } else if (eql(action, "insert_at_cursor"))
470 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
471 else if (eql(action, "place_cursor")) {
472 if (eql(data, "end"))
473 gtk_text_buffer_get_end_iter(textbuf, &iter);
474 else /* numeric offset */
475 gtk_text_buffer_get_iter_at_offset(textbuf, &iter, strtol(data, NULL, 10));
476 gtk_text_buffer_place_cursor(textbuf, &iter);
477 } else if (eql(action, "place_cursor_at_line")) {
478 gtk_text_buffer_get_iter_at_line(textbuf, &iter, strtol(data, NULL, 10));
479 gtk_text_buffer_place_cursor(textbuf, &iter);
480 } else if (eql(action, "scroll_to_cursor"))
481 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(obj), gtk_text_buffer_get_insert(textbuf), 0.0, 0, 0.0, 0.0);
482 else
483 ign_cmd(type, ud->msg);
484 } else if (type == GTK_TYPE_BUTTON) {
485 if (eql(action, "set_label"))
486 gtk_button_set_label(GTK_BUTTON(obj), data);
487 else
488 ign_cmd(type, ud->msg);
489 } else if (type == GTK_TYPE_FILE_CHOOSER_DIALOG) {
490 if (eql(action, "set_filename"))
491 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
492 else if (eql(action, "set_current_name"))
493 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(obj), data);
494 else
495 ign_cmd(type, ud->msg);
496 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON) {
497 if (eql(action, "set_filename"))
498 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
499 else
500 ign_cmd(type, ud->msg);
501 } else if (type == GTK_TYPE_COLOR_BUTTON) {
502 if (eql(action, "set_color")) {
503 GdkRGBA color;
505 gdk_rgba_parse(&color, data);
506 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
507 } else
508 ign_cmd(type, ud->msg);
509 } else if (type == GTK_TYPE_FONT_BUTTON) {
510 if (eql(action, "set_font_name"))
511 gtk_font_button_set_font_name(GTK_FONT_BUTTON(obj), data);
512 else
513 ign_cmd(type, ud->msg);
514 } else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON) {
515 if (eql(action, "set_label"))
516 gtk_button_set_label(GTK_BUTTON(obj), data);
517 else if (eql(action, "set_active"))
518 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
519 else
520 ign_cmd(type, ud->msg);
521 } else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY) {
522 if (eql(action, "set_text"))
523 gtk_entry_set_text(GTK_ENTRY(obj), data);
524 else
525 ign_cmd(type, ud->msg);
526 } else if (type == GTK_TYPE_SCALE) {
527 if (eql(action, "set_value"))
528 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
529 else
530 ign_cmd(type, ud->msg);
531 } else if (type == GTK_TYPE_PROGRESS_BAR) {
532 if (eql(action, "set_text"))
533 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(obj), data);
534 else if (eql(action, "set_fraction"))
535 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(obj), strtod(data, NULL));
536 else
537 ign_cmd(type, ud->msg);
538 } else if (type == GTK_TYPE_SPINNER) {
539 if (eql(action, "start"))
540 gtk_spinner_start(GTK_SPINNER(obj));
541 else if (eql(action, "stop"))
542 gtk_spinner_stop(GTK_SPINNER(obj));
543 else
544 ign_cmd(type, ud->msg);
545 } else if (type == GTK_TYPE_COMBO_BOX_TEXT) {
546 if (eql(action, "prepend_text"))
547 gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(obj), data);
548 else if (eql(action, "append_text"))
549 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(obj), data);
550 else if (eql(action, "remove"))
551 gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(obj), strtol(data, NULL, 10));
552 else if (eql(action, "insert_text")) {
553 char *position, *text;
555 position = strtok(data, WHITESPACE);
556 text = strtok(NULL, WHITESPACE);
557 gtk_combo_box_text_insert_text(GTK_COMBO_BOX_TEXT(obj), strtol(position, NULL, 10), text);
558 } else
559 ign_cmd(type, ud->msg);
560 } else if (type == GTK_TYPE_STATUSBAR) {
561 if (eql(action, "push"))
562 gtk_statusbar_push(GTK_STATUSBAR(obj), 0, data);
563 else if (eql(action, "pop"))
564 gtk_statusbar_pop(GTK_STATUSBAR(obj), 0);
565 else
566 ign_cmd(type, ud->msg);
567 } else if (type == GTK_TYPE_CALENDAR) {
568 int year = 0, month = 0, day = 0;
570 if (eql(action, "select_date")) {
571 sscanf(data, "%d-%d-%d", &year, &month, &day);
572 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
573 gtk_calendar_select_month(GTK_CALENDAR(obj), --month, year);
574 gtk_calendar_select_day(GTK_CALENDAR(obj), day);
575 } else
576 ign_cmd(type, ud->msg);
577 } else if (eql(action, "mark_day")) {
578 day = strtol(data, NULL, 10);
579 if (day > 0 && day <= 31)
580 gtk_calendar_mark_day(GTK_CALENDAR(obj), day);
581 else
582 ign_cmd(type, ud->msg);
583 } else if (eql(action, "clear_marks"))
584 gtk_calendar_clear_marks(GTK_CALENDAR(obj));
585 else
586 ign_cmd(type, ud->msg);
587 } else if (type == GTK_TYPE_TREE_VIEW) {
588 GtkTreeIter iter0, iter1;
589 GtkTreeView *view;
590 GtkListStore *store;
591 GtkTreeModel *model;
592 char *tokens, *arg0_s, *arg1_s, *arg2_s, *endptr;
593 int arg0_n = 0, arg1_n = 0;
594 bool arg0_n_valid = false, arg1_n_valid = false;
595 bool iter0_valid = false, iter1_valid = false;
597 view = GTK_TREE_VIEW(obj);
598 model = gtk_tree_view_get_model(view);
599 store = GTK_LIST_STORE(model);
600 if ((tokens = malloc(strlen(data) + 1)) == NULL) {
601 fprintf(stderr, "Out of memory (%s in %s)\n", __func__, __FILE__);
602 abort();
604 strcpy(tokens, data);
605 arg0_s = strtok(tokens, WHITESPACE);
606 arg1_s = strtok(NULL, WHITESPACE);
607 arg2_s = strtok(NULL, "\n");
608 errno = 0;
609 endptr = NULL;
610 iter0_valid =
611 arg0_s != NULL &&
612 (arg0_n_valid = (arg0_n = strtol(arg0_s, &endptr, 10)) >= 0 &&
613 errno == 0 && endptr != arg0_s) &&
614 gtk_tree_model_get_iter_from_string(model, &iter0, arg0_s);
615 errno = 0;
616 endptr = NULL;
617 iter1_valid =
618 arg1_s != NULL &&
619 (arg1_n_valid = (arg1_n = strtol(arg1_s, &endptr, 10)) >= 0 &&
620 errno == 0 && endptr != arg1_s) &&
621 gtk_tree_model_get_iter_from_string(model, &iter1, arg1_s);
622 if (eql(action, "set") && iter0_valid && arg1_n_valid &&
623 arg1_n < gtk_tree_model_get_n_columns(model)) {
624 GType col_type;
625 long long int n;
626 double d;
628 col_type = gtk_tree_model_get_column_type(model, arg1_n);
629 switch (col_type) {
630 case G_TYPE_BOOLEAN:
631 case G_TYPE_INT:
632 case G_TYPE_LONG:
633 case G_TYPE_INT64:
634 case G_TYPE_UINT:
635 case G_TYPE_ULONG:
636 case G_TYPE_UINT64:
637 errno = 0;
638 endptr = NULL;
639 n = strtoll(arg2_s, &endptr, 10);
640 if (!errno && endptr != arg2_s)
641 gtk_list_store_set(store, &iter0, arg1_n, n, -1);
642 else
643 ign_cmd(type, ud->msg);
644 break;
645 case G_TYPE_FLOAT:
646 case G_TYPE_DOUBLE:
647 errno = 0;
648 endptr = NULL;
649 d = strtod(arg2_s, &endptr);
650 if (!errno && endptr != arg2_s)
651 gtk_list_store_set(store, &iter0, arg1_n, d, -1);
652 else
653 ign_cmd(type, ud->msg);
654 gtk_list_store_set(store, &iter0, arg1_n, strtod(arg2_s, NULL), -1);
655 break;
656 case G_TYPE_STRING:
657 gtk_list_store_set(store, &iter0, arg1_n, arg2_s, -1);
658 break;
659 default:
660 fprintf(stderr, "Column %d: %s not implemented\n", arg1_n, g_type_name(col_type));
661 break;
663 } else if (eql(action, "scroll") && arg0_n_valid && arg1_n_valid)
664 gtk_tree_view_scroll_to_cell (view,
665 gtk_tree_path_new_from_string(arg0_s),
666 gtk_tree_view_get_column(view, arg1_n),
667 0, 0.0, 0.0);
668 else if (eql(action, "insert_row"))
669 if (eql(arg0_s, "end"))
670 gtk_list_store_insert_before(store, &iter1, NULL);
671 else if (iter0_valid)
672 gtk_list_store_insert_before(store, &iter1, &iter0);
673 else
674 ign_cmd(type, ud->msg);
675 else if (eql(action, "move_row") && iter0_valid)
676 if (eql(arg1_s, "end"))
677 gtk_list_store_move_before(store, &iter0, NULL);
678 else if (iter1_valid)
679 gtk_list_store_move_before(store, &iter0, &iter1);
680 else
681 ign_cmd(type, ud->msg);
682 else if (eql(action, "remove_row") && iter0_valid)
683 gtk_list_store_remove(store, &iter0);
684 else
685 ign_cmd(type, ud->msg);
686 free(tokens);
687 } else if (type == GTK_TYPE_WINDOW) {
688 if (eql(action, "set_title"))
689 gtk_window_set_title(GTK_WINDOW(obj), data);
690 else
691 ign_cmd(type, ud->msg);
692 } else
693 ign_cmd(type, ud->msg);
694 ud->msg_digested = true;
695 return G_SOURCE_REMOVE;
699 * Read lines from global stream "in" and perform the appropriate
700 * actions on the GUI
702 static void *
703 digest_msg(void *builder)
705 for (;;) {
706 char first_char;
707 struct ui_data ud;
709 if ((ud.msg = malloc(ud.msg_size = 32)) == NULL ) {
710 fprintf(stderr, "Out of memory (%s in %s)\n", __func__, __FILE__);
711 abort();
713 read_buf(in, &ud.msg, &ud.msg_size);
714 sscanf(ud.msg, " %c", &first_char);
715 ud.builder = builder;
716 if (first_char != '#') {
717 ud.msg_digested = false;
718 gdk_threads_add_timeout(1, (GSourceFunc)update_ui, &ud);
719 while (!ud.msg_digested)
720 nanosleep(&(struct timespec){0, 1e6}, NULL);
722 free(ud.msg);
724 return NULL;
728 * Create a fifo if necessary, and open it. Give up if the file
729 * exists but is not a fifo
731 static FILE *
732 fifo(const char *name, const char *mode)
734 struct stat sb;
735 int fd;
736 FILE *stream;
738 stat(name, &sb);
739 if (!S_ISFIFO(sb.st_mode))
740 if (name != NULL && mkfifo(name, 0666) != 0) {
741 perror("making fifo");
742 exit(EXIT_FAILURE);
744 switch (mode[0]) {
745 case 'r':
746 if (name == NULL)
747 stream = stdin;
748 else {
749 fd = open(name, O_RDWR | O_NONBLOCK);
750 if (fd < 0) {
751 perror("opening fifo");
752 exit(EXIT_FAILURE);
754 stream = fdopen(fd, "r");
756 break;
757 case 'w':
758 if (name == NULL)
759 stream = stdout;
760 else {
761 /* fopen blocks if there is no reader so here is one */
762 fd = open(name, O_RDONLY | O_NONBLOCK);
763 if (fd < 0) {
764 perror("opening fifo");
765 exit(EXIT_FAILURE);
767 stream = fopen(name, "w");
768 /* unblocking done */
769 close(fd);
771 break;
772 default:
773 abort();
774 break;
776 if (stream == NULL) {
777 perror("opening fifo");
778 exit(EXIT_FAILURE);
779 } else {
780 setvbuf(stream, NULL, _IOLBF, 0);
781 return stream;
786 main(int argc, char *argv[])
788 char opt, *ui_file = NULL;
789 char *in_fifo = NULL, *out_fifo = NULL;
790 GtkBuilder *builder;
791 pthread_t receiver;
792 GError *error = NULL;
793 GObject *window = NULL;
795 in = NULL;
796 out = NULL;
797 while ((opt = getopt(argc, argv, "hi:o:u:GV")) != -1) {
798 switch (opt) {
799 case 'i': in_fifo = optarg; break;
800 case 'o': out_fifo = optarg; break;
801 case 'u': ui_file = optarg; break;
802 case 'G': gtk_versions(); break;
803 case 'V': version(); break;
804 case '?':
805 case 'h':
806 default: usage(argv); break;
809 if (argv[optind] != NULL) {
810 fprintf(stderr, "illegal parameter '%s'\n", argv[optind]);
811 usage(argv);
813 if (ui_file == NULL)
814 ui_file = "pipeglade.ui";
815 gtk_init(0, NULL);
816 builder = gtk_builder_new();
817 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0) {
818 fprintf(stderr, "%s\n", error->message);
819 exit(EXIT_FAILURE);
821 in = fifo(in_fifo, "r");
822 out = fifo(out_fifo, "w");
823 gtk_builder_connect_signals(builder, builder);
824 pthread_create(&receiver, NULL, digest_msg, (void*) builder);
825 window = (gtk_builder_get_object(builder, "window"));
826 if (!GTK_IS_WIDGET(window)) {
827 fprintf(stderr, "No toplevel window named \'window\'\n");
828 exit(EXIT_FAILURE);
830 gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "window")));
831 gtk_main();
832 if (in != stdin) {
833 fclose(in);
834 unlink(in_fifo);
836 if (out != stdout) {
837 fclose(out);
838 unlink(out_fifo);
840 pthread_cancel(receiver);
841 pthread_join(receiver, NULL);
842 exit(EXIT_SUCCESS);