Add style command; follow GTK's deprecation of overwrite_* commands
[pipeglade.git] / pipeglade.c
blobd09a1de29f753cdfb767a23314c82c479219c2cf
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.1.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 * One style provider for each widget
896 struct style_provider {
897 struct style_provider *next;
898 struct style_provider *prev;
899 const char *name;
900 GtkCssProvider *provider;
901 char *style_decl;
902 } *widget_style_providers = NULL;
905 * Perform various kinds of actions on the widget passed
907 static void
908 update_widget_font(GtkWidget *widget, char *data)
910 if (data[0]) {
911 PangoFontDescription *font = pango_font_description_from_string(data);
913 gtk_widget_override_font(widget, font);
914 pango_font_description_free(font);
915 } else
916 gtk_widget_override_font(widget, NULL);
919 static void
920 update_widget_style(GtkWidget *widget, char *name , char *data)
922 GtkStyleContext *context;
923 struct style_provider *sp;
924 char *prefix = "* {", *suffix = "}";
925 size_t sz;
927 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
928 context = gtk_widget_get_style_context(widget);
929 for (sp = widget_style_providers; !eql(name, sp->name); sp = sp->next);
930 gtk_style_context_remove_provider(context, GTK_STYLE_PROVIDER(sp->provider));
931 free(sp->style_decl);
932 if ((sp->style_decl = malloc(sz)) == NULL)
933 OOM_ABORT;
934 strcpy(sp->style_decl, prefix);
935 strcat(sp->style_decl, data);
936 strcat(sp->style_decl, suffix);
937 gtk_style_context_add_provider(context,
938 GTK_STYLE_PROVIDER(sp->provider),
939 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
940 gtk_css_provider_load_from_data(sp->provider, sp->style_decl, -1, NULL);
943 static void
944 update_widget_color(GtkWidget *widget, char *data)
946 GdkRGBA color;
948 if (data[0]) {
949 gdk_rgba_parse(&color, data);
950 gtk_widget_override_color(widget, GTK_STATE_FLAG_NORMAL, &color);
951 } else
952 gtk_widget_override_color(widget, GTK_STATE_FLAG_NORMAL, NULL);
955 static void
956 update_widget_background_color(GtkWidget *widget, char *data)
958 GdkRGBA color;
960 if (data[0]) {
961 gdk_rgba_parse(&color, data);
962 gtk_widget_override_background_color(widget, GTK_STATE_FLAG_NORMAL, &color);
963 } else
964 gtk_widget_override_background_color(widget, GTK_STATE_FLAG_NORMAL, NULL);
968 * Update various kinds of widgets according to the respective action
969 * parameter
971 static void
972 update_button(GtkButton *button, char *action, char *data, char *whole_msg)
974 if (eql(action, "set_label"))
975 gtk_button_set_label(button, data);
976 else
977 ign_cmd(GTK_TYPE_BUTTON, whole_msg);
980 static void
981 update_calendar(GtkCalendar *calendar, char *action, char *data, char *whole_msg)
983 int year = 0, month = 0, day = 0;
985 if (eql(action, "select_date")) {
986 sscanf(data, "%d-%d-%d", &year, &month, &day);
987 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
988 gtk_calendar_select_month(calendar, --month, year);
989 gtk_calendar_select_day(calendar, day);
990 } else
991 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
992 } else if (eql(action, "mark_day")) {
993 day = strtol(data, NULL, 10);
994 if (day > 0 && day <= 31)
995 gtk_calendar_mark_day(calendar, day);
996 else
997 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
998 } else if (eql(action, "clear_marks"))
999 gtk_calendar_clear_marks(calendar);
1000 else
1001 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
1004 static void
1005 update_color_button(GtkColorChooser *chooser, char *action, char *data, char *whole_msg)
1007 GdkRGBA color;
1009 if (eql(action, "set_color")) {
1010 gdk_rgba_parse(&color, data);
1011 gtk_color_chooser_set_rgba(chooser, &color);
1012 } else
1013 ign_cmd(GTK_TYPE_COLOR_BUTTON, whole_msg);
1016 static void
1017 update_combo_box_text(GtkComboBoxText *combobox, char *action, char *data, char *whole_msg)
1019 if (eql(action, "prepend_text"))
1020 gtk_combo_box_text_prepend_text(combobox, data);
1021 else if (eql(action, "append_text"))
1022 gtk_combo_box_text_append_text(combobox, data);
1023 else if (eql(action, "remove"))
1024 gtk_combo_box_text_remove(combobox, strtol(data, NULL, 10));
1025 else if (eql(action, "insert_text")) {
1026 char *position = strtok(data, WHITESPACE);
1027 char *text = strtok(NULL, WHITESPACE);
1029 gtk_combo_box_text_insert_text(combobox, strtol(position, NULL, 10), text);
1030 } else
1031 ign_cmd(GTK_TYPE_COMBO_BOX_TEXT, whole_msg);
1034 static void
1035 update_frame(GtkFrame *frame, char *action, char *data, char *whole_msg)
1037 if (eql(action, "set_label"))
1038 gtk_frame_set_label(frame, data);
1039 else
1040 ign_cmd(GTK_TYPE_FRAME, whole_msg);
1043 static void
1044 update_drawing_area(GtkWidget *widget, char *action, char *data, char *whole_msg)
1046 if (eql(action, "remove")) {
1047 if (!rem_draw_op(widget, data))
1048 ign_cmd(GTK_TYPE_DRAWING_AREA, whole_msg);
1049 } else if (eql(action, "refresh")) {
1050 gint width = gtk_widget_get_allocated_width (widget);
1051 gint height = gtk_widget_get_allocated_height (widget);
1053 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1054 } else if (ins_draw_op(widget, action, data));
1055 else
1056 ign_cmd(GTK_TYPE_DRAWING_AREA, whole_msg);
1059 static void
1060 update_entry(GtkEntry *entry, char *action, char *data, char *whole_msg, GType type)
1062 if (eql(action, "set_text"))
1063 gtk_entry_set_text(entry, data);
1064 else
1065 ign_cmd(type, whole_msg);
1068 static void
1069 update_label(GtkLabel *label, char *action, char *data, char *whole_msg)
1071 if (eql(action, "set_text"))
1072 gtk_label_set_text(label, data);
1073 else
1074 ign_cmd(GTK_TYPE_LABEL, whole_msg);
1077 static void
1078 update_expander(GtkExpander *expander, char *action, char *data, char *whole_msg)
1080 if (eql(action, "set_expanded"))
1081 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1082 else if (eql(action, "set_label"))
1083 gtk_expander_set_label(expander, data);
1084 else
1085 ign_cmd(GTK_TYPE_EXPANDER, whole_msg);
1088 static void
1089 update_file_chooser_button(GtkFileChooser *chooser, char *action, char *data, char *whole_msg)
1091 if (eql(action, "set_filename"))
1092 gtk_file_chooser_set_filename(chooser, data);
1093 else
1094 ign_cmd(GTK_TYPE_FILE_CHOOSER_BUTTON, whole_msg);
1097 static void
1098 update_file_chooser_dialog(GtkFileChooser *chooser, char *action, char *data, char *whole_msg)
1100 if (eql(action, "set_filename"))
1101 gtk_file_chooser_set_filename(chooser, data);
1102 else if (eql(action, "set_current_name"))
1103 gtk_file_chooser_set_current_name(chooser, data);
1104 else
1105 ign_cmd(GTK_TYPE_FILE_CHOOSER_DIALOG, whole_msg);
1108 static void
1109 update_font_button(GtkFontButton *font_button, char *action, char *data, char *whole_msg)
1111 if (eql(action, "set_font_name"))
1112 gtk_font_button_set_font_name(font_button, data);
1113 else
1114 ign_cmd(GTK_TYPE_FONT_BUTTON, whole_msg);
1117 static void
1118 update_image(GtkImage *image, char *action, char *data, char *whole_msg)
1120 GtkIconSize size;
1122 gtk_image_get_icon_name(image, NULL, &size);
1123 if (eql(action, "set_from_file"))
1124 gtk_image_set_from_file(image, data);
1125 else if (eql(action, "set_from_icon_name"))
1126 gtk_image_set_from_icon_name(image, data, size);
1127 else
1128 ign_cmd(GTK_TYPE_IMAGE, whole_msg);
1131 static void
1132 update_notebook(GtkNotebook *notebook, char *action, char *data, char *whole_msg)
1134 if (eql(action, "set_current_page"))
1135 gtk_notebook_set_current_page(notebook, strtol(data, NULL, 10));
1136 else
1137 ign_cmd(GTK_TYPE_NOTEBOOK, whole_msg);
1140 static void
1141 update_progress_bar(GtkProgressBar *progressbar, char *action, char *data, char *whole_msg)
1143 if (eql(action, "set_text"))
1144 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1145 else if (eql(action, "set_fraction"))
1146 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1147 else
1148 ign_cmd(GTK_TYPE_PROGRESS_BAR, whole_msg);
1151 static void
1152 update_scale(GtkRange *range, char *action, char *data, char *whole_msg)
1154 if (eql(action, "set_value"))
1155 gtk_range_set_value(range, strtod(data, NULL));
1156 else
1157 ign_cmd(GTK_TYPE_SCALE, whole_msg);
1160 static void
1161 update_spinner(GtkSpinner *spinner, char *action, char *whole_msg)
1163 if (eql(action, "start"))
1164 gtk_spinner_start(spinner);
1165 else if (eql(action, "stop"))
1166 gtk_spinner_stop(spinner);
1167 else
1168 ign_cmd(GTK_TYPE_SPINNER, whole_msg);
1171 static void
1172 update_statusbar(GtkStatusbar *statusbar, char *action, char *data, char *whole_msg)
1174 if (eql(action, "push"))
1175 gtk_statusbar_push(statusbar, 0, data);
1176 else if (eql(action, "pop"))
1177 gtk_statusbar_pop(statusbar, 0);
1178 else if (eql(action, "remove_all"))
1179 gtk_statusbar_remove_all(statusbar, 0);
1180 else
1181 ign_cmd(GTK_TYPE_STATUSBAR, whole_msg);
1184 static void
1185 update_switch(GtkSwitch *switcher, char *action, char *data, char *whole_msg)
1187 if (eql(action, "set_active"))
1188 gtk_switch_set_active(switcher, strtol(data, NULL, 10));
1189 else
1190 ign_cmd(GTK_TYPE_SWITCH, whole_msg);
1193 static void
1194 update_text_view(GtkTextView *view, char *action, char *data, char *whole_msg)
1196 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1197 GtkTextIter a, b;
1199 if (eql(action, "set_text"))
1200 gtk_text_buffer_set_text(textbuf, data, -1);
1201 else if (eql(action, "delete")) {
1202 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1203 gtk_text_buffer_delete(textbuf, &a, &b);
1204 } else if (eql(action, "insert_at_cursor"))
1205 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1206 else if (eql(action, "place_cursor")) {
1207 if (eql(data, "end"))
1208 gtk_text_buffer_get_end_iter(textbuf, &a);
1209 else /* numeric offset */
1210 gtk_text_buffer_get_iter_at_offset(textbuf, &a, strtol(data, NULL, 10));
1211 gtk_text_buffer_place_cursor(textbuf, &a);
1212 } else if (eql(action, "place_cursor_at_line")) {
1213 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1214 gtk_text_buffer_place_cursor(textbuf, &a);
1215 } else if (eql(action, "scroll_to_cursor"))
1216 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf), 0., 0, 0., 0.);
1217 else
1218 ign_cmd(GTK_TYPE_TEXT_VIEW, whole_msg);
1221 static void
1222 update_toggle_button(GtkToggleButton *toggle, char *action, char *data, char *whole_msg, GType type)
1224 if (eql(action, "set_label"))
1225 gtk_button_set_label(GTK_BUTTON(toggle), data);
1226 else if (eql(action, "set_active"))
1227 gtk_toggle_button_set_active(toggle, strtol(data, NULL, 10));
1228 else
1229 ign_cmd(type, whole_msg);
1232 static void
1233 update_tree_view(GtkTreeView *view, char *action, char *data, char *whole_msg)
1235 GtkTreeModel *model = gtk_tree_view_get_model(view);
1236 GtkListStore *store = GTK_LIST_STORE(model);
1237 GtkTreeIter iter0, iter1;
1238 char *tokens, *arg0_s, *arg1_s, *arg2_s, *endptr;
1239 int arg0_n = 0, arg1_n = 0;
1240 bool arg0_n_valid = false, arg1_n_valid = false;
1241 bool iter0_valid = false, iter1_valid = false;
1243 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1244 OOM_ABORT;
1245 strcpy(tokens, data);
1246 arg0_s = strtok(tokens, WHITESPACE);
1247 arg1_s = strtok(NULL, WHITESPACE);
1248 arg2_s = strtok(NULL, "\n");
1249 errno = 0;
1250 endptr = NULL;
1251 iter0_valid =
1252 arg0_s != NULL &&
1253 (arg0_n_valid = (arg0_n = strtol(arg0_s, &endptr, 10)) >= 0 &&
1254 errno == 0 && endptr != arg0_s) &&
1255 gtk_tree_model_get_iter_from_string(model, &iter0, arg0_s);
1256 errno = 0;
1257 endptr = NULL;
1258 iter1_valid =
1259 arg1_s != NULL &&
1260 (arg1_n_valid = (arg1_n = strtol(arg1_s, &endptr, 10)) >= 0 &&
1261 errno == 0 && endptr != arg1_s) &&
1262 gtk_tree_model_get_iter_from_string(model, &iter1, arg1_s);
1263 if (eql(action, "set") && iter0_valid && arg1_n_valid &&
1264 arg1_n < gtk_tree_model_get_n_columns(model)) {
1265 GType col_type = gtk_tree_model_get_column_type(model, arg1_n);
1266 long long int n;
1267 double d;
1269 switch (col_type) {
1270 case G_TYPE_BOOLEAN:
1271 case G_TYPE_INT:
1272 case G_TYPE_LONG:
1273 case G_TYPE_INT64:
1274 case G_TYPE_UINT:
1275 case G_TYPE_ULONG:
1276 case G_TYPE_UINT64:
1277 errno = 0;
1278 endptr = NULL;
1279 n = strtoll(arg2_s, &endptr, 10);
1280 if (!errno && endptr != arg2_s)
1281 gtk_list_store_set(store, &iter0, arg1_n, n, -1);
1282 else
1283 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1284 break;
1285 case G_TYPE_FLOAT:
1286 case G_TYPE_DOUBLE:
1287 errno = 0;
1288 endptr = NULL;
1289 d = strtod(arg2_s, &endptr);
1290 if (!errno && endptr != arg2_s)
1291 gtk_list_store_set(store, &iter0, arg1_n, d, -1);
1292 else
1293 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1294 gtk_list_store_set(store, &iter0, arg1_n, strtod(arg2_s, NULL), -1);
1295 break;
1296 case G_TYPE_STRING:
1297 gtk_list_store_set(store, &iter0, arg1_n, arg2_s, -1);
1298 break;
1299 default:
1300 fprintf(stderr, "column %d: %s not implemented\n", arg1_n, g_type_name(col_type));
1301 break;
1303 } else if (eql(action, "scroll") && arg0_n_valid && arg1_n_valid)
1304 gtk_tree_view_scroll_to_cell (view,
1305 gtk_tree_path_new_from_string(arg0_s),
1306 gtk_tree_view_get_column(view, arg1_n),
1307 0, 0., 0.);
1308 else if (eql(action, "insert_row"))
1309 if (eql(arg0_s, "end"))
1310 gtk_list_store_insert_before(store, &iter1, NULL);
1311 else if (iter0_valid)
1312 gtk_list_store_insert_before(store, &iter1, &iter0);
1313 else
1314 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1315 else if (eql(action, "move_row") && iter0_valid)
1316 if (eql(arg1_s, "end"))
1317 gtk_list_store_move_before(store, &iter0, NULL);
1318 else if (iter1_valid)
1319 gtk_list_store_move_before(store, &iter0, &iter1);
1320 else
1321 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1322 else if (eql(action, "remove_row") && iter0_valid)
1323 gtk_list_store_remove(store, &iter0);
1324 else
1325 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1326 free(tokens);
1329 static void
1330 update_window(GtkWindow *window, char *action, char *data, char *whole_msg)
1332 if (eql(action, "set_title"))
1333 gtk_window_set_title(window, data);
1334 else
1335 ign_cmd(GTK_TYPE_WINDOW, whole_msg);
1338 static void
1339 fake_ui_activity(GObject *obj, char *whole_msg, GType type)
1341 if (!GTK_IS_WIDGET(obj))
1342 ign_cmd(type, whole_msg);
1343 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
1344 cb(GTK_BUILDABLE(obj), "text");
1345 else if (GTK_IS_SCALE(obj))
1346 cb(GTK_BUILDABLE(obj), "value");
1347 else if (GTK_IS_CALENDAR(obj))
1348 cb(GTK_BUILDABLE(obj), "clicked");
1349 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
1350 cb(GTK_BUILDABLE(obj), "file");
1351 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
1352 ign_cmd(type, whole_msg);
1356 * Parse command pointed to by ud, and act on ui accordingly. Set
1357 * ud->digested = true if done. Runs once per command inside
1358 * gtk_main_loop()
1360 static gboolean
1361 update_ui(struct ui_data *ud)
1363 char name[ud->msg_size], action[ud->msg_size];
1364 char *data;
1365 int data_start = strlen(ud->msg);
1366 GObject *obj = NULL;
1367 GType type = G_TYPE_INVALID;
1369 name[0] = action[0] = '\0';
1370 sscanf(ud->msg,
1371 " %[0-9a-zA-Z_]:%[0-9a-zA-Z_]%*1[ \t]%n",
1372 name, action, &data_start);
1373 if (eql(action, "main_quit")) {
1374 gtk_main_quit();
1375 goto done;
1377 if ((obj = (gtk_builder_get_object(ud->builder, name))) == NULL) {
1378 ign_cmd(type, ud->msg);
1379 goto done;
1381 type = G_TYPE_FROM_INSTANCE(obj);
1382 data = ud->msg + data_start;
1383 if (eql(action, "force"))
1384 fake_ui_activity(obj, ud->msg, type);
1385 else if (eql(action, "set_sensitive"))
1386 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1387 else if (eql(action, "set_visible"))
1388 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
1389 else if (eql(action, "style"))
1390 update_widget_style(GTK_WIDGET(obj), name, data);
1391 else if (eql(action, "override_font"))
1392 update_widget_font(GTK_WIDGET(obj), data);
1393 else if (eql(action, "override_color"))
1394 update_widget_color(GTK_WIDGET(obj), data);
1395 else if (eql(action, "override_background_color"))
1396 update_widget_background_color(GTK_WIDGET(obj), data);
1397 else if (type == GTK_TYPE_LABEL)
1398 update_label(GTK_LABEL(obj), action, data, ud->msg);
1399 else if (type == GTK_TYPE_IMAGE)
1400 update_image(GTK_IMAGE(obj), action, data, ud->msg);
1401 else if (type == GTK_TYPE_TEXT_VIEW)
1402 update_text_view(GTK_TEXT_VIEW(obj), action, data, ud->msg);
1403 else if (type == GTK_TYPE_NOTEBOOK)
1404 update_notebook(GTK_NOTEBOOK(obj), action, data, ud->msg);
1405 else if (type == GTK_TYPE_EXPANDER)
1406 update_expander(GTK_EXPANDER(obj), action, data, ud->msg);
1407 else if (type == GTK_TYPE_FRAME)
1408 update_frame(GTK_FRAME(obj), action, data, ud->msg);
1409 else if (type == GTK_TYPE_BUTTON)
1410 update_button(GTK_BUTTON(obj), action, data, ud->msg);
1411 else if (type == GTK_TYPE_FILE_CHOOSER_DIALOG)
1412 update_file_chooser_dialog(GTK_FILE_CHOOSER(obj), action, data, ud->msg);
1413 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1414 update_file_chooser_button(GTK_FILE_CHOOSER(obj), action, data, ud->msg);
1415 else if (type == GTK_TYPE_COLOR_BUTTON)
1416 update_color_button(GTK_COLOR_CHOOSER(obj), action, data, ud->msg);
1417 else if (type == GTK_TYPE_FONT_BUTTON)
1418 update_font_button(GTK_FONT_BUTTON(obj), action, data, ud->msg);
1419 else if (type == GTK_TYPE_SWITCH)
1420 update_switch(GTK_SWITCH(obj), action, data, ud->msg);
1421 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
1422 update_toggle_button(GTK_TOGGLE_BUTTON(obj), action, data, ud->msg, type);
1423 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
1424 update_entry(GTK_ENTRY(obj), action, data, ud->msg, type);
1425 else if (type == GTK_TYPE_SCALE)
1426 update_scale(GTK_RANGE(obj), action, data, ud->msg);
1427 else if (type == GTK_TYPE_PROGRESS_BAR)
1428 update_progress_bar(GTK_PROGRESS_BAR(obj), action, data, ud->msg);
1429 else if (type == GTK_TYPE_SPINNER)
1430 update_spinner(GTK_SPINNER(obj), action, ud->msg);
1431 else if (type == GTK_TYPE_COMBO_BOX_TEXT)
1432 update_combo_box_text(GTK_COMBO_BOX_TEXT(obj), action, data, ud->msg);
1433 else if (type == GTK_TYPE_STATUSBAR)
1434 update_statusbar(GTK_STATUSBAR(obj), action, data, ud->msg);
1435 else if (type == GTK_TYPE_CALENDAR)
1436 update_calendar(GTK_CALENDAR(obj), action, data, ud->msg);
1437 else if (type == GTK_TYPE_TREE_VIEW)
1438 update_tree_view(GTK_TREE_VIEW(obj), action, data, ud->msg);
1439 else if (type == GTK_TYPE_DRAWING_AREA)
1440 update_drawing_area(GTK_WIDGET(obj), action, data, ud->msg);
1441 else if (type == GTK_TYPE_WINDOW)
1442 update_window(GTK_WINDOW(obj), action, data, ud->msg);
1443 else
1444 ign_cmd(type, ud->msg);
1445 done:
1446 ud->msg_digested = true;
1447 return G_SOURCE_REMOVE;
1450 static void
1451 free_at(void **mem)
1453 free(*mem);
1457 * Read lines from global stream "in" and perform the appropriate
1458 * actions on the GUI
1460 static void *
1461 digest_msg(void *builder)
1463 for (;;) {
1464 char first_char = '\0';
1465 struct ui_data ud;
1467 if ((ud.msg = malloc(ud.msg_size = 32)) == NULL )
1468 OOM_ABORT;
1469 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
1470 pthread_testcancel();
1471 read_buf(in, &ud.msg, &ud.msg_size);
1472 sscanf(ud.msg, " %c", &first_char);
1473 ud.builder = builder;
1474 if (first_char != '#') {
1475 ud.msg_digested = false;
1476 pthread_testcancel();
1477 gdk_threads_add_timeout(1, (GSourceFunc)update_ui, &ud);
1478 while (!ud.msg_digested)
1479 nanosleep(&(struct timespec){0, 1e6}, NULL);
1481 pthread_cleanup_pop(1);
1483 return NULL;
1487 * Create a fifo if necessary, and open it. Give up if the file
1488 * exists but is not a fifo
1490 static FILE *
1491 fifo(const char *name, const char *mode)
1493 struct stat sb;
1494 int fd;
1495 FILE *stream;
1497 if (name != NULL && (stat(name, &sb), !S_ISFIFO(sb.st_mode)))
1498 if (mkfifo(name, 0666) != 0) {
1499 perror("making fifo");
1500 exit(EXIT_FAILURE);
1502 switch (mode[0]) {
1503 case 'r':
1504 if (name == NULL)
1505 stream = stdin;
1506 else {
1507 fd = open(name, O_RDWR | O_NONBLOCK);
1508 if (fd < 0) {
1509 perror("opening fifo");
1510 exit(EXIT_FAILURE);
1512 stream = fdopen(fd, "r");
1514 break;
1515 case 'w':
1516 if (name == NULL)
1517 stream = stdout;
1518 else {
1519 /* fopen blocks if there is no reader, so here is one */
1520 fd = open(name, O_RDONLY | O_NONBLOCK);
1521 if (fd < 0) {
1522 perror("opening fifo");
1523 exit(EXIT_FAILURE);
1525 stream = fopen(name, "w");
1526 /* unblocking done */
1527 close(fd);
1529 break;
1530 default:
1531 abort();
1532 break;
1534 if (stream == NULL) {
1535 perror("opening fifo");
1536 exit(EXIT_FAILURE);
1537 } else {
1538 setvbuf(stream, NULL, _IOLBF, 0);
1539 return stream;
1543 static GObject *
1544 obj_sans_suffix(char *suffix, const char *name, gpointer *builder)
1546 int str_l;
1547 char str[BUFLEN + 1] = {'\0'};
1549 str_l = suffix - name;
1550 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
1551 return gtk_builder_get_object(GTK_BUILDER(builder), str);
1554 static void
1555 connect_widget_signals(gpointer *obj, gpointer *builder)
1557 const char *name = NULL;
1558 char *suffix = NULL;
1559 GObject *obj2;
1560 GType type = G_TYPE_INVALID;
1562 type = G_TYPE_FROM_INSTANCE(obj);
1563 if (GTK_IS_BUILDABLE(obj))
1564 name = widget_name(obj);
1565 if (type == GTK_TYPE_TREE_VIEW_COLUMN)
1566 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1567 if (type == GTK_TYPE_BUTTON) {
1568 /* button associated with a GtkTextView */
1569 if ((suffix = strstr(name, "_send_text")) != NULL &&
1570 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name, builder)))
1571 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
1572 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
1573 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
1574 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name, builder)))
1575 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
1576 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
1577 /* button associated with (and part of) a GtkDialog */
1578 else if ((suffix = strstr(name, "_cancel")) != NULL &&
1579 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1580 if (eql(widget_name(obj2), MAIN_WIN))
1581 g_signal_connect(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
1582 else
1583 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
1584 else if ((suffix = strstr(name, "_ok")) != NULL &&
1585 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder))) {
1586 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
1587 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
1588 else /* generic button */
1589 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1590 if (eql(widget_name(obj2), MAIN_WIN))
1591 g_signal_connect(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
1592 else
1593 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
1594 } else if ((suffix = strstr(name, "_apply")) != NULL &&
1595 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1596 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
1597 else /* generic button */
1598 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1600 else if (GTK_IS_MENU_ITEM(obj))
1601 if ((suffix = strstr(name, "_invoke")) != NULL &&
1602 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1603 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
1604 else
1605 g_signal_connect(obj, "activate", G_CALLBACK(cb), "active");
1606 else if (GTK_IS_WINDOW(obj))
1607 if (eql(name, MAIN_WIN))
1608 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
1609 else
1610 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1611 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1612 g_signal_connect(obj, "file-set", G_CALLBACK(cb), "file");
1613 else if (type == GTK_TYPE_COLOR_BUTTON)
1614 g_signal_connect(obj, "color-set", G_CALLBACK(cb), "color");
1615 else if (type == GTK_TYPE_FONT_BUTTON)
1616 g_signal_connect(obj, "font-set", G_CALLBACK(cb), "font");
1617 else if (type == GTK_TYPE_SWITCH)
1618 g_signal_connect(obj, "notify::active", G_CALLBACK(cb), NULL);
1619 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
1620 g_signal_connect(obj, "toggled", G_CALLBACK(cb), NULL);
1621 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
1622 g_signal_connect(obj, "changed", G_CALLBACK(cb), "text");
1623 else if (type == GTK_TYPE_SCALE)
1624 g_signal_connect(obj, "value-changed", G_CALLBACK(cb), "value");
1625 else if (type == GTK_TYPE_CALENDAR) {
1626 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb), "doubleclicked");
1627 g_signal_connect(obj, "day-selected", G_CALLBACK(cb), "clicked");
1628 } else if (type == GTK_TYPE_TREE_SELECTION)
1629 g_signal_connect(obj, "changed", G_CALLBACK(cb), "clicked");
1630 else if (type == GTK_TYPE_DRAWING_AREA)
1631 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
1634 static void
1635 add_widget_style_provider(gpointer *obj, void *data)
1637 const char *name = NULL;
1638 struct style_provider *sp = NULL, *last_sp = NULL;
1639 GtkStyleContext *context;
1641 (void)data;
1642 if (!GTK_IS_WIDGET(obj))
1643 return;
1644 if ((sp = malloc(sizeof(struct style_provider))) == NULL)
1645 OOM_ABORT;
1646 name = widget_name(obj);
1647 sp->name = name;
1648 sp->provider = gtk_css_provider_new();
1649 if ((sp->style_decl = malloc(sizeof('\0'))) == NULL)
1650 OOM_ABORT;
1651 *(sp->style_decl) = '\0';
1652 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
1653 gtk_style_context_add_provider(context,
1654 GTK_STYLE_PROVIDER(sp->provider),
1655 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1656 gtk_css_provider_load_from_data(sp->provider, sp->style_decl, -1, NULL);
1657 if (widget_style_providers == NULL) {
1658 widget_style_providers = sp;
1659 insque(widget_style_providers, NULL);
1660 } else {
1661 for (last_sp = widget_style_providers;
1662 last_sp->next != NULL;
1663 last_sp = last_sp->next);
1664 insque(sp, last_sp);
1668 static void
1669 prepare_widgets(GtkBuilder *builder)
1671 GSList *objects = NULL;
1673 objects = gtk_builder_get_objects(builder);
1674 g_slist_foreach(objects, (GFunc)connect_widget_signals, builder);
1675 g_slist_foreach(objects, (GFunc)add_widget_style_provider, NULL);
1676 g_slist_free(objects);
1680 main(int argc, char *argv[])
1682 char opt;
1683 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
1684 GtkBuilder *builder;
1685 pthread_t receiver;
1686 GError *error = NULL;
1687 GObject *main_window = NULL;
1689 in = NULL;
1690 out = NULL;
1691 while ((opt = getopt(argc, argv, "hi:o:u:GV")) != -1) {
1692 switch (opt) {
1693 case 'i': in_fifo = optarg; break;
1694 case 'o': out_fifo = optarg; break;
1695 case 'u': ui_file = optarg; break;
1696 case 'G': gtk_versions(); break;
1697 case 'V': version(); break;
1698 case '?':
1699 case 'h':
1700 default: usage(argv); break;
1703 if (argv[optind] != NULL) {
1704 fprintf(stderr, "illegal parameter '%s'\n", argv[optind]);
1705 usage(argv);
1707 if (ui_file == NULL)
1708 ui_file = "pipeglade.ui";
1709 gtk_init(0, NULL);
1710 builder = gtk_builder_new();
1711 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0) {
1712 fprintf(stderr, "%s\n", error->message);
1713 exit(EXIT_FAILURE);
1715 in = fifo(in_fifo, "r");
1716 out = fifo(out_fifo, "w");
1717 pthread_create(&receiver, NULL, digest_msg, (void*)builder);
1718 main_window = gtk_builder_get_object(builder, MAIN_WIN);
1719 if (!GTK_IS_WINDOW(main_window)) {
1720 fprintf(stderr, "no toplevel window named \'" MAIN_WIN "\'\n");
1721 exit(EXIT_FAILURE);
1723 prepare_widgets(builder);
1724 gtk_widget_show(GTK_WIDGET(main_window));
1725 gtk_main();
1726 if (in != stdin) {
1727 fclose(in);
1728 unlink(in_fifo);
1730 if (out != stdout) {
1731 fclose(out);
1732 unlink(out_fifo);
1734 pthread_cancel(receiver);
1735 pthread_join(receiver, NULL);
1736 exit(EXIT_SUCCESS);