Add support for GtkSwitch
[pipeglade.git] / pipeglade.c
blob43f4f0bb97804fe341a7318e65ce2190be97e8a8
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 "2.0.1"
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 (void)user_data;
139 if (gtk_widget_is_toplevel(toplevel))
140 gtk_widget_hide(toplevel);
144 * Callback that sends a message describing the user selection of a
145 * dialog
147 void
148 cb_send_dialog_selection(GtkBuildable *obj, gpointer user_data)
150 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(obj));
152 (void)user_data;
153 if (gtk_widget_is_toplevel(toplevel))
155 if (GTK_IS_FILE_CHOOSER(toplevel)) {
156 send_msg(GTK_BUILDABLE(toplevel),
157 "file",
158 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(toplevel)), NULL);
159 send_msg(GTK_BUILDABLE(toplevel),
160 "folder",
161 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(toplevel)), NULL);
162 } /* responses of other dialogs go here */
167 * Callback that sends in a message the content of the text buffer
168 * passed in user_data
170 void
171 cb_send_text(GtkBuildable *obj, gpointer user_data)
173 GtkTextIter a, b;
175 gtk_text_buffer_get_bounds(user_data, &a, &b);
176 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
180 * Callback that sends in a message the highlighted text from the text
181 * buffer which was passed in user_data
183 void
184 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
186 GtkTextIter a, b;
188 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
189 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
192 struct selection_data {
193 GtkBuildable *buildable;
194 const char *section;
198 * send_tree_row_msg serves as an argument for
199 * gtk_tree_selection_selected_foreach()
201 static void
202 send_tree_row_msg(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, struct selection_data *data)
204 char *path_s = gtk_tree_path_to_string(path);
205 const char *section = data->section;
206 GtkBuildable *obj = data->buildable;
207 int col;
209 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++) {
210 GValue value = G_VALUE_INIT;
211 GType col_type;
212 char str[BUFLEN];
214 gtk_tree_model_get_value(model, iter, col, &value);
215 col_type = gtk_tree_model_get_column_type(model, col);
216 switch (col_type) {
217 case G_TYPE_INT:
218 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
219 send_msg(obj, section, path_s, str, NULL);
220 break;
221 case G_TYPE_LONG:
222 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
223 send_msg(obj, section, path_s, str, NULL);
224 break;
225 case G_TYPE_INT64:
226 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
227 send_msg(obj, section, path_s, str, NULL);
228 break;
229 case G_TYPE_UINT:
230 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
231 send_msg(obj, section, path_s, str, NULL);
232 break;
233 case G_TYPE_ULONG:
234 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
235 send_msg(obj, section, path_s, str, NULL);
236 break;
237 case G_TYPE_UINT64:
238 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
239 send_msg(obj, section, path_s, str, NULL);
240 break;
241 case G_TYPE_BOOLEAN:
242 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
243 send_msg(obj, section, path_s, str, NULL);
244 break;
245 case G_TYPE_FLOAT:
246 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
247 send_msg(obj, section, path_s, str, NULL);
248 break;
249 case G_TYPE_DOUBLE:
250 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
251 send_msg(obj, section, path_s, str, NULL);
252 break;
253 case G_TYPE_STRING:
254 snprintf(str, BUFLEN, " %d ", col);
255 send_msg(obj, section, path_s, str, g_value_get_string(&value), NULL);
256 break;
257 default:
258 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
259 break;
261 g_value_unset(&value);
263 g_free(path_s);
267 * cb_0(), cb_1(), etc. call this function to do their work: Send
268 * message(s) whose nature depends on the arguments passed. A call to
269 * this function will also be initiated by the user command
270 * ...:force_cb.
272 static void
273 do_callback(GtkBuildable *obj, gpointer user_data, const char *section)
275 char str[BUFLEN];
276 const char *item_name;
277 GdkRGBA color;
278 GtkTreeView *tree_view;
279 unsigned int year = 0, month = 0, day = 0;
281 if (GTK_IS_ENTRY(obj)) {
282 send_msg(obj, section, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
283 } else if (GTK_IS_MENU_ITEM(obj)) {
284 item_name = gtk_buildable_get_name(obj);
285 strncpy(str, item_name, BUFLEN - 1);
286 strncpy(str + strlen(item_name), "_dialog", BUFLEN - 1 - strlen(item_name));
287 if (gtk_builder_get_object(user_data, str) != NULL)
288 gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(user_data, str)));
289 else
290 send_msg(obj, section, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
291 } else if (GTK_IS_COMBO_BOX_TEXT(obj))
292 send_msg(obj, section, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(obj)), NULL);
293 else if (GTK_IS_RANGE(obj)) {
294 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
295 send_msg(obj, section, str, NULL);
296 } else if (GTK_IS_SWITCH(obj))
297 send_msg(obj, section, gtk_switch_get_active(GTK_SWITCH(obj))?"1":"0", NULL);
298 else if (GTK_IS_TOGGLE_BUTTON(obj))
299 send_msg(obj, section, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj))?"1":"0", NULL);
300 else if (GTK_IS_COLOR_BUTTON(obj)) {
301 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
302 send_msg(obj, section, gdk_rgba_to_string(&color), NULL);
303 } else if (GTK_IS_FONT_BUTTON(obj))
304 send_msg(obj, section, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
305 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
306 send_msg(obj, section, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
307 else if (GTK_IS_BUTTON(obj))
308 send_msg(obj, section, "clicked", NULL);
309 else if (GTK_IS_CALENDAR(obj)) {
310 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
311 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
312 send_msg(obj, section, str, NULL);
313 } else if (GTK_IS_TREE_VIEW_COLUMN(obj))
314 send_msg(obj, section, "clicked", NULL);
315 else if (GTK_IS_TREE_SELECTION(obj)) {
316 tree_view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
317 send_msg(GTK_BUILDABLE(tree_view), section, "clicked", NULL);
318 gtk_tree_selection_selected_foreach(
319 GTK_TREE_SELECTION(obj),
320 (GtkTreeSelectionForeachFunc)send_tree_row_msg,
321 &(struct selection_data){GTK_BUILDABLE(tree_view),
322 section});
323 } else if (GTK_IS_WINDOW(obj))
324 gtk_widget_hide_on_delete(GTK_WIDGET(obj));
325 else
326 fprintf(stderr, "ignoring callback %s from %s\n", section, gtk_buildable_get_name(obj));
330 * Callback functions cb_0(), cb_1(), cb_2(), and cb_3() for use in
331 * simple widgets. They're all identical except for sending different
332 * section strings.
334 #define MAKE_CB_N(n) \
335 void cb_##n(GtkBuildable *obj, gpointer user_data) \
336 {do_callback(obj, user_data, #n);} \
338 MAKE_CB_N(0)
339 MAKE_CB_N(1)
340 MAKE_CB_N(2)
341 MAKE_CB_N(3)
343 #undef MAKE_CB_N
346 * Store a line from stream into buf, which should have been malloc'd
347 * to bufsize. Enlarge buf and bufsize if necessary.
349 static size_t
350 read_buf(FILE *stream, char **buf, size_t *bufsize)
352 size_t i = 0;
353 int c;
355 for (;;) {
356 c = getc(stream);
357 if (c == EOF) {
358 i = 0;
359 nanosleep(&(struct timespec){0, 1e7}, NULL);
360 continue;
362 if (c == '\n')
363 break;
364 if (i >= *bufsize - 1)
365 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL) {
366 fprintf(stderr, "out of memory (%s in %s)\n", __func__, __FILE__);
367 abort();
369 if (c == '\\')
370 switch (c = getc(stream)) {
371 case 'n': (*buf)[i++] = '\n'; break;
372 case 'r': (*buf)[i++] = '\r'; break;
373 default: (*buf)[i++] = c; break;
375 else
376 (*buf)[i++] = c;
378 (*buf)[i] = '\0';
379 return i;
383 * Error message
385 static void
386 ign_cmd(GType type, const char *msg)
388 const char *name, *pad = " ";
390 if (type == G_TYPE_INVALID) {
391 name = "";
392 pad = "";
394 else
395 name = g_type_name(type);
396 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
400 * Parse command pointed to by ud, and act on ui accordingly. Set
401 * ud->digested = true if done. Runs once per command inside
402 * gtk_main_loop()
404 static gboolean
405 update_ui(struct ui_data *ud)
407 char name[ud->msg_size], action[ud->msg_size];
408 char *data;
409 int data_start = strlen(ud->msg);
410 GObject *obj = NULL;
411 GType type = G_TYPE_INVALID;
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 goto done;
421 obj = (gtk_builder_get_object(ud->builder, name));
422 if (obj == NULL) {
423 ign_cmd(type, ud->msg);
424 goto done;
426 if (eql(action, "force_cb")) {
427 do_callback(GTK_BUILDABLE(obj), NULL, "forced");
428 goto done;
430 data = ud->msg + data_start;
431 if (eql(action, "set_sensitive")) {
432 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
433 goto done;
434 } else if (eql(action, "set_visible")) {
435 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
436 goto done;
438 type = G_TYPE_FROM_INSTANCE(obj);
439 if (type == GTK_TYPE_LABEL) {
440 if (eql(action, "set_text"))
441 gtk_label_set_text(GTK_LABEL(obj), data);
442 else
443 ign_cmd(type, ud->msg);
444 } else if (type == GTK_TYPE_IMAGE) {
445 GtkIconSize size;
447 gtk_image_get_icon_name(GTK_IMAGE(obj), NULL, &size);
448 if (eql(action, "set_from_file"))
449 gtk_image_set_from_file(GTK_IMAGE(obj), data);
450 else if (eql(action, "set_from_icon_name"))
451 gtk_image_set_from_icon_name(GTK_IMAGE(obj), data, size);
452 else
453 ign_cmd(type, ud->msg);
454 } else if (type == GTK_TYPE_TEXT_VIEW) {
455 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj));
456 GtkTextIter a, b;
458 if (eql(action, "set_text"))
459 gtk_text_buffer_set_text(textbuf, data, -1);
460 else if (eql(action, "delete")) {
461 gtk_text_buffer_get_bounds(textbuf, &a, &b);
462 gtk_text_buffer_delete(textbuf, &a, &b);
463 } else if (eql(action, "insert_at_cursor"))
464 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
465 else if (eql(action, "place_cursor")) {
466 if (eql(data, "end"))
467 gtk_text_buffer_get_end_iter(textbuf, &a);
468 else /* numeric offset */
469 gtk_text_buffer_get_iter_at_offset(textbuf, &a, strtol(data, NULL, 10));
470 gtk_text_buffer_place_cursor(textbuf, &a);
471 } else if (eql(action, "place_cursor_at_line")) {
472 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
473 gtk_text_buffer_place_cursor(textbuf, &a);
474 } else if (eql(action, "scroll_to_cursor"))
475 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(obj), gtk_text_buffer_get_insert(textbuf), 0., 0, 0., 0.);
476 else
477 ign_cmd(type, ud->msg);
478 } else if (type == GTK_TYPE_BUTTON) {
479 if (eql(action, "set_label"))
480 gtk_button_set_label(GTK_BUTTON(obj), data);
481 else
482 ign_cmd(type, ud->msg);
483 } else if (type == GTK_TYPE_FILE_CHOOSER_DIALOG) {
484 if (eql(action, "set_filename"))
485 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
486 else if (eql(action, "set_current_name"))
487 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(obj), data);
488 else
489 ign_cmd(type, ud->msg);
490 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON) {
491 if (eql(action, "set_filename"))
492 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
493 else
494 ign_cmd(type, ud->msg);
495 } else if (type == GTK_TYPE_COLOR_BUTTON) {
496 if (eql(action, "set_color")) {
497 GdkRGBA color;
499 gdk_rgba_parse(&color, data);
500 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
501 } else
502 ign_cmd(type, ud->msg);
503 } else if (type == GTK_TYPE_FONT_BUTTON) {
504 if (eql(action, "set_font_name"))
505 gtk_font_button_set_font_name(GTK_FONT_BUTTON(obj), data);
506 else
507 ign_cmd(type, ud->msg);
508 } else if (type == GTK_TYPE_SWITCH) {
509 if (eql(action, "set_active"))
510 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
511 else
512 ign_cmd(type, ud->msg);
513 } else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON) {
514 if (eql(action, "set_label"))
515 gtk_button_set_label(GTK_BUTTON(obj), data);
516 else if (eql(action, "set_active"))
517 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
518 else
519 ign_cmd(type, ud->msg);
520 } else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY) {
521 if (eql(action, "set_text"))
522 gtk_entry_set_text(GTK_ENTRY(obj), data);
523 else
524 ign_cmd(type, ud->msg);
525 } else if (type == GTK_TYPE_SCALE) {
526 if (eql(action, "set_value"))
527 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
528 else
529 ign_cmd(type, ud->msg);
530 } else if (type == GTK_TYPE_PROGRESS_BAR) {
531 if (eql(action, "set_text"))
532 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(obj), data);
533 else if (eql(action, "set_fraction"))
534 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(obj), strtod(data, NULL));
535 else
536 ign_cmd(type, ud->msg);
537 } else if (type == GTK_TYPE_SPINNER) {
538 if (eql(action, "start"))
539 gtk_spinner_start(GTK_SPINNER(obj));
540 else if (eql(action, "stop"))
541 gtk_spinner_stop(GTK_SPINNER(obj));
542 else
543 ign_cmd(type, ud->msg);
544 } else if (type == GTK_TYPE_COMBO_BOX_TEXT) {
545 if (eql(action, "prepend_text"))
546 gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(obj), data);
547 else if (eql(action, "append_text"))
548 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(obj), data);
549 else if (eql(action, "remove"))
550 gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(obj), strtol(data, NULL, 10));
551 else if (eql(action, "insert_text")) {
552 char *position = strtok(data, WHITESPACE);
553 char *text = strtok(NULL, WHITESPACE);
555 gtk_combo_box_text_insert_text(GTK_COMBO_BOX_TEXT(obj), strtol(position, NULL, 10), text);
556 } else
557 ign_cmd(type, ud->msg);
558 } else if (type == GTK_TYPE_STATUSBAR) {
559 if (eql(action, "push"))
560 gtk_statusbar_push(GTK_STATUSBAR(obj), 0, data);
561 else if (eql(action, "pop"))
562 gtk_statusbar_pop(GTK_STATUSBAR(obj), 0);
563 else
564 ign_cmd(type, ud->msg);
565 } else if (type == GTK_TYPE_CALENDAR) {
566 int year = 0, month = 0, day = 0;
568 if (eql(action, "select_date")) {
569 sscanf(data, "%d-%d-%d", &year, &month, &day);
570 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
571 gtk_calendar_select_month(GTK_CALENDAR(obj), --month, year);
572 gtk_calendar_select_day(GTK_CALENDAR(obj), day);
573 } else
574 ign_cmd(type, ud->msg);
575 } else if (eql(action, "mark_day")) {
576 day = strtol(data, NULL, 10);
577 if (day > 0 && day <= 31)
578 gtk_calendar_mark_day(GTK_CALENDAR(obj), day);
579 else
580 ign_cmd(type, ud->msg);
581 } else if (eql(action, "clear_marks"))
582 gtk_calendar_clear_marks(GTK_CALENDAR(obj));
583 else
584 ign_cmd(type, ud->msg);
585 } else if (type == GTK_TYPE_TREE_VIEW) {
586 GtkTreeView *view = GTK_TREE_VIEW(obj);
587 GtkTreeModel *model = gtk_tree_view_get_model(view);
588 GtkListStore *store = GTK_LIST_STORE(model);
589 GtkTreeIter iter0, iter1;
590 char *tokens, *arg0_s, *arg1_s, *arg2_s, *endptr;
591 int arg0_n = 0, arg1_n = 0;
592 bool arg0_n_valid = false, arg1_n_valid = false;
593 bool iter0_valid = false, iter1_valid = false;
595 if ((tokens = malloc(strlen(data) + 1)) == NULL) {
596 fprintf(stderr, "out of memory (%s in %s)\n", __func__, __FILE__);
597 abort();
599 strcpy(tokens, data);
600 arg0_s = strtok(tokens, WHITESPACE);
601 arg1_s = strtok(NULL, WHITESPACE);
602 arg2_s = strtok(NULL, "\n");
603 errno = 0;
604 endptr = NULL;
605 iter0_valid =
606 arg0_s != NULL &&
607 (arg0_n_valid = (arg0_n = strtol(arg0_s, &endptr, 10)) >= 0 &&
608 errno == 0 && endptr != arg0_s) &&
609 gtk_tree_model_get_iter_from_string(model, &iter0, arg0_s);
610 errno = 0;
611 endptr = NULL;
612 iter1_valid =
613 arg1_s != NULL &&
614 (arg1_n_valid = (arg1_n = strtol(arg1_s, &endptr, 10)) >= 0 &&
615 errno == 0 && endptr != arg1_s) &&
616 gtk_tree_model_get_iter_from_string(model, &iter1, arg1_s);
617 if (eql(action, "set") && iter0_valid && arg1_n_valid &&
618 arg1_n < gtk_tree_model_get_n_columns(model)) {
619 GType col_type = gtk_tree_model_get_column_type(model, arg1_n);
620 long long int n;
621 double d;
623 switch (col_type) {
624 case G_TYPE_BOOLEAN:
625 case G_TYPE_INT:
626 case G_TYPE_LONG:
627 case G_TYPE_INT64:
628 case G_TYPE_UINT:
629 case G_TYPE_ULONG:
630 case G_TYPE_UINT64:
631 errno = 0;
632 endptr = NULL;
633 n = strtoll(arg2_s, &endptr, 10);
634 if (!errno && endptr != arg2_s)
635 gtk_list_store_set(store, &iter0, arg1_n, n, -1);
636 else
637 ign_cmd(type, ud->msg);
638 break;
639 case G_TYPE_FLOAT:
640 case G_TYPE_DOUBLE:
641 errno = 0;
642 endptr = NULL;
643 d = strtod(arg2_s, &endptr);
644 if (!errno && endptr != arg2_s)
645 gtk_list_store_set(store, &iter0, arg1_n, d, -1);
646 else
647 ign_cmd(type, ud->msg);
648 gtk_list_store_set(store, &iter0, arg1_n, strtod(arg2_s, NULL), -1);
649 break;
650 case G_TYPE_STRING:
651 gtk_list_store_set(store, &iter0, arg1_n, arg2_s, -1);
652 break;
653 default:
654 fprintf(stderr, "column %d: %s not implemented\n", arg1_n, g_type_name(col_type));
655 break;
657 } else if (eql(action, "scroll") && arg0_n_valid && arg1_n_valid)
658 gtk_tree_view_scroll_to_cell (view,
659 gtk_tree_path_new_from_string(arg0_s),
660 gtk_tree_view_get_column(view, arg1_n),
661 0, 0., 0.);
662 else if (eql(action, "insert_row"))
663 if (eql(arg0_s, "end"))
664 gtk_list_store_insert_before(store, &iter1, NULL);
665 else if (iter0_valid)
666 gtk_list_store_insert_before(store, &iter1, &iter0);
667 else
668 ign_cmd(type, ud->msg);
669 else if (eql(action, "move_row") && iter0_valid)
670 if (eql(arg1_s, "end"))
671 gtk_list_store_move_before(store, &iter0, NULL);
672 else if (iter1_valid)
673 gtk_list_store_move_before(store, &iter0, &iter1);
674 else
675 ign_cmd(type, ud->msg);
676 else if (eql(action, "remove_row") && iter0_valid)
677 gtk_list_store_remove(store, &iter0);
678 else
679 ign_cmd(type, ud->msg);
680 free(tokens);
681 } else if (type == GTK_TYPE_WINDOW) {
682 if (eql(action, "set_title"))
683 gtk_window_set_title(GTK_WINDOW(obj), data);
684 else
685 ign_cmd(type, ud->msg);
686 } else
687 ign_cmd(type, ud->msg);
688 done:
689 ud->msg_digested = true;
690 return G_SOURCE_REMOVE;
693 static void
694 free_at(void **mem)
696 free(*mem);
700 * Read lines from global stream "in" and perform the appropriate
701 * actions on the GUI
703 static void *
704 digest_msg(void *builder)
706 for (;;) {
707 char first_char = '\0';
708 struct ui_data ud;
710 if ((ud.msg = malloc(ud.msg_size = 32)) == NULL ) {
711 fprintf(stderr, "out of memory (%s in %s)\n", __func__, __FILE__);
712 abort();
714 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
715 pthread_testcancel();
716 read_buf(in, &ud.msg, &ud.msg_size);
717 sscanf(ud.msg, " %c", &first_char);
718 ud.builder = builder;
719 if (first_char != '#') {
720 ud.msg_digested = false;
721 pthread_testcancel();
722 gdk_threads_add_timeout(1, (GSourceFunc)update_ui, &ud);
723 while (!ud.msg_digested)
724 nanosleep(&(struct timespec){0, 1e6}, NULL);
726 pthread_cleanup_pop(1);
728 return NULL;
732 * Create a fifo if necessary, and open it. Give up if the file
733 * exists but is not a fifo
735 static FILE *
736 fifo(const char *name, const char *mode)
738 struct stat sb;
739 int fd;
740 FILE *stream;
742 if (name != NULL && (stat(name, &sb), !S_ISFIFO(sb.st_mode)))
743 if (mkfifo(name, 0666) != 0) {
744 perror("making fifo");
745 exit(EXIT_FAILURE);
747 switch (mode[0]) {
748 case 'r':
749 if (name == NULL)
750 stream = stdin;
751 else {
752 fd = open(name, O_RDWR | O_NONBLOCK);
753 if (fd < 0) {
754 perror("opening fifo");
755 exit(EXIT_FAILURE);
757 stream = fdopen(fd, "r");
759 break;
760 case 'w':
761 if (name == NULL)
762 stream = stdout;
763 else {
764 /* fopen blocks if there is no reader, so here is one */
765 fd = open(name, O_RDONLY | O_NONBLOCK);
766 if (fd < 0) {
767 perror("opening fifo");
768 exit(EXIT_FAILURE);
770 stream = fopen(name, "w");
771 /* unblocking done */
772 close(fd);
774 break;
775 default:
776 abort();
777 break;
779 if (stream == NULL) {
780 perror("opening fifo");
781 exit(EXIT_FAILURE);
782 } else {
783 setvbuf(stream, NULL, _IOLBF, 0);
784 return stream;
789 main(int argc, char *argv[])
791 char opt;
792 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
793 GtkBuilder *builder;
794 pthread_t receiver;
795 GError *error = NULL;
796 GObject *window = NULL;
798 in = NULL;
799 out = NULL;
800 while ((opt = getopt(argc, argv, "hi:o:u:GV")) != -1) {
801 switch (opt) {
802 case 'i': in_fifo = optarg; break;
803 case 'o': out_fifo = optarg; break;
804 case 'u': ui_file = optarg; break;
805 case 'G': gtk_versions(); break;
806 case 'V': version(); break;
807 case '?':
808 case 'h':
809 default: usage(argv); break;
812 if (argv[optind] != NULL) {
813 fprintf(stderr, "illegal parameter '%s'\n", argv[optind]);
814 usage(argv);
816 if (ui_file == NULL)
817 ui_file = "pipeglade.ui";
818 gtk_init(0, NULL);
819 builder = gtk_builder_new();
820 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0) {
821 fprintf(stderr, "%s\n", error->message);
822 exit(EXIT_FAILURE);
824 in = fifo(in_fifo, "r");
825 out = fifo(out_fifo, "w");
826 gtk_builder_connect_signals(builder, builder);
827 pthread_create(&receiver, NULL, digest_msg, (void*)builder);
828 window = (gtk_builder_get_object(builder, "window"));
829 if (!GTK_IS_WIDGET(window)) {
830 fprintf(stderr, "no toplevel window named \'window\'\n");
831 exit(EXIT_FAILURE);
833 gtk_widget_show(GTK_WIDGET(window));
834 gtk_main();
835 if (in != stdin) {
836 fclose(in);
837 unlink(in_fifo);
839 if (out != stdout) {
840 fclose(out);
841 unlink(out_fifo);
843 pthread_cancel(receiver);
844 pthread_join(receiver, NULL);
845 exit(EXIT_SUCCESS);