Code cleanup
[pipeglade.git] / pipeglade.c
blob015bbc21c472fef8054579bff57e01e9a38dae7b
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 <math.h>
30 #include <pthread.h>
31 #include <search.h>
32 #include <stdio.h>
33 #include <stdarg.h>
34 #include <stdbool.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <time.h>
39 #include <unistd.h>
41 #define VERSION "3.0.1"
42 #define BUFLEN 256
43 #define WHITESPACE " \t\n"
44 #define MAIN_WIN "main"
46 #define OOM_ABORT \
47 { \
48 fprintf(stderr, \
49 "out of memory: %s (%s:%d)\n", \
50 __func__, __FILE__, __LINE__); \
51 abort(); \
54 static FILE *in;
55 static FILE *out;
56 struct ui_data {
57 GtkBuilder *builder;
58 size_t msg_size;
59 char *msg;
60 bool msg_digested;
63 static void
64 usage(char **argv)
66 fprintf(stderr,
67 "usage: %s "
68 "[-h] "
69 "[-i in-fifo] "
70 "[-o out-fifo] "
71 "[-u glade-builder-file.ui] "
72 "[-G] "
73 "[-V]\n",
74 argv[0]);
75 exit(EXIT_SUCCESS);
78 static void
79 version(void)
81 printf(VERSION "\n");
82 exit(EXIT_SUCCESS);
85 static void
86 gtk_versions(void)
88 printf("GTK+ v%d.%d.%d (running v%d.%d.%d)\n",
89 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
90 gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version());
91 exit(EXIT_SUCCESS);
94 static bool
95 eql(const char *s1, const char *s2)
97 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
100 static const char *
101 widget_name(void *obj)
103 return gtk_buildable_get_name(GTK_BUILDABLE(obj));
107 * Send GUI feedback to global stream "out". The message format is
108 * "<origin>:<section> <data ...>". The variadic arguments are
109 * strings; last argument must be NULL. We're being patient with
110 * receivers which may intermittently close their end of the fifo, and
111 * make a couple of retries if an error occurs.
113 static void
114 send_msg(GtkBuildable *obj, const char *section, ...)
116 va_list ap;
117 char *data;
118 long nsec;
120 for (nsec = 1e6; nsec < 1e9; nsec <<= 3) {
121 va_start(ap, section);
122 fprintf(out, "%s:%s ", widget_name(obj), section);
123 while ((data = va_arg(ap, char *)) != NULL) {
124 size_t i = 0;
125 char c;
127 while ((c = data[i++]) != '\0')
128 if (c == '\\')
129 fprintf(out, "\\\\");
130 else if (c == '\n')
131 fprintf(out, "\\n");
132 else
133 putc(c, out);
135 va_end(ap);
136 putc('\n', out);
137 if (ferror(out)) {
138 fprintf(stderr, "send error; retrying\n");
139 clearerr(out);
140 nanosleep(&(struct timespec){0, nsec}, NULL);
141 putc('\n', out);
142 } else
143 break;
148 * Callback that sends user's selection from a file dialog
150 static void
151 cb_send_file_chooser_dialog_selection(gpointer user_data)
153 send_msg(user_data, "file",
154 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(user_data)), NULL);
155 send_msg(user_data, "folder",
156 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(user_data)), NULL);
160 * Callback that sends in a message the content of the text buffer
161 * passed in user_data
163 static void
164 cb_send_text(GtkBuildable *obj, gpointer user_data)
166 GtkTextIter a, b;
168 gtk_text_buffer_get_bounds(user_data, &a, &b);
169 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
173 * Callback that sends in a message the highlighted text from the text
174 * buffer which was passed in user_data
176 static void
177 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
179 GtkTextIter a, b;
181 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
182 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
186 * send_tree_row_msg serves as an argument for
187 * gtk_tree_selection_selected_foreach()
189 static void
190 send_tree_row_msg(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
192 char *path_s = gtk_tree_path_to_string(path);
193 int col;
195 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++) {
196 GValue value = G_VALUE_INIT;
197 GType col_type;
198 char str[BUFLEN];
200 gtk_tree_model_get_value(model, iter, col, &value);
201 col_type = gtk_tree_model_get_column_type(model, col);
202 switch (col_type) {
203 case G_TYPE_INT:
204 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
205 send_msg(obj, "gint", path_s, str, NULL);
206 break;
207 case G_TYPE_LONG:
208 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
209 send_msg(obj, "glong", path_s, str, NULL);
210 break;
211 case G_TYPE_INT64:
212 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
213 send_msg(obj, "gint64", path_s, str, NULL);
214 break;
215 case G_TYPE_UINT:
216 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
217 send_msg(obj, "guint", path_s, str, NULL);
218 break;
219 case G_TYPE_ULONG:
220 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
221 send_msg(obj, "gulong", path_s, str, NULL);
222 break;
223 case G_TYPE_UINT64:
224 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
225 send_msg(obj, "guint64", path_s, str, NULL);
226 break;
227 case G_TYPE_BOOLEAN:
228 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
229 send_msg(obj, "gboolean", path_s, str, NULL);
230 break;
231 case G_TYPE_FLOAT:
232 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
233 send_msg(obj, "gfloat", path_s, str, NULL);
234 break;
235 case G_TYPE_DOUBLE:
236 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
237 send_msg(obj, "gdouble", path_s, str, NULL);
238 break;
239 case G_TYPE_STRING:
240 snprintf(str, BUFLEN, " %d ", col);
241 send_msg(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
242 break;
243 default:
244 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
245 break;
247 g_value_unset(&value);
249 g_free(path_s);
253 * Callback that sends message(s) whose nature depends on the
254 * arguments passed. A call to this function will also be initiated
255 * by the user command ...:force.
257 static void
258 cb(GtkBuildable *obj, gpointer user_data)
260 char str[BUFLEN];
261 GdkRGBA color;
262 GtkTreeView *tree_view;
263 unsigned int year = 0, month = 0, day = 0;
265 if (GTK_IS_ENTRY(obj))
266 send_msg(obj, user_data, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
267 else if (GTK_IS_MENU_ITEM(obj))
268 send_msg(obj, user_data, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
269 else if (GTK_IS_RANGE(obj)) {
270 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
271 send_msg(obj, user_data, str, NULL);
272 } else if (GTK_IS_SWITCH(obj))
273 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
274 else if (GTK_IS_TOGGLE_BUTTON(obj))
275 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
276 else if (GTK_IS_COLOR_BUTTON(obj)) {
277 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
278 send_msg(obj, user_data, gdk_rgba_to_string(&color), NULL);
279 } else if (GTK_IS_FONT_BUTTON(obj))
280 send_msg(obj, user_data, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
281 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
282 send_msg(obj, user_data, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
283 else if (GTK_IS_BUTTON(obj) || GTK_IS_TREE_VIEW_COLUMN(obj))
284 send_msg(obj, user_data, NULL);
285 else if (GTK_IS_CALENDAR(obj)) {
286 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
287 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
288 send_msg(obj, user_data, str, NULL);
289 } else if (GTK_IS_TREE_SELECTION(obj)) {
290 tree_view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
291 send_msg(GTK_BUILDABLE(tree_view), user_data, NULL);
292 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
293 (GtkTreeSelectionForeachFunc)send_tree_row_msg,
294 GTK_BUILDABLE(tree_view));
295 } else
296 fprintf(stderr, "ignoring callback from %s\n", widget_name(obj));
300 * Store a line from stream into buf, which should have been malloc'd
301 * to bufsize. Enlarge buf and bufsize if necessary.
303 static size_t
304 read_buf(FILE *stream, char **buf, size_t *bufsize)
306 size_t i = 0;
307 int c;
309 for (;;) {
310 c = getc(stream);
311 if (c == EOF) {
312 i = 0;
313 nanosleep(&(struct timespec){0, 1e7}, NULL);
314 continue;
316 if (c == '\n')
317 break;
318 if (i >= *bufsize - 1)
319 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
320 OOM_ABORT;
321 if (c == '\\')
322 switch (c = getc(stream)) {
323 case 'n': (*buf)[i++] = '\n'; break;
324 case 'r': (*buf)[i++] = '\r'; break;
325 default: (*buf)[i++] = c; break;
327 else
328 (*buf)[i++] = c;
330 (*buf)[i] = '\0';
331 return i;
335 * Warning message
337 static void
338 ign_cmd(GType type, const char *msg)
340 const char *name, *pad = " ";
342 if (type == G_TYPE_INVALID) {
343 name = "";
344 pad = "";
346 else
347 name = g_type_name(type);
348 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
352 * Drawing on a GtkDrawingArea
354 enum cairo_fn {
355 RECTANGLE,
356 ARC,
357 ARC_NEGATIVE,
358 CURVE_TO,
359 REL_CURVE_TO,
360 LINE_TO,
361 REL_LINE_TO,
362 MOVE_TO,
363 REL_MOVE_TO,
364 CLOSE_PATH,
365 SET_SOURCE_RGBA,
366 SET_DASH,
367 SET_LINE_CAP,
368 SET_LINE_JOIN,
369 SET_LINE_WIDTH,
370 FILL,
371 FILL_PRESERVE,
372 STROKE,
373 STROKE_PRESERVE,
374 SHOW_TEXT,
375 SET_FONT_SIZE,
379 * One single element of a drawing
381 struct draw_op {
382 struct draw_op *next;
383 struct draw_op *prev;
384 unsigned int id;
385 enum cairo_fn op;
386 void *op_args;
390 * The content of all GtkDrawingAreas
392 static struct drawing {
393 struct drawing *next;
394 struct drawing *prev;
395 GtkWidget *widget;
396 struct draw_op *draw_ops;
397 } *drawings = NULL;
400 * Sets of arguments for various drawing functions
402 struct rectangle_args {
403 double x;
404 double y;
405 double width;
406 double height;
409 struct arc_args {
410 double x;
411 double y;
412 double radius;
413 double angle1;
414 double angle2;
417 struct curve_to_args {
418 double x1;
419 double y1;
420 double x2;
421 double y2;
422 double x3;
423 double y3;
426 struct move_to_args {
427 double x;
428 double y;
431 struct set_source_rgba_args {
432 GdkRGBA color;
435 struct set_dash_args {
436 int num_dashes;
437 double dashes[];
440 struct set_line_cap_args {
441 cairo_line_cap_t line_cap;
444 struct set_line_join_args {
445 cairo_line_join_t line_join;
448 struct set_line_width_args {
449 double width;
452 struct show_text_args {
453 int len;
454 char text[];
457 struct set_font_size_args {
458 double size;
461 static void
462 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
464 switch (op) {
465 case RECTANGLE: {
466 struct rectangle_args *args = op_args;
468 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
469 break;
471 case ARC: {
472 struct arc_args *args = op_args;
474 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
475 break;
477 case ARC_NEGATIVE: {
478 struct arc_args *args = op_args;
480 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
481 break;
483 case CURVE_TO: {
484 struct curve_to_args *args = op_args;
486 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
487 break;
489 case REL_CURVE_TO: {
490 struct curve_to_args *args = op_args;
492 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
493 break;
495 case LINE_TO: {
496 struct move_to_args *args = op_args;
498 cairo_line_to(cr, args->x, args->y);
499 break;
501 case REL_LINE_TO: {
502 struct move_to_args *args = op_args;
504 cairo_rel_line_to(cr, args->x, args->y);
505 break;
507 case MOVE_TO: {
508 struct move_to_args *args = op_args;
510 cairo_move_to(cr, args->x, args->y);
511 break;
513 case REL_MOVE_TO: {
514 struct move_to_args *args = op_args;
516 cairo_rel_move_to(cr, args->x, args->y);
517 break;
519 case CLOSE_PATH:
520 cairo_close_path(cr);
521 break;
522 case SET_SOURCE_RGBA: {
523 struct set_source_rgba_args *args = op_args;
525 gdk_cairo_set_source_rgba(cr, &args->color);
526 break;
528 case SET_DASH: {
529 struct set_dash_args *args = op_args;
531 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
532 break;
534 case SET_LINE_CAP: {
535 struct set_line_cap_args *args = op_args;
537 cairo_set_line_cap(cr, args->line_cap);
538 break;
540 case SET_LINE_JOIN: {
541 struct set_line_join_args *args = op_args;
543 cairo_set_line_join(cr, args->line_join);
544 break;
546 case SET_LINE_WIDTH: {
547 struct set_line_width_args *args = op_args;
549 cairo_set_line_width(cr, args->width);
550 break;
552 case FILL:
553 cairo_fill(cr);
554 break;
555 case FILL_PRESERVE:
556 cairo_fill_preserve(cr);
557 break;
558 case STROKE:
559 cairo_stroke(cr);
560 break;
561 case STROKE_PRESERVE:
562 cairo_stroke_preserve(cr);
563 break;
564 case SHOW_TEXT: {
565 struct show_text_args *args = op_args;
567 cairo_show_text(cr, args->text);
568 break;
570 case SET_FONT_SIZE: {
571 struct set_font_size_args *args = op_args;
573 cairo_set_font_size(cr, args->size);
574 break;
576 default:
577 abort();
578 break;
582 static bool
583 set_draw_op(struct draw_op *op, char* action, char *data)
585 if (eql(action, "rectangle")) {
586 struct rectangle_args *args;
588 if ((args = malloc(sizeof(*args))) == NULL)
589 OOM_ABORT;
590 op->op = RECTANGLE;
591 op->op_args = args;
592 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->width, &args->height) != 5)
593 return false;
594 } else if (eql(action, "arc")) {
595 struct arc_args *args;
596 double deg1, deg2;
598 if ((args = malloc(sizeof(*args))) == NULL)
599 OOM_ABORT;
600 op->op = ARC;
601 op->op_args = args;
602 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
603 return false;
604 args->angle1 = deg1 * (M_PI / 180.);
605 args->angle2 = deg2 * (M_PI / 180.);
606 } else if (eql(action, "arc_negative")) {
607 struct arc_args *args;
608 double deg1, deg2;
610 if ((args = malloc(sizeof(*args))) == NULL)
611 OOM_ABORT;
612 op->op = ARC_NEGATIVE;
613 op->op_args = args;
614 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
615 return false;
616 args->angle1 = deg1 * (M_PI / 180.);
617 args->angle2 = deg2 * (M_PI / 180.);
618 } else if (eql(action, "curve_to")) {
619 struct curve_to_args *args;
621 if ((args = malloc(sizeof(*args))) == NULL)
622 OOM_ABORT;
623 op->op = CURVE_TO;
624 op->op_args = args;
625 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id, &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
626 return false;
627 } else if (eql(action, "rel_curve_to")) {
628 struct curve_to_args *args;
630 if ((args = malloc(sizeof(*args))) == NULL)
631 OOM_ABORT;
632 op->op = REL_CURVE_TO;
633 op->op_args = args;
634 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id, &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
635 return false;
636 } else if (eql(action, "line_to")) {
637 struct move_to_args *args;
639 if ((args = malloc(sizeof(*args))) == NULL)
640 OOM_ABORT;
641 op->op = LINE_TO;
642 op->op_args = args;
643 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
644 return false;
645 } else if (eql(action, "rel_line_to")) {
646 struct move_to_args *args;
648 if ((args = malloc(sizeof(*args))) == NULL)
649 OOM_ABORT;
650 op->op = REL_LINE_TO;
651 op->op_args = args;
652 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
653 return false;
654 } else if (eql(action, "move_to")) {
655 struct move_to_args *args;
657 if ((args = malloc(sizeof(*args))) == NULL)
658 OOM_ABORT;
659 op->op = MOVE_TO;
660 op->op_args = args;
661 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
662 return false;
663 } else if (eql(action, "rel_move_to")) {
664 struct move_to_args *args;
666 if ((args = malloc(sizeof(*args))) == NULL)
667 OOM_ABORT;
668 op->op = REL_MOVE_TO;
669 op->op_args = args;
670 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
671 return false;
672 } else if (eql(action, "close_path")) {
673 op->op = CLOSE_PATH;
674 if (sscanf(data, "%u", &op->id) != 1)
675 return false;
676 op->op_args = NULL;
677 } else if (eql(action, "set_source_rgba")) {
678 struct set_source_rgba_args *args;
679 int c_start;
681 if ((args = malloc(sizeof(*args))) == NULL)
682 OOM_ABORT;
683 op->op = SET_SOURCE_RGBA;
684 op->op_args = args;
685 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
686 return false;;
687 gdk_rgba_parse(&args->color, data + c_start);
688 } else if (eql(action, "set_dash")) {
689 struct set_dash_args *args;
690 int d_start, n, i;
691 char *next, *end;
693 if (sscanf(data, "%u %n", &op->id, &d_start) < 1)
694 return false;
695 next = end = data + d_start;
696 n = -1;
697 do {
698 n++;
699 next = end;
700 strtod(next, &end);
701 } while (next != end);
702 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
703 OOM_ABORT;
704 op->op = SET_DASH;
705 op->op_args = args;
706 args->num_dashes = n;
707 for (i = 0, next = data + d_start; i < n; i++, next = end) {
708 args->dashes[i] = strtod(next, &end);
710 } else if (eql(action, "set_line_cap")) {
711 struct set_line_cap_args *args;
712 char str[6 + 1];
714 if ((args = malloc(sizeof(*args))) == NULL)
715 OOM_ABORT;
716 op->op = SET_LINE_CAP;
717 op->op_args = args;
718 if (sscanf(data, "%u %6s", &op->id, str) != 2)
719 return false;
720 if (eql(str, "butt"))
721 args->line_cap = CAIRO_LINE_CAP_BUTT;
722 else if (eql(str, "round"))
723 args->line_cap = CAIRO_LINE_CAP_ROUND;
724 else if (eql(str, "square"))
725 args->line_cap = CAIRO_LINE_CAP_SQUARE;
726 else
727 return false;
728 } else if (eql(action, "set_line_join")) {
729 struct set_line_join_args *args;
730 char str[5 + 1];
732 if ((args = malloc(sizeof(*args))) == NULL)
733 OOM_ABORT;
734 op->op = SET_LINE_JOIN;
735 op->op_args = args;
736 if (sscanf(data, "%u %5s", &op->id, str) != 2)
737 return false;
738 if (eql(str, "miter"))
739 args->line_join = CAIRO_LINE_JOIN_MITER;
740 else if (eql(str, "round"))
741 args->line_join = CAIRO_LINE_JOIN_ROUND;
742 else if (eql(str, "bevel"))
743 args->line_join = CAIRO_LINE_JOIN_BEVEL;
744 else
745 return false;
746 } else if (eql(action, "set_line_width")) {
747 struct set_line_width_args *args;
749 if ((args = malloc(sizeof(*args))) == NULL)
750 OOM_ABORT;
751 op->op = SET_LINE_WIDTH;
752 op->op_args = args;
753 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
754 return false;
755 } else if (eql(action, "fill")) {
756 op->op = FILL;
757 if (sscanf(data, "%u", &op->id) != 1)
758 return false;
759 op->op_args = NULL;
760 } else if (eql(action, "fill_preserve")) {
761 op->op = FILL_PRESERVE;
762 if (sscanf(data, "%u", &op->id) != 1)
763 return false;
764 op->op_args = NULL;
765 } else if (eql(action, "stroke")) {
766 op->op = STROKE;
767 if (sscanf(data, "%u", &op->id) != 1)
768 return false;
769 op->op_args = NULL;
770 } else if (eql(action, "stroke_preserve")) {
771 op->op = STROKE_PRESERVE;
772 if (sscanf(data, "%u", &op->id) != 1)
773 return false;
774 op->op_args = NULL;
775 } else if (eql(action, "show_text")) {
776 struct show_text_args *args;
777 int start, len;
779 if (sscanf(data, "%u %n", &op->id, &start) < 1)
780 return false;
781 len = strlen(data + start) + 1;
782 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
783 OOM_ABORT;
784 op->op = SHOW_TEXT;
785 op->op_args = args;
786 args->len = len; /* not used */
787 strncpy(args->text, (data + start), len);
788 } else if (eql(action, "set_font_size")) {
789 struct set_font_size_args *args;
791 if ((args = malloc(sizeof(*args))) == NULL)
792 OOM_ABORT;
793 op->op = SET_FONT_SIZE;
794 op->op_args = args;
795 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
796 return false;
797 } else
798 return false;
799 return true;
803 * Add another element to widget's list of drawing operations
805 static bool
806 ins_draw_op(GtkWidget *widget, char *action, char *data)
808 struct draw_op *op, *last_op;
809 struct drawing *d;
811 if ((op = malloc(sizeof(*op))) == NULL)
812 OOM_ABORT;
813 op->op_args = NULL;
814 if (!set_draw_op(op, action, data)) {
815 free(op->op_args);
816 free(op);
817 return false;
819 for (d = drawings; d != NULL; d = d->next)
820 if (d->widget == widget)
821 break;
822 if (d == NULL) {
823 if ((d = malloc(sizeof(*d))) == NULL)
824 OOM_ABORT;
825 if (drawings == NULL) {
826 drawings = d;
827 insque(d, NULL);
828 } else
829 insque(d, drawings);
830 d->widget = widget;
831 d->draw_ops = op;
832 insque(op, NULL);
833 } else if (d->draw_ops == NULL) {
834 d->draw_ops = op;
835 insque(op, NULL);
836 } else {
837 for (last_op = d->draw_ops; last_op->next != NULL; last_op = last_op->next);
838 insque(op, last_op);
840 return true;
844 * Remove all elements with the given id from widget's list of drawing
845 * operations
847 static bool
848 rem_draw_op(GtkWidget *widget, char *data)
850 struct draw_op *op, *next_op;
851 struct drawing *d;
852 unsigned int id;
854 if (sscanf(data, "%u", &id) != 1)
855 return false;
856 for (d = drawings; d != NULL; d = d->next)
857 if (d->widget == widget)
858 break;
859 if (d != NULL) {
860 op = d->draw_ops;
861 while (op != NULL) {
862 next_op = op->next;
863 if (op->id == id) {
864 if (op->prev == NULL)
865 d->draw_ops = next_op;
866 remque(op);
867 free(op->op_args);
868 free(op);
870 op = next_op;
873 return true;
877 * Callback that draws on a GtkDrawingArea
879 static gboolean
880 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
882 struct draw_op *op;
883 struct drawing *p;
885 (void)data;
886 for (p = drawings; p != NULL; p = p->next)
887 if (p->widget == widget)
888 for (op = p->draw_ops; op != NULL; op = op->next)
889 draw(cr, op->op, op->op_args);
890 return FALSE;
894 * Perform various kinds of actions on the widget passed
896 static void
897 update_widget_font(GtkWidget *widget, char *data)
899 if (data[0]) {
900 PangoFontDescription *font = pango_font_description_from_string(data);
902 gtk_widget_override_font(widget, font);
903 pango_font_description_free(font);
904 } else
905 gtk_widget_override_font(widget, NULL);
908 static void
909 update_widget_color(GtkWidget *widget, char *data)
911 GdkRGBA color;
913 if (data[0]) {
914 gdk_rgba_parse(&color, data);
915 gtk_widget_override_color(widget, GTK_STATE_FLAG_NORMAL, &color);
916 } else
917 gtk_widget_override_color(widget, GTK_STATE_FLAG_NORMAL, NULL);
920 static void
921 update_widget_background_color(GtkWidget *widget, char *data)
923 GdkRGBA color;
925 if (data[0]) {
926 gdk_rgba_parse(&color, data);
927 gtk_widget_override_background_color(widget, GTK_STATE_FLAG_NORMAL, &color);
928 } else
929 gtk_widget_override_background_color(widget, GTK_STATE_FLAG_NORMAL, NULL);
933 * Update various kinds of widgets according to the respective action
934 * parameter
936 static void
937 update_button(GtkButton *button, char *action, char *data, char *whole_msg)
939 if (eql(action, "set_label"))
940 gtk_button_set_label(button, data);
941 else
942 ign_cmd(GTK_TYPE_BUTTON, whole_msg);
945 static void
946 update_calendar(GtkCalendar *calendar, char *action, char *data, char *whole_msg)
948 int year = 0, month = 0, day = 0;
950 if (eql(action, "select_date")) {
951 sscanf(data, "%d-%d-%d", &year, &month, &day);
952 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
953 gtk_calendar_select_month(calendar, --month, year);
954 gtk_calendar_select_day(calendar, day);
955 } else
956 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
957 } else if (eql(action, "mark_day")) {
958 day = strtol(data, NULL, 10);
959 if (day > 0 && day <= 31)
960 gtk_calendar_mark_day(calendar, day);
961 else
962 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
963 } else if (eql(action, "clear_marks"))
964 gtk_calendar_clear_marks(calendar);
965 else
966 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
969 static void
970 update_color_button(GtkColorChooser *chooser, char *action, char *data, char *whole_msg)
972 GdkRGBA color;
974 if (eql(action, "set_color")) {
975 gdk_rgba_parse(&color, data);
976 gtk_color_chooser_set_rgba(chooser, &color);
977 } else
978 ign_cmd(GTK_TYPE_COLOR_BUTTON, whole_msg);
981 static void
982 update_combo_box_text(GtkComboBoxText *combobox, char *action, char *data, char *whole_msg)
984 if (eql(action, "prepend_text"))
985 gtk_combo_box_text_prepend_text(combobox, data);
986 else if (eql(action, "append_text"))
987 gtk_combo_box_text_append_text(combobox, data);
988 else if (eql(action, "remove"))
989 gtk_combo_box_text_remove(combobox, strtol(data, NULL, 10));
990 else if (eql(action, "insert_text")) {
991 char *position = strtok(data, WHITESPACE);
992 char *text = strtok(NULL, WHITESPACE);
994 gtk_combo_box_text_insert_text(combobox, strtol(position, NULL, 10), text);
995 } else
996 ign_cmd(GTK_TYPE_COMBO_BOX_TEXT, whole_msg);
999 static void
1000 update_frame(GtkFrame *frame, char *action, char *data, char *whole_msg)
1002 if (eql(action, "set_label"))
1003 gtk_frame_set_label(frame, data);
1004 else
1005 ign_cmd(GTK_TYPE_FRAME, whole_msg);
1008 static void
1009 update_drawing_area(GtkWidget *widget, char *action, char *data, char *whole_msg)
1011 if (eql(action, "remove")) {
1012 if (!rem_draw_op(widget, data))
1013 ign_cmd(GTK_TYPE_DRAWING_AREA, whole_msg);
1014 } else if (eql(action, "refresh")) {
1015 gint width = gtk_widget_get_allocated_width (widget);
1016 gint height = gtk_widget_get_allocated_height (widget);
1018 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1019 } else if (ins_draw_op(widget, action, data));
1020 else
1021 ign_cmd(GTK_TYPE_DRAWING_AREA, whole_msg);
1024 static void
1025 update_entry(GtkEntry *entry, char *action, char *data, char *whole_msg, GType type)
1027 if (eql(action, "set_text"))
1028 gtk_entry_set_text(entry, data);
1029 else
1030 ign_cmd(type, whole_msg);
1033 static void
1034 update_label(GtkLabel *label, char *action, char *data, char *whole_msg)
1036 if (eql(action, "set_text"))
1037 gtk_label_set_text(label, data);
1038 else
1039 ign_cmd(GTK_TYPE_LABEL, whole_msg);
1042 static void
1043 update_expander(GtkExpander *expander, char *action, char *data, char *whole_msg)
1045 if (eql(action, "set_expanded"))
1046 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1047 else if (eql(action, "set_label"))
1048 gtk_expander_set_label(expander, data);
1049 else
1050 ign_cmd(GTK_TYPE_EXPANDER, whole_msg);
1053 static void
1054 update_file_chooser_button(GtkFileChooser *chooser, char *action, char *data, char *whole_msg)
1056 if (eql(action, "set_filename"))
1057 gtk_file_chooser_set_filename(chooser, data);
1058 else
1059 ign_cmd(GTK_TYPE_FILE_CHOOSER_BUTTON, whole_msg);
1062 static void
1063 update_file_chooser_dialog(GtkFileChooser *chooser, char *action, char *data, char *whole_msg)
1065 if (eql(action, "set_filename"))
1066 gtk_file_chooser_set_filename(chooser, data);
1067 else if (eql(action, "set_current_name"))
1068 gtk_file_chooser_set_current_name(chooser, data);
1069 else
1070 ign_cmd(GTK_TYPE_FILE_CHOOSER_DIALOG, whole_msg);
1073 static void
1074 update_font_button(GtkFontButton *font_button, char *action, char *data, char *whole_msg)
1076 if (eql(action, "set_font_name"))
1077 gtk_font_button_set_font_name(font_button, data);
1078 else
1079 ign_cmd(GTK_TYPE_FONT_BUTTON, whole_msg);
1082 static void
1083 update_image(GtkImage *image, char *action, char *data, char *whole_msg)
1085 GtkIconSize size;
1087 gtk_image_get_icon_name(image, NULL, &size);
1088 if (eql(action, "set_from_file"))
1089 gtk_image_set_from_file(image, data);
1090 else if (eql(action, "set_from_icon_name"))
1091 gtk_image_set_from_icon_name(image, data, size);
1092 else
1093 ign_cmd(GTK_TYPE_IMAGE, whole_msg);
1096 static void
1097 update_notebook(GtkNotebook *notebook, char *action, char *data, char *whole_msg)
1099 if (eql(action, "set_current_page"))
1100 gtk_notebook_set_current_page(notebook, strtol(data, NULL, 10));
1101 else
1102 ign_cmd(GTK_TYPE_NOTEBOOK, whole_msg);
1105 static void
1106 update_progress_bar(GtkProgressBar *progressbar, char *action, char *data, char *whole_msg)
1108 if (eql(action, "set_text"))
1109 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1110 else if (eql(action, "set_fraction"))
1111 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1112 else
1113 ign_cmd(GTK_TYPE_PROGRESS_BAR, whole_msg);
1116 static void
1117 update_scale(GtkRange *range, char *action, char *data, char *whole_msg)
1119 if (eql(action, "set_value"))
1120 gtk_range_set_value(range, strtod(data, NULL));
1121 else
1122 ign_cmd(GTK_TYPE_SCALE, whole_msg);
1125 static void
1126 update_spinner(GtkSpinner *spinner, char *action, char *whole_msg)
1128 if (eql(action, "start"))
1129 gtk_spinner_start(spinner);
1130 else if (eql(action, "stop"))
1131 gtk_spinner_stop(spinner);
1132 else
1133 ign_cmd(GTK_TYPE_SPINNER, whole_msg);
1136 static void
1137 update_statusbar(GtkStatusbar *statusbar, char *action, char *data, char *whole_msg)
1139 if (eql(action, "push"))
1140 gtk_statusbar_push(statusbar, 0, data);
1141 else if (eql(action, "pop"))
1142 gtk_statusbar_pop(statusbar, 0);
1143 else if (eql(action, "remove_all"))
1144 gtk_statusbar_remove_all(statusbar, 0);
1145 else
1146 ign_cmd(GTK_TYPE_STATUSBAR, whole_msg);
1149 static void
1150 update_switch(GtkSwitch *switcher, char *action, char *data, char *whole_msg)
1152 if (eql(action, "set_active"))
1153 gtk_switch_set_active(switcher, strtol(data, NULL, 10));
1154 else
1155 ign_cmd(GTK_TYPE_SWITCH, whole_msg);
1158 static void
1159 update_text_view(GtkTextView *view, char *action, char *data, char *whole_msg)
1161 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1162 GtkTextIter a, b;
1164 if (eql(action, "set_text"))
1165 gtk_text_buffer_set_text(textbuf, data, -1);
1166 else if (eql(action, "delete")) {
1167 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1168 gtk_text_buffer_delete(textbuf, &a, &b);
1169 } else if (eql(action, "insert_at_cursor"))
1170 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1171 else if (eql(action, "place_cursor")) {
1172 if (eql(data, "end"))
1173 gtk_text_buffer_get_end_iter(textbuf, &a);
1174 else /* numeric offset */
1175 gtk_text_buffer_get_iter_at_offset(textbuf, &a, strtol(data, NULL, 10));
1176 gtk_text_buffer_place_cursor(textbuf, &a);
1177 } else if (eql(action, "place_cursor_at_line")) {
1178 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1179 gtk_text_buffer_place_cursor(textbuf, &a);
1180 } else if (eql(action, "scroll_to_cursor"))
1181 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf), 0., 0, 0., 0.);
1182 else
1183 ign_cmd(GTK_TYPE_TEXT_VIEW, whole_msg);
1186 static void
1187 update_toggle_button(GtkToggleButton *toggle, char *action, char *data, char *whole_msg, GType type)
1189 if (eql(action, "set_label"))
1190 gtk_button_set_label(GTK_BUTTON(toggle), data);
1191 else if (eql(action, "set_active"))
1192 gtk_toggle_button_set_active(toggle, strtol(data, NULL, 10));
1193 else
1194 ign_cmd(type, whole_msg);
1197 static void
1198 update_tree_view(GtkTreeView *view, char *action, char *data, char *whole_msg)
1200 GtkTreeModel *model = gtk_tree_view_get_model(view);
1201 GtkListStore *store = GTK_LIST_STORE(model);
1202 GtkTreeIter iter0, iter1;
1203 char *tokens, *arg0_s, *arg1_s, *arg2_s, *endptr;
1204 int arg0_n = 0, arg1_n = 0;
1205 bool arg0_n_valid = false, arg1_n_valid = false;
1206 bool iter0_valid = false, iter1_valid = false;
1208 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1209 OOM_ABORT;
1210 strcpy(tokens, data);
1211 arg0_s = strtok(tokens, WHITESPACE);
1212 arg1_s = strtok(NULL, WHITESPACE);
1213 arg2_s = strtok(NULL, "\n");
1214 errno = 0;
1215 endptr = NULL;
1216 iter0_valid =
1217 arg0_s != NULL &&
1218 (arg0_n_valid = (arg0_n = strtol(arg0_s, &endptr, 10)) >= 0 &&
1219 errno == 0 && endptr != arg0_s) &&
1220 gtk_tree_model_get_iter_from_string(model, &iter0, arg0_s);
1221 errno = 0;
1222 endptr = NULL;
1223 iter1_valid =
1224 arg1_s != NULL &&
1225 (arg1_n_valid = (arg1_n = strtol(arg1_s, &endptr, 10)) >= 0 &&
1226 errno == 0 && endptr != arg1_s) &&
1227 gtk_tree_model_get_iter_from_string(model, &iter1, arg1_s);
1228 if (eql(action, "set") && iter0_valid && arg1_n_valid &&
1229 arg1_n < gtk_tree_model_get_n_columns(model)) {
1230 GType col_type = gtk_tree_model_get_column_type(model, arg1_n);
1231 long long int n;
1232 double d;
1234 switch (col_type) {
1235 case G_TYPE_BOOLEAN:
1236 case G_TYPE_INT:
1237 case G_TYPE_LONG:
1238 case G_TYPE_INT64:
1239 case G_TYPE_UINT:
1240 case G_TYPE_ULONG:
1241 case G_TYPE_UINT64:
1242 errno = 0;
1243 endptr = NULL;
1244 n = strtoll(arg2_s, &endptr, 10);
1245 if (!errno && endptr != arg2_s)
1246 gtk_list_store_set(store, &iter0, arg1_n, n, -1);
1247 else
1248 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1249 break;
1250 case G_TYPE_FLOAT:
1251 case G_TYPE_DOUBLE:
1252 errno = 0;
1253 endptr = NULL;
1254 d = strtod(arg2_s, &endptr);
1255 if (!errno && endptr != arg2_s)
1256 gtk_list_store_set(store, &iter0, arg1_n, d, -1);
1257 else
1258 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1259 gtk_list_store_set(store, &iter0, arg1_n, strtod(arg2_s, NULL), -1);
1260 break;
1261 case G_TYPE_STRING:
1262 gtk_list_store_set(store, &iter0, arg1_n, arg2_s, -1);
1263 break;
1264 default:
1265 fprintf(stderr, "column %d: %s not implemented\n", arg1_n, g_type_name(col_type));
1266 break;
1268 } else if (eql(action, "scroll") && arg0_n_valid && arg1_n_valid)
1269 gtk_tree_view_scroll_to_cell (view,
1270 gtk_tree_path_new_from_string(arg0_s),
1271 gtk_tree_view_get_column(view, arg1_n),
1272 0, 0., 0.);
1273 else if (eql(action, "insert_row"))
1274 if (eql(arg0_s, "end"))
1275 gtk_list_store_insert_before(store, &iter1, NULL);
1276 else if (iter0_valid)
1277 gtk_list_store_insert_before(store, &iter1, &iter0);
1278 else
1279 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1280 else if (eql(action, "move_row") && iter0_valid)
1281 if (eql(arg1_s, "end"))
1282 gtk_list_store_move_before(store, &iter0, NULL);
1283 else if (iter1_valid)
1284 gtk_list_store_move_before(store, &iter0, &iter1);
1285 else
1286 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1287 else if (eql(action, "remove_row") && iter0_valid)
1288 gtk_list_store_remove(store, &iter0);
1289 else
1290 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1291 free(tokens);
1294 static void
1295 update_window(GtkWindow *window, char *action, char *data, char *whole_msg)
1297 if (eql(action, "set_title"))
1298 gtk_window_set_title(window, data);
1299 else
1300 ign_cmd(GTK_TYPE_WINDOW, whole_msg);
1303 static void
1304 fake_ui_activity(GObject *obj, char *whole_msg, GType type)
1306 if (!GTK_IS_WIDGET(obj))
1307 ign_cmd(type, whole_msg);
1308 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
1309 cb(GTK_BUILDABLE(obj), "text");
1310 else if (GTK_IS_SCALE(obj))
1311 cb(GTK_BUILDABLE(obj), "value");
1312 else if (GTK_IS_CALENDAR(obj))
1313 cb(GTK_BUILDABLE(obj), "clicked");
1314 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
1315 cb(GTK_BUILDABLE(obj), "file");
1316 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
1317 ign_cmd(type, whole_msg);
1321 * Parse command pointed to by ud, and act on ui accordingly. Set
1322 * ud->digested = true if done. Runs once per command inside
1323 * gtk_main_loop()
1325 static gboolean
1326 update_ui(struct ui_data *ud)
1328 char name[ud->msg_size], action[ud->msg_size];
1329 char *data;
1330 int data_start = strlen(ud->msg);
1331 GObject *obj = NULL;
1332 GType type = G_TYPE_INVALID;
1334 name[0] = action[0] = '\0';
1335 sscanf(ud->msg,
1336 " %[0-9a-zA-Z_]:%[0-9a-zA-Z_]%*1[ \t]%n",
1337 name, action, &data_start);
1338 if (eql(action, "main_quit")) {
1339 gtk_main_quit();
1340 goto done;
1342 if ((obj = (gtk_builder_get_object(ud->builder, name))) == NULL) {
1343 ign_cmd(type, ud->msg);
1344 goto done;
1346 type = G_TYPE_FROM_INSTANCE(obj);
1347 data = ud->msg + data_start;
1348 if (eql(action, "force"))
1349 fake_ui_activity(obj, ud->msg, type);
1350 else if (eql(action, "set_sensitive"))
1351 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1352 else if (eql(action, "set_visible"))
1353 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
1354 else if (eql(action, "override_font"))
1355 update_widget_font(GTK_WIDGET(obj), data);
1356 else if (eql(action, "override_color"))
1357 update_widget_color(GTK_WIDGET(obj), data);
1358 else if (eql(action, "override_background_color"))
1359 update_widget_background_color(GTK_WIDGET(obj), data);
1360 else if (type == GTK_TYPE_LABEL)
1361 update_label(GTK_LABEL(obj), action, data, ud->msg);
1362 else if (type == GTK_TYPE_IMAGE)
1363 update_image(GTK_IMAGE(obj), action, data, ud->msg);
1364 else if (type == GTK_TYPE_TEXT_VIEW)
1365 update_text_view(GTK_TEXT_VIEW(obj), action, data, ud->msg);
1366 else if (type == GTK_TYPE_NOTEBOOK)
1367 update_notebook(GTK_NOTEBOOK(obj), action, data, ud->msg);
1368 else if (type == GTK_TYPE_EXPANDER)
1369 update_expander(GTK_EXPANDER(obj), action, data, ud->msg);
1370 else if (type == GTK_TYPE_FRAME)
1371 update_frame(GTK_FRAME(obj), action, data, ud->msg);
1372 else if (type == GTK_TYPE_BUTTON)
1373 update_button(GTK_BUTTON(obj), action, data, ud->msg);
1374 else if (type == GTK_TYPE_FILE_CHOOSER_DIALOG)
1375 update_file_chooser_dialog(GTK_FILE_CHOOSER(obj), action, data, ud->msg);
1376 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1377 update_file_chooser_button(GTK_FILE_CHOOSER(obj), action, data, ud->msg);
1378 else if (type == GTK_TYPE_COLOR_BUTTON)
1379 update_color_button(GTK_COLOR_CHOOSER(obj), action, data, ud->msg);
1380 else if (type == GTK_TYPE_FONT_BUTTON)
1381 update_font_button(GTK_FONT_BUTTON(obj), action, data, ud->msg);
1382 else if (type == GTK_TYPE_SWITCH)
1383 update_switch(GTK_SWITCH(obj), action, data, ud->msg);
1384 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
1385 update_toggle_button(GTK_TOGGLE_BUTTON(obj), action, data, ud->msg, type);
1386 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
1387 update_entry(GTK_ENTRY(obj), action, data, ud->msg, type);
1388 else if (type == GTK_TYPE_SCALE)
1389 update_scale(GTK_RANGE(obj), action, data, ud->msg);
1390 else if (type == GTK_TYPE_PROGRESS_BAR)
1391 update_progress_bar(GTK_PROGRESS_BAR(obj), action, data, ud->msg);
1392 else if (type == GTK_TYPE_SPINNER)
1393 update_spinner(GTK_SPINNER(obj), action, ud->msg);
1394 else if (type == GTK_TYPE_COMBO_BOX_TEXT)
1395 update_combo_box_text(GTK_COMBO_BOX_TEXT(obj), action, data, ud->msg);
1396 else if (type == GTK_TYPE_STATUSBAR)
1397 update_statusbar(GTK_STATUSBAR(obj), action, data, ud->msg);
1398 else if (type == GTK_TYPE_CALENDAR)
1399 update_calendar(GTK_CALENDAR(obj), action, data, ud->msg);
1400 else if (type == GTK_TYPE_TREE_VIEW)
1401 update_tree_view(GTK_TREE_VIEW(obj), action, data, ud->msg);
1402 else if (type == GTK_TYPE_DRAWING_AREA)
1403 update_drawing_area(GTK_WIDGET(obj), action, data, ud->msg);
1404 else if (type == GTK_TYPE_WINDOW)
1405 update_window(GTK_WINDOW(obj), action, data, ud->msg);
1406 else
1407 ign_cmd(type, ud->msg);
1408 done:
1409 ud->msg_digested = true;
1410 return G_SOURCE_REMOVE;
1413 static void
1414 free_at(void **mem)
1416 free(*mem);
1420 * Read lines from global stream "in" and perform the appropriate
1421 * actions on the GUI
1423 static void *
1424 digest_msg(void *builder)
1426 for (;;) {
1427 char first_char = '\0';
1428 struct ui_data ud;
1430 if ((ud.msg = malloc(ud.msg_size = 32)) == NULL )
1431 OOM_ABORT;
1432 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
1433 pthread_testcancel();
1434 read_buf(in, &ud.msg, &ud.msg_size);
1435 sscanf(ud.msg, " %c", &first_char);
1436 ud.builder = builder;
1437 if (first_char != '#') {
1438 ud.msg_digested = false;
1439 pthread_testcancel();
1440 gdk_threads_add_timeout(1, (GSourceFunc)update_ui, &ud);
1441 while (!ud.msg_digested)
1442 nanosleep(&(struct timespec){0, 1e6}, NULL);
1444 pthread_cleanup_pop(1);
1446 return NULL;
1450 * Create a fifo if necessary, and open it. Give up if the file
1451 * exists but is not a fifo
1453 static FILE *
1454 fifo(const char *name, const char *mode)
1456 struct stat sb;
1457 int fd;
1458 FILE *stream;
1460 if (name != NULL && (stat(name, &sb), !S_ISFIFO(sb.st_mode)))
1461 if (mkfifo(name, 0666) != 0) {
1462 perror("making fifo");
1463 exit(EXIT_FAILURE);
1465 switch (mode[0]) {
1466 case 'r':
1467 if (name == NULL)
1468 stream = stdin;
1469 else {
1470 fd = open(name, O_RDWR | O_NONBLOCK);
1471 if (fd < 0) {
1472 perror("opening fifo");
1473 exit(EXIT_FAILURE);
1475 stream = fdopen(fd, "r");
1477 break;
1478 case 'w':
1479 if (name == NULL)
1480 stream = stdout;
1481 else {
1482 /* fopen blocks if there is no reader, so here is one */
1483 fd = open(name, O_RDONLY | O_NONBLOCK);
1484 if (fd < 0) {
1485 perror("opening fifo");
1486 exit(EXIT_FAILURE);
1488 stream = fopen(name, "w");
1489 /* unblocking done */
1490 close(fd);
1492 break;
1493 default:
1494 abort();
1495 break;
1497 if (stream == NULL) {
1498 perror("opening fifo");
1499 exit(EXIT_FAILURE);
1500 } else {
1501 setvbuf(stream, NULL, _IOLBF, 0);
1502 return stream;
1506 static GObject *
1507 obj_sans_suffix(char *suffix, const char *name, gpointer *builder)
1509 int str_l;
1510 char str[BUFLEN + 1] = {'\0'};
1512 str_l = suffix - name;
1513 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
1514 return gtk_builder_get_object(GTK_BUILDER(builder), str);
1517 static void
1518 connect_widget_signals(gpointer *obj, gpointer *builder)
1520 const char *name = NULL;
1521 char *suffix = NULL;
1522 GObject *obj2;
1523 GType type = G_TYPE_INVALID;
1525 type = G_TYPE_FROM_INSTANCE(obj);
1526 if (GTK_IS_BUILDABLE(obj))
1527 name = widget_name(obj);
1528 if (type == GTK_TYPE_TREE_VIEW_COLUMN)
1529 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1530 if (type == GTK_TYPE_BUTTON) {
1531 /* button associated with a GtkTextView */
1532 if ((suffix = strstr(name, "_send_text")) != NULL &&
1533 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name, builder)))
1534 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
1535 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
1536 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
1537 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name, builder)))
1538 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
1539 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
1540 /* button associated with (and part of) a GtkDialog */
1541 else if ((suffix = strstr(name, "_cancel")) != NULL &&
1542 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1543 if (eql(widget_name(obj2), MAIN_WIN))
1544 g_signal_connect(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
1545 else
1546 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
1547 else if ((suffix = strstr(name, "_ok")) != NULL &&
1548 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder))) {
1549 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
1550 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
1551 else /* generic button */
1552 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1553 if (eql(widget_name(obj2), MAIN_WIN))
1554 g_signal_connect(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
1555 else
1556 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
1557 } else if ((suffix = strstr(name, "_apply")) != NULL &&
1558 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1559 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
1560 else /* generic button */
1561 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1563 else if (GTK_IS_MENU_ITEM(obj))
1564 if ((suffix = strstr(name, "_invoke")) != NULL &&
1565 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1566 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
1567 else
1568 g_signal_connect(obj, "activate", G_CALLBACK(cb), "active");
1569 else if (GTK_IS_WINDOW(obj))
1570 if (eql(name, MAIN_WIN))
1571 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
1572 else
1573 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1574 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1575 g_signal_connect(obj, "file-set", G_CALLBACK(cb), "file");
1576 else if (type == GTK_TYPE_COLOR_BUTTON)
1577 g_signal_connect(obj, "color-set", G_CALLBACK(cb), "color");
1578 else if (type == GTK_TYPE_FONT_BUTTON)
1579 g_signal_connect(obj, "font-set", G_CALLBACK(cb), "font");
1580 else if (type == GTK_TYPE_SWITCH)
1581 g_signal_connect(obj, "notify::active", G_CALLBACK(cb), NULL);
1582 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
1583 g_signal_connect(obj, "toggled", G_CALLBACK(cb), NULL);
1584 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
1585 g_signal_connect(obj, "changed", G_CALLBACK(cb), "text");
1586 else if (type == GTK_TYPE_SCALE)
1587 g_signal_connect(obj, "value-changed", G_CALLBACK(cb), "value");
1588 else if (type == GTK_TYPE_CALENDAR) {
1589 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb), "doubleclicked");
1590 g_signal_connect(obj, "day-selected", G_CALLBACK(cb), "clicked");
1591 } else if (type == GTK_TYPE_TREE_SELECTION)
1592 g_signal_connect(obj, "changed", G_CALLBACK(cb), "clicked");
1593 else if (type == GTK_TYPE_DRAWING_AREA)
1594 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
1597 static void
1598 connect_signals(GtkBuilder *builder)
1600 GSList *objects = NULL;
1602 objects = gtk_builder_get_objects(builder);
1603 g_slist_foreach(objects, (GFunc)connect_widget_signals, builder);
1604 g_slist_free(objects);
1608 main(int argc, char *argv[])
1610 char opt;
1611 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
1612 GtkBuilder *builder;
1613 pthread_t receiver;
1614 GError *error = NULL;
1615 GObject *main_window = NULL;
1617 in = NULL;
1618 out = NULL;
1619 while ((opt = getopt(argc, argv, "hi:o:u:GV")) != -1) {
1620 switch (opt) {
1621 case 'i': in_fifo = optarg; break;
1622 case 'o': out_fifo = optarg; break;
1623 case 'u': ui_file = optarg; break;
1624 case 'G': gtk_versions(); break;
1625 case 'V': version(); break;
1626 case '?':
1627 case 'h':
1628 default: usage(argv); break;
1631 if (argv[optind] != NULL) {
1632 fprintf(stderr, "illegal parameter '%s'\n", argv[optind]);
1633 usage(argv);
1635 if (ui_file == NULL)
1636 ui_file = "pipeglade.ui";
1637 gtk_init(0, NULL);
1638 builder = gtk_builder_new();
1639 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0) {
1640 fprintf(stderr, "%s\n", error->message);
1641 exit(EXIT_FAILURE);
1643 in = fifo(in_fifo, "r");
1644 out = fifo(out_fifo, "w");
1645 pthread_create(&receiver, NULL, digest_msg, (void*)builder);
1646 main_window = gtk_builder_get_object(builder, MAIN_WIN);
1647 if (!GTK_IS_WINDOW(main_window)) {
1648 fprintf(stderr, "no toplevel window named \'" MAIN_WIN "\'\n");
1649 exit(EXIT_FAILURE);
1651 connect_signals(builder);
1652 gtk_widget_show(GTK_WIDGET(main_window));
1653 gtk_main();
1654 if (in != stdin) {
1655 fclose(in);
1656 unlink(in_fifo);
1658 if (out != stdout) {
1659 fclose(out);
1660 unlink(out_fifo);
1662 pthread_cancel(receiver);
1663 pthread_join(receiver, NULL);
1664 exit(EXIT_SUCCESS);