Avoid spinning on command input
[pipeglade.git] / pipeglade.c
blob7bccc12646f77f74ebaf6fb93bd56dd0b57eded6
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 <gtk/gtkunixprint.h>
28 #include <gtk/gtkx.h>
29 #include <inttypes.h>
30 #include <locale.h>
31 #include <math.h>
32 #include <pthread.h>
33 #include <search.h>
34 #include <stdio.h>
35 #include <stdarg.h>
36 #include <stdbool.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/select.h>
40 #include <sys/stat.h>
41 #include <time.h>
42 #include <unistd.h>
44 #define VERSION "4.1.0"
45 #define BUFLEN 256
46 #define WHITESPACE " \t\n"
47 #define MAIN_WIN "main"
48 #define USAGE \
49 "usage: pipeglade " \
50 "[-h] " \
51 "[-e xid] " \
52 "[-i in-fifo] " \
53 "[-o out-fifo] " \
54 "[-u glade-file.ui] " \
55 "[-G] " \
56 "[-V]\n"
58 #define OOM_ABORT \
59 { \
60 fprintf(stderr, \
61 "out of memory: %s (%s:%d)\n", \
62 __func__, __FILE__, __LINE__); \
63 abort(); \
66 static FILE *in;
67 static FILE *out;
68 struct ui_data {
69 GtkBuilder *builder;
70 size_t msg_size;
71 char *msg;
72 bool msg_digested;
76 * Print a formatted message to stream s and give up with status
78 static void
79 bye(int status, FILE *s, const char *fmt, ...)
81 va_list ap0, ap;
83 va_start(ap0, fmt);
84 va_copy(ap, ap0);
85 vfprintf(s, fmt, ap);
86 va_end(ap);
87 va_end(ap0);
88 exit(status);
91 static bool
92 eql(const char *s1, const char *s2)
94 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
97 static const char *
98 widget_name(void *obj)
100 return gtk_buildable_get_name(GTK_BUILDABLE(obj));
104 * Send GUI feedback to global stream "out". The message format is
105 * "<origin>:<tag> <data ...>". The variadic arguments are
106 * strings; last argument must be NULL.
108 static void
109 send_msg(GtkBuildable *obj, const char *tag, ...)
111 va_list ap;
112 char *data;
113 const char *w_name = widget_name(obj);
114 fd_set wfds;
115 int ofd = fileno(out);
116 struct timeval timeout = {1, 0};
118 FD_ZERO(&wfds);
119 FD_SET(ofd, &wfds);
120 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
121 va_start(ap, tag);
122 fprintf(out, "%s:%s ", w_name, tag);
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 } else
138 fprintf(stderr,
139 "send error; discarding feedback message %s:%s\n",
140 w_name, tag);
144 * Callback that sends user's selection from a file dialog
146 static void
147 cb_send_file_chooser_dialog_selection(gpointer user_data)
149 send_msg(user_data, "file",
150 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(user_data)), NULL);
151 send_msg(user_data, "folder",
152 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(user_data)), NULL);
156 * Callback that sends in a message the content of the text buffer
157 * passed in user_data
159 static void
160 cb_send_text(GtkBuildable *obj, gpointer user_data)
162 GtkTextIter a, b;
164 gtk_text_buffer_get_bounds(user_data, &a, &b);
165 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
169 * Callback that sends in a message the highlighted text from the text
170 * buffer which was passed in user_data
172 static void
173 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
175 GtkTextIter a, b;
177 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
178 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, FALSE), NULL);
182 * send_tree_row_msg serves as an argument for
183 * gtk_tree_selection_selected_foreach()
185 static void
186 send_tree_row_msg(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
188 char *path_s = gtk_tree_path_to_string(path);
189 int col;
191 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++) {
192 GValue value = G_VALUE_INIT;
193 GType col_type;
194 char str[BUFLEN];
196 gtk_tree_model_get_value(model, iter, col, &value);
197 col_type = gtk_tree_model_get_column_type(model, col);
198 switch (col_type) {
199 case G_TYPE_INT:
200 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
201 send_msg(obj, "gint", path_s, str, NULL);
202 break;
203 case G_TYPE_LONG:
204 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
205 send_msg(obj, "glong", path_s, str, NULL);
206 break;
207 case G_TYPE_INT64:
208 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
209 send_msg(obj, "gint64", path_s, str, NULL);
210 break;
211 case G_TYPE_UINT:
212 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
213 send_msg(obj, "guint", path_s, str, NULL);
214 break;
215 case G_TYPE_ULONG:
216 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
217 send_msg(obj, "gulong", path_s, str, NULL);
218 break;
219 case G_TYPE_UINT64:
220 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
221 send_msg(obj, "guint64", path_s, str, NULL);
222 break;
223 case G_TYPE_BOOLEAN:
224 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
225 send_msg(obj, "gboolean", path_s, str, NULL);
226 break;
227 case G_TYPE_FLOAT:
228 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
229 send_msg(obj, "gfloat", path_s, str, NULL);
230 break;
231 case G_TYPE_DOUBLE:
232 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
233 send_msg(obj, "gdouble", path_s, str, NULL);
234 break;
235 case G_TYPE_STRING:
236 snprintf(str, BUFLEN, " %d ", col);
237 send_msg(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
238 break;
239 default:
240 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
241 break;
243 g_value_unset(&value);
245 g_free(path_s);
249 * Callback that sends message(s) whose nature depends on the
250 * arguments passed. A call to this function will also be initiated
251 * by the user command ...:force.
253 static void
254 cb(GtkBuildable *obj, gpointer user_data)
256 char str[BUFLEN];
257 GdkRGBA color;
258 GtkTreeView *tree_view;
259 unsigned int year = 0, month = 0, day = 0;
261 if (GTK_IS_ENTRY(obj))
262 send_msg(obj, user_data, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
263 else if (GTK_IS_MENU_ITEM(obj))
264 send_msg(obj, user_data, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
265 else if (GTK_IS_RANGE(obj)) {
266 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
267 send_msg(obj, user_data, str, NULL);
268 } else if (GTK_IS_SWITCH(obj))
269 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
270 else if (GTK_IS_TOGGLE_BUTTON(obj))
271 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
272 else if (GTK_IS_COLOR_BUTTON(obj)) {
273 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
274 send_msg(obj, user_data, gdk_rgba_to_string(&color), NULL);
275 } else if (GTK_IS_FONT_BUTTON(obj))
276 send_msg(obj, user_data, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
277 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
278 send_msg(obj, user_data, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
279 else if (GTK_IS_BUTTON(obj) || GTK_IS_TREE_VIEW_COLUMN(obj) || GTK_IS_SOCKET(obj))
280 send_msg(obj, user_data, NULL);
281 else if (GTK_IS_CALENDAR(obj)) {
282 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
283 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
284 send_msg(obj, user_data, str, NULL);
285 } else if (GTK_IS_TREE_SELECTION(obj)) {
286 tree_view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
287 send_msg(GTK_BUILDABLE(tree_view), user_data, NULL);
288 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
289 (GtkTreeSelectionForeachFunc)send_tree_row_msg,
290 GTK_BUILDABLE(tree_view));
291 } else
292 fprintf(stderr, "ignoring callback from %s\n", widget_name(obj));
296 * Callback like cb(), but returning true.
298 static bool
299 cb_true(GtkBuildable *obj, gpointer user_data)
301 cb(obj, user_data);
302 return true;
306 * Store a line from stream s into buf, which should have been malloc'd
307 * to bufsize. Enlarge buf and bufsize if necessary.
309 static size_t
310 read_buf(FILE *s, char **buf, size_t *bufsize)
312 size_t i = 0;
313 int c;
314 fd_set rfds;
315 int ifd = fileno(in);
316 bool esc = false;
318 FD_ZERO(&rfds);
319 FD_SET(ifd, &rfds);
320 for (;;) {
321 select(ifd + 1, &rfds, NULL, NULL, NULL);
322 c = getc(s);
323 if (c == '\n')
324 break;
325 if (i >= *bufsize - 1)
326 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
327 OOM_ABORT;
328 if (esc) {
329 esc = false;
330 switch (c) {
331 case 'n': (*buf)[i++] = '\n'; break;
332 case 'r': (*buf)[i++] = '\r'; break;
333 default: (*buf)[i++] = c; break;
335 } else if (c == '\\')
336 esc = true;
337 else
338 (*buf)[i++] = c;
340 (*buf)[i] = '\0';
341 return i;
345 * Warning message
347 static void
348 ign_cmd(GType type, const char *msg)
350 const char *name, *pad = " ";
352 if (type == G_TYPE_INVALID) {
353 name = "";
354 pad = "";
356 else
357 name = g_type_name(type);
358 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
362 * Drawing on a GtkDrawingArea
364 enum cairo_fn {
365 RECTANGLE,
366 ARC,
367 ARC_NEGATIVE,
368 CURVE_TO,
369 REL_CURVE_TO,
370 LINE_TO,
371 REL_LINE_TO,
372 MOVE_TO,
373 REL_MOVE_TO,
374 CLOSE_PATH,
375 SET_SOURCE_RGBA,
376 SET_DASH,
377 SET_LINE_CAP,
378 SET_LINE_JOIN,
379 SET_LINE_WIDTH,
380 FILL,
381 FILL_PRESERVE,
382 STROKE,
383 STROKE_PRESERVE,
384 SHOW_TEXT,
385 SET_FONT_SIZE,
389 * One single element of a drawing
391 struct draw_op {
392 struct draw_op *next;
393 struct draw_op *prev;
394 unsigned int id;
395 enum cairo_fn op;
396 void *op_args;
400 * The content of all GtkDrawingAreas
402 static struct drawing {
403 struct drawing *next;
404 struct drawing *prev;
405 GtkWidget *widget;
406 struct draw_op *draw_ops;
407 } *drawings = NULL;
410 * Sets of arguments for various drawing functions
412 struct rectangle_args {
413 double x;
414 double y;
415 double width;
416 double height;
419 struct arc_args {
420 double x;
421 double y;
422 double radius;
423 double angle1;
424 double angle2;
427 struct curve_to_args {
428 double x1;
429 double y1;
430 double x2;
431 double y2;
432 double x3;
433 double y3;
436 struct move_to_args {
437 double x;
438 double y;
441 struct set_source_rgba_args {
442 GdkRGBA color;
445 struct set_dash_args {
446 int num_dashes;
447 double dashes[];
450 struct set_line_cap_args {
451 cairo_line_cap_t line_cap;
454 struct set_line_join_args {
455 cairo_line_join_t line_join;
458 struct set_line_width_args {
459 double width;
462 struct show_text_args {
463 int len;
464 char text[];
467 struct set_font_size_args {
468 double size;
471 static void
472 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
474 switch (op) {
475 case RECTANGLE: {
476 struct rectangle_args *args = op_args;
478 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
479 break;
481 case ARC: {
482 struct arc_args *args = op_args;
484 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
485 break;
487 case ARC_NEGATIVE: {
488 struct arc_args *args = op_args;
490 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
491 break;
493 case CURVE_TO: {
494 struct curve_to_args *args = op_args;
496 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
497 break;
499 case REL_CURVE_TO: {
500 struct curve_to_args *args = op_args;
502 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
503 break;
505 case LINE_TO: {
506 struct move_to_args *args = op_args;
508 cairo_line_to(cr, args->x, args->y);
509 break;
511 case REL_LINE_TO: {
512 struct move_to_args *args = op_args;
514 cairo_rel_line_to(cr, args->x, args->y);
515 break;
517 case MOVE_TO: {
518 struct move_to_args *args = op_args;
520 cairo_move_to(cr, args->x, args->y);
521 break;
523 case REL_MOVE_TO: {
524 struct move_to_args *args = op_args;
526 cairo_rel_move_to(cr, args->x, args->y);
527 break;
529 case CLOSE_PATH:
530 cairo_close_path(cr);
531 break;
532 case SET_SOURCE_RGBA: {
533 struct set_source_rgba_args *args = op_args;
535 gdk_cairo_set_source_rgba(cr, &args->color);
536 break;
538 case SET_DASH: {
539 struct set_dash_args *args = op_args;
541 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
542 break;
544 case SET_LINE_CAP: {
545 struct set_line_cap_args *args = op_args;
547 cairo_set_line_cap(cr, args->line_cap);
548 break;
550 case SET_LINE_JOIN: {
551 struct set_line_join_args *args = op_args;
553 cairo_set_line_join(cr, args->line_join);
554 break;
556 case SET_LINE_WIDTH: {
557 struct set_line_width_args *args = op_args;
559 cairo_set_line_width(cr, args->width);
560 break;
562 case FILL:
563 cairo_fill(cr);
564 break;
565 case FILL_PRESERVE:
566 cairo_fill_preserve(cr);
567 break;
568 case STROKE:
569 cairo_stroke(cr);
570 break;
571 case STROKE_PRESERVE:
572 cairo_stroke_preserve(cr);
573 break;
574 case SHOW_TEXT: {
575 struct show_text_args *args = op_args;
577 cairo_show_text(cr, args->text);
578 break;
580 case SET_FONT_SIZE: {
581 struct set_font_size_args *args = op_args;
583 cairo_set_font_size(cr, args->size);
584 break;
586 default:
587 abort();
588 break;
592 static bool
593 set_draw_op(struct draw_op *op, char* action, char *data)
595 if (eql(action, "rectangle")) {
596 struct rectangle_args *args;
598 if ((args = malloc(sizeof(*args))) == NULL)
599 OOM_ABORT;
600 op->op = RECTANGLE;
601 op->op_args = args;
602 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->width, &args->height) != 5)
603 return false;
604 } else if (eql(action, "arc")) {
605 struct arc_args *args;
606 double deg1, deg2;
608 if ((args = malloc(sizeof(*args))) == NULL)
609 OOM_ABORT;
610 op->op = ARC;
611 op->op_args = args;
612 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
613 return false;
614 args->angle1 = deg1 * (M_PI / 180.);
615 args->angle2 = deg2 * (M_PI / 180.);
616 } else if (eql(action, "arc_negative")) {
617 struct arc_args *args;
618 double deg1, deg2;
620 if ((args = malloc(sizeof(*args))) == NULL)
621 OOM_ABORT;
622 op->op = ARC_NEGATIVE;
623 op->op_args = args;
624 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
625 return false;
626 args->angle1 = deg1 * (M_PI / 180.);
627 args->angle2 = deg2 * (M_PI / 180.);
628 } else if (eql(action, "curve_to")) {
629 struct curve_to_args *args;
631 if ((args = malloc(sizeof(*args))) == NULL)
632 OOM_ABORT;
633 op->op = CURVE_TO;
634 op->op_args = args;
635 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)
636 return false;
637 } else if (eql(action, "rel_curve_to")) {
638 struct curve_to_args *args;
640 if ((args = malloc(sizeof(*args))) == NULL)
641 OOM_ABORT;
642 op->op = REL_CURVE_TO;
643 op->op_args = args;
644 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)
645 return false;
646 } else if (eql(action, "line_to")) {
647 struct move_to_args *args;
649 if ((args = malloc(sizeof(*args))) == NULL)
650 OOM_ABORT;
651 op->op = LINE_TO;
652 op->op_args = args;
653 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
654 return false;
655 } else if (eql(action, "rel_line_to")) {
656 struct move_to_args *args;
658 if ((args = malloc(sizeof(*args))) == NULL)
659 OOM_ABORT;
660 op->op = REL_LINE_TO;
661 op->op_args = args;
662 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
663 return false;
664 } else if (eql(action, "move_to")) {
665 struct move_to_args *args;
667 if ((args = malloc(sizeof(*args))) == NULL)
668 OOM_ABORT;
669 op->op = MOVE_TO;
670 op->op_args = args;
671 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
672 return false;
673 } else if (eql(action, "rel_move_to")) {
674 struct move_to_args *args;
676 if ((args = malloc(sizeof(*args))) == NULL)
677 OOM_ABORT;
678 op->op = REL_MOVE_TO;
679 op->op_args = args;
680 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
681 return false;
682 } else if (eql(action, "close_path")) {
683 op->op = CLOSE_PATH;
684 if (sscanf(data, "%u", &op->id) != 1)
685 return false;
686 op->op_args = NULL;
687 } else if (eql(action, "set_source_rgba")) {
688 struct set_source_rgba_args *args;
689 int c_start;
691 if ((args = malloc(sizeof(*args))) == NULL)
692 OOM_ABORT;
693 op->op = SET_SOURCE_RGBA;
694 op->op_args = args;
695 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
696 return false;;
697 gdk_rgba_parse(&args->color, data + c_start);
698 } else if (eql(action, "set_dash")) {
699 struct set_dash_args *args;
700 int d_start, n, i;
701 char *next, *end;
703 if (sscanf(data, "%u %n", &op->id, &d_start) < 1)
704 return false;
705 next = end = data + d_start;
706 n = -1;
707 do {
708 n++;
709 next = end;
710 strtod(next, &end);
711 } while (next != end);
712 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
713 OOM_ABORT;
714 op->op = SET_DASH;
715 op->op_args = args;
716 args->num_dashes = n;
717 for (i = 0, next = data + d_start; i < n; i++, next = end) {
718 args->dashes[i] = strtod(next, &end);
720 } else if (eql(action, "set_line_cap")) {
721 struct set_line_cap_args *args;
722 char str[6 + 1];
724 if ((args = malloc(sizeof(*args))) == NULL)
725 OOM_ABORT;
726 op->op = SET_LINE_CAP;
727 op->op_args = args;
728 if (sscanf(data, "%u %6s", &op->id, str) != 2)
729 return false;
730 if (eql(str, "butt"))
731 args->line_cap = CAIRO_LINE_CAP_BUTT;
732 else if (eql(str, "round"))
733 args->line_cap = CAIRO_LINE_CAP_ROUND;
734 else if (eql(str, "square"))
735 args->line_cap = CAIRO_LINE_CAP_SQUARE;
736 else
737 return false;
738 } else if (eql(action, "set_line_join")) {
739 struct set_line_join_args *args;
740 char str[5 + 1];
742 if ((args = malloc(sizeof(*args))) == NULL)
743 OOM_ABORT;
744 op->op = SET_LINE_JOIN;
745 op->op_args = args;
746 if (sscanf(data, "%u %5s", &op->id, str) != 2)
747 return false;
748 if (eql(str, "miter"))
749 args->line_join = CAIRO_LINE_JOIN_MITER;
750 else if (eql(str, "round"))
751 args->line_join = CAIRO_LINE_JOIN_ROUND;
752 else if (eql(str, "bevel"))
753 args->line_join = CAIRO_LINE_JOIN_BEVEL;
754 else
755 return false;
756 } else if (eql(action, "set_line_width")) {
757 struct set_line_width_args *args;
759 if ((args = malloc(sizeof(*args))) == NULL)
760 OOM_ABORT;
761 op->op = SET_LINE_WIDTH;
762 op->op_args = args;
763 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
764 return false;
765 } else if (eql(action, "fill")) {
766 op->op = FILL;
767 if (sscanf(data, "%u", &op->id) != 1)
768 return false;
769 op->op_args = NULL;
770 } else if (eql(action, "fill_preserve")) {
771 op->op = FILL_PRESERVE;
772 if (sscanf(data, "%u", &op->id) != 1)
773 return false;
774 op->op_args = NULL;
775 } else if (eql(action, "stroke")) {
776 op->op = STROKE;
777 if (sscanf(data, "%u", &op->id) != 1)
778 return false;
779 op->op_args = NULL;
780 } else if (eql(action, "stroke_preserve")) {
781 op->op = STROKE_PRESERVE;
782 if (sscanf(data, "%u", &op->id) != 1)
783 return false;
784 op->op_args = NULL;
785 } else if (eql(action, "show_text")) {
786 struct show_text_args *args;
787 int start, len;
789 if (sscanf(data, "%u %n", &op->id, &start) < 1)
790 return false;
791 len = strlen(data + start) + 1;
792 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
793 OOM_ABORT;
794 op->op = SHOW_TEXT;
795 op->op_args = args;
796 args->len = len; /* not used */
797 strncpy(args->text, (data + start), len);
798 } else if (eql(action, "set_font_size")) {
799 struct set_font_size_args *args;
801 if ((args = malloc(sizeof(*args))) == NULL)
802 OOM_ABORT;
803 op->op = SET_FONT_SIZE;
804 op->op_args = args;
805 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
806 return false;
807 } else
808 return false;
809 return true;
813 * Add another element to widget's list of drawing operations
815 static bool
816 ins_draw_op(GtkWidget *widget, char *action, char *data)
818 struct draw_op *op, *last_op;
819 struct drawing *d;
821 if ((op = malloc(sizeof(*op))) == NULL)
822 OOM_ABORT;
823 op->op_args = NULL;
824 if (!set_draw_op(op, action, data)) {
825 free(op->op_args);
826 free(op);
827 return false;
829 for (d = drawings; d != NULL; d = d->next)
830 if (d->widget == widget)
831 break;
832 if (d == NULL) {
833 if ((d = malloc(sizeof(*d))) == NULL)
834 OOM_ABORT;
835 if (drawings == NULL) {
836 drawings = d;
837 insque(d, NULL);
838 } else
839 insque(d, drawings);
840 d->widget = widget;
841 d->draw_ops = op;
842 insque(op, NULL);
843 } else if (d->draw_ops == NULL) {
844 d->draw_ops = op;
845 insque(op, NULL);
846 } else {
847 for (last_op = d->draw_ops; last_op->next != NULL; last_op = last_op->next);
848 insque(op, last_op);
850 return true;
854 * Remove all elements with the given id from widget's list of drawing
855 * operations
857 static bool
858 rem_draw_op(GtkWidget *widget, char *data)
860 struct draw_op *op, *next_op;
861 struct drawing *d;
862 unsigned int id;
864 if (sscanf(data, "%u", &id) != 1)
865 return false;
866 for (d = drawings; d != NULL; d = d->next)
867 if (d->widget == widget)
868 break;
869 if (d != NULL) {
870 op = d->draw_ops;
871 while (op != NULL) {
872 next_op = op->next;
873 if (op->id == id) {
874 if (op->prev == NULL)
875 d->draw_ops = next_op;
876 remque(op);
877 free(op->op_args);
878 free(op);
880 op = next_op;
883 return true;
887 * Callback that draws on a GtkDrawingArea
889 static gboolean
890 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
892 struct draw_op *op;
893 struct drawing *p;
895 (void)data;
896 for (p = drawings; p != NULL; p = p->next)
897 if (p->widget == widget)
898 for (op = p->draw_ops; op != NULL; op = op->next)
899 draw(cr, op->op, op->op_args);
900 return FALSE;
904 * One style provider for each widget
906 struct style_provider {
907 struct style_provider *next;
908 struct style_provider *prev;
909 const char *name;
910 GtkCssProvider *provider;
911 char *style_decl;
912 } *widget_style_providers = NULL;
915 * Change the style of the widget passed
917 static void
918 update_widget_style(GtkWidget *widget, const char *name , const char *data)
920 GtkStyleContext *context;
921 struct style_provider *sp;
922 const char *prefix = "* {", *suffix = "}";
923 size_t sz;
925 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
926 context = gtk_widget_get_style_context(widget);
927 for (sp = widget_style_providers; !eql(name, sp->name); sp = sp->next);
928 gtk_style_context_remove_provider(context, GTK_STYLE_PROVIDER(sp->provider));
929 free(sp->style_decl);
930 if ((sp->style_decl = malloc(sz)) == NULL)
931 OOM_ABORT;
932 strcpy(sp->style_decl, prefix);
933 strcat(sp->style_decl, data);
934 strcat(sp->style_decl, suffix);
935 gtk_style_context_add_provider(context,
936 GTK_STYLE_PROVIDER(sp->provider),
937 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
938 gtk_css_provider_load_from_data(sp->provider, sp->style_decl, -1, NULL);
942 * Update various kinds of widgets according to the respective action
943 * parameter
945 static void
946 update_button(GtkButton *button, const char *action,
947 const char *data, const char *whole_msg)
949 if (eql(action, "set_label"))
950 gtk_button_set_label(button, data);
951 else
952 ign_cmd(GTK_TYPE_BUTTON, whole_msg);
955 static void
956 update_calendar(GtkCalendar *calendar, const char *action,
957 const char *data, const char *whole_msg)
959 int year = 0, month = 0, day = 0;
961 if (eql(action, "select_date")) {
962 sscanf(data, "%d-%d-%d", &year, &month, &day);
963 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
964 gtk_calendar_select_month(calendar, --month, year);
965 gtk_calendar_select_day(calendar, day);
966 } else
967 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
968 } else if (eql(action, "mark_day")) {
969 day = strtol(data, NULL, 10);
970 if (day > 0 && day <= 31)
971 gtk_calendar_mark_day(calendar, day);
972 else
973 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
974 } else if (eql(action, "clear_marks"))
975 gtk_calendar_clear_marks(calendar);
976 else
977 ign_cmd(GTK_TYPE_CALENDAR, whole_msg);
980 static void
981 update_color_button(GtkColorChooser *chooser, const char *action,
982 const char *data, const char *whole_msg)
984 GdkRGBA color;
986 if (eql(action, "set_color")) {
987 gdk_rgba_parse(&color, data);
988 gtk_color_chooser_set_rgba(chooser, &color);
989 } else
990 ign_cmd(GTK_TYPE_COLOR_BUTTON, whole_msg);
993 static void
994 update_combo_box_text(GtkComboBoxText *combobox, const char *action,
995 char *data, const char *whole_msg)
997 if (eql(action, "prepend_text"))
998 gtk_combo_box_text_prepend_text(combobox, data);
999 else if (eql(action, "append_text"))
1000 gtk_combo_box_text_append_text(combobox, data);
1001 else if (eql(action, "remove"))
1002 gtk_combo_box_text_remove(combobox, strtol(data, NULL, 10));
1003 else if (eql(action, "insert_text")) {
1004 char *position = strtok(data, WHITESPACE);
1005 char *text = strtok(NULL, WHITESPACE);
1007 gtk_combo_box_text_insert_text(combobox, strtol(position, NULL, 10), text);
1008 } else
1009 ign_cmd(GTK_TYPE_COMBO_BOX_TEXT, whole_msg);
1012 static void
1013 update_frame(GtkFrame *frame, const char *action,
1014 const char *data, const char *whole_msg)
1016 if (eql(action, "set_label"))
1017 gtk_frame_set_label(frame, data);
1018 else
1019 ign_cmd(GTK_TYPE_FRAME, whole_msg);
1022 static void
1023 update_drawing_area(GtkWidget *widget, char *action,
1024 char *data, const char *whole_msg)
1026 if (eql(action, "remove")) {
1027 if (!rem_draw_op(widget, data))
1028 ign_cmd(GTK_TYPE_DRAWING_AREA, whole_msg);
1029 } else if (eql(action, "refresh")) {
1030 gint width = gtk_widget_get_allocated_width (widget);
1031 gint height = gtk_widget_get_allocated_height (widget);
1033 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1034 } else if (ins_draw_op(widget, action, data));
1035 else
1036 ign_cmd(GTK_TYPE_DRAWING_AREA, whole_msg);
1039 static void
1040 update_entry(GtkEntry *entry, const char *action,
1041 const char *data, const char *whole_msg, GType type)
1043 if (eql(action, "set_text"))
1044 gtk_entry_set_text(entry, data);
1045 else if (eql(action, "set_placeholder_text"))
1046 gtk_entry_set_placeholder_text(entry, data);
1047 else
1048 ign_cmd(type, whole_msg);
1051 static void
1052 update_label(GtkLabel *label, const char *action,
1053 const char *data, const char *whole_msg)
1055 if (eql(action, "set_text"))
1056 gtk_label_set_text(label, data);
1057 else
1058 ign_cmd(GTK_TYPE_LABEL, whole_msg);
1061 static void
1062 update_expander(GtkExpander *expander, const char *action,
1063 const char *data, const char *whole_msg)
1065 if (eql(action, "set_expanded"))
1066 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1067 else if (eql(action, "set_label"))
1068 gtk_expander_set_label(expander, data);
1069 else
1070 ign_cmd(GTK_TYPE_EXPANDER, whole_msg);
1073 static void
1074 update_file_chooser_button(GtkFileChooser *chooser, const char *action,
1075 const char *data, const char *whole_msg)
1077 if (eql(action, "set_filename"))
1078 gtk_file_chooser_set_filename(chooser, data);
1079 else
1080 ign_cmd(GTK_TYPE_FILE_CHOOSER_BUTTON, whole_msg);
1083 static void
1084 update_file_chooser_dialog(GtkFileChooser *chooser, const char *action,
1085 const char *data, const char *whole_msg)
1087 if (eql(action, "set_filename"))
1088 gtk_file_chooser_set_filename(chooser, data);
1089 else if (eql(action, "set_current_name"))
1090 gtk_file_chooser_set_current_name(chooser, data);
1091 else
1092 ign_cmd(GTK_TYPE_FILE_CHOOSER_DIALOG, whole_msg);
1095 static void
1096 update_font_button(GtkFontButton *font_button, const char *action,
1097 const char *data, const char *whole_msg)
1099 if (eql(action, "set_font_name"))
1100 gtk_font_button_set_font_name(font_button, data);
1101 else
1102 ign_cmd(GTK_TYPE_FONT_BUTTON, whole_msg);
1105 static void
1106 update_print_dialog(GtkPrintUnixDialog *dialog, const char *action,
1107 const char *data, const char *whole_msg)
1109 gint response_id;
1110 GtkPrinter *printer;
1111 GtkPrintSettings *settings;
1112 GtkPageSetup *page_setup;
1113 GtkPrintJob *job;
1115 if (eql(action, "print")) {
1116 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1117 switch (response_id) {
1118 case GTK_RESPONSE_OK:
1119 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1120 settings = gtk_print_unix_dialog_get_settings(dialog);
1121 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1122 job = gtk_print_job_new(data, printer, settings, page_setup);
1123 if (gtk_print_job_set_source_file(job, data, NULL))
1124 gtk_print_job_send(job, NULL, NULL, NULL);
1125 else
1126 ign_cmd(GTK_TYPE_PRINT_UNIX_DIALOG, whole_msg);
1127 g_clear_object(&settings);
1128 g_clear_object(&job);
1129 break;
1130 case GTK_RESPONSE_CANCEL:
1131 case GTK_RESPONSE_DELETE_EVENT:
1132 break;
1133 default:
1134 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1135 widget_name(GTK_WIDGET(dialog)), response_id);
1136 break;
1138 gtk_widget_hide(GTK_WIDGET(dialog));
1139 } else
1140 ign_cmd(GTK_TYPE_PRINT_UNIX_DIALOG, whole_msg);
1143 static void
1144 update_image(GtkImage *image, const char *action,
1145 const char *data, const char *whole_msg)
1147 GtkIconSize size;
1149 gtk_image_get_icon_name(image, NULL, &size);
1150 if (eql(action, "set_from_file"))
1151 gtk_image_set_from_file(image, data);
1152 else if (eql(action, "set_from_icon_name"))
1153 gtk_image_set_from_icon_name(image, data, size);
1154 else
1155 ign_cmd(GTK_TYPE_IMAGE, whole_msg);
1158 static void
1159 update_notebook(GtkNotebook *notebook, const char *action,
1160 const char *data, const char *whole_msg)
1162 if (eql(action, "set_current_page"))
1163 gtk_notebook_set_current_page(notebook, strtol(data, NULL, 10));
1164 else
1165 ign_cmd(GTK_TYPE_NOTEBOOK, whole_msg);
1168 static void
1169 update_progress_bar(GtkProgressBar *progressbar, const char *action,
1170 const char *data, const char *whole_msg)
1172 if (eql(action, "set_text"))
1173 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1174 else if (eql(action, "set_fraction"))
1175 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1176 else
1177 ign_cmd(GTK_TYPE_PROGRESS_BAR, whole_msg);
1180 static void
1181 update_scale(GtkRange *range, const char *action,
1182 const char *data, const char *whole_msg)
1184 if (eql(action, "set_value"))
1185 gtk_range_set_value(range, strtod(data, NULL));
1186 else
1187 ign_cmd(GTK_TYPE_SCALE, whole_msg);
1190 static void
1191 update_spinner(GtkSpinner *spinner, const char *action, const char *whole_msg)
1193 if (eql(action, "start"))
1194 gtk_spinner_start(spinner);
1195 else if (eql(action, "stop"))
1196 gtk_spinner_stop(spinner);
1197 else
1198 ign_cmd(GTK_TYPE_SPINNER, whole_msg);
1201 static void
1202 update_statusbar(GtkStatusbar *statusbar, const char *action,
1203 const char *data, const char *whole_msg)
1205 if (eql(action, "push"))
1206 gtk_statusbar_push(statusbar, 0, data);
1207 else if (eql(action, "pop"))
1208 gtk_statusbar_pop(statusbar, 0);
1209 else if (eql(action, "remove_all"))
1210 gtk_statusbar_remove_all(statusbar, 0);
1211 else
1212 ign_cmd(GTK_TYPE_STATUSBAR, whole_msg);
1215 static void
1216 update_switch(GtkSwitch *switcher, const char *action,
1217 const char *data, const char *whole_msg)
1219 if (eql(action, "set_active"))
1220 gtk_switch_set_active(switcher, strtol(data, NULL, 10));
1221 else
1222 ign_cmd(GTK_TYPE_SWITCH, whole_msg);
1225 static void
1226 update_text_view(GtkTextView *view, const char *action,
1227 const char *data, const char *whole_msg)
1229 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1230 GtkTextIter a, b;
1232 if (eql(action, "set_text"))
1233 gtk_text_buffer_set_text(textbuf, data, -1);
1234 else if (eql(action, "delete")) {
1235 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1236 gtk_text_buffer_delete(textbuf, &a, &b);
1237 } else if (eql(action, "insert_at_cursor"))
1238 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1239 else if (eql(action, "place_cursor")) {
1240 if (eql(data, "end"))
1241 gtk_text_buffer_get_end_iter(textbuf, &a);
1242 else /* numeric offset */
1243 gtk_text_buffer_get_iter_at_offset(textbuf, &a, strtol(data, NULL, 10));
1244 gtk_text_buffer_place_cursor(textbuf, &a);
1245 } else if (eql(action, "place_cursor_at_line")) {
1246 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1247 gtk_text_buffer_place_cursor(textbuf, &a);
1248 } else if (eql(action, "scroll_to_cursor"))
1249 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf), 0., 0, 0., 0.);
1250 else
1251 ign_cmd(GTK_TYPE_TEXT_VIEW, whole_msg);
1254 static void
1255 update_toggle_button(GtkToggleButton *toggle, const char *action,
1256 const char *data, const char *whole_msg, GType type)
1258 if (eql(action, "set_label"))
1259 gtk_button_set_label(GTK_BUTTON(toggle), data);
1260 else if (eql(action, "set_active"))
1261 gtk_toggle_button_set_active(toggle, strtol(data, NULL, 10));
1262 else
1263 ign_cmd(type, whole_msg);
1266 static void
1267 update_tree_view(GtkTreeView *view, const char *action,
1268 const char *data, const char *whole_msg)
1270 GtkTreeModel *model = gtk_tree_view_get_model(view);
1271 GtkListStore *store = GTK_LIST_STORE(model);
1272 GtkTreeIter iter0, iter1;
1273 char *tokens, *arg0_s, *arg1_s, *arg2_s, *endptr;
1274 int arg0_n = 0, arg1_n = 0;
1275 bool arg0_n_valid = false, arg1_n_valid = false;
1276 bool iter0_valid = false, iter1_valid = false;
1278 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1279 OOM_ABORT;
1280 strcpy(tokens, data);
1281 arg0_s = strtok(tokens, WHITESPACE);
1282 arg1_s = strtok(NULL, WHITESPACE);
1283 arg2_s = strtok(NULL, "\n");
1284 errno = 0;
1285 endptr = NULL;
1286 iter0_valid =
1287 arg0_s != NULL &&
1288 (arg0_n_valid = (arg0_n = strtol(arg0_s, &endptr, 10)) >= 0 &&
1289 errno == 0 && endptr != arg0_s) &&
1290 gtk_tree_model_get_iter_from_string(model, &iter0, arg0_s);
1291 errno = 0;
1292 endptr = NULL;
1293 iter1_valid =
1294 arg1_s != NULL &&
1295 (arg1_n_valid = (arg1_n = strtol(arg1_s, &endptr, 10)) >= 0 &&
1296 errno == 0 && endptr != arg1_s) &&
1297 gtk_tree_model_get_iter_from_string(model, &iter1, arg1_s);
1298 if (eql(action, "set") && iter0_valid && arg1_n_valid &&
1299 arg1_n < gtk_tree_model_get_n_columns(model)) {
1300 GType col_type = gtk_tree_model_get_column_type(model, arg1_n);
1301 long long int n;
1302 double d;
1304 switch (col_type) {
1305 case G_TYPE_BOOLEAN:
1306 case G_TYPE_INT:
1307 case G_TYPE_LONG:
1308 case G_TYPE_INT64:
1309 case G_TYPE_UINT:
1310 case G_TYPE_ULONG:
1311 case G_TYPE_UINT64:
1312 errno = 0;
1313 endptr = NULL;
1314 n = strtoll(arg2_s, &endptr, 10);
1315 if (!errno && endptr != arg2_s)
1316 gtk_list_store_set(store, &iter0, arg1_n, n, -1);
1317 else
1318 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1319 break;
1320 case G_TYPE_FLOAT:
1321 case G_TYPE_DOUBLE:
1322 errno = 0;
1323 endptr = NULL;
1324 d = strtod(arg2_s, &endptr);
1325 if (!errno && endptr != arg2_s)
1326 gtk_list_store_set(store, &iter0, arg1_n, d, -1);
1327 else
1328 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1329 gtk_list_store_set(store, &iter0, arg1_n, strtod(arg2_s, NULL), -1);
1330 break;
1331 case G_TYPE_STRING:
1332 gtk_list_store_set(store, &iter0, arg1_n, arg2_s, -1);
1333 break;
1334 default:
1335 fprintf(stderr, "column %d: %s not implemented\n", arg1_n, g_type_name(col_type));
1336 break;
1338 } else if (eql(action, "scroll") && arg0_n_valid && arg1_n_valid)
1339 gtk_tree_view_scroll_to_cell (view,
1340 gtk_tree_path_new_from_string(arg0_s),
1341 gtk_tree_view_get_column(view, arg1_n),
1342 0, 0., 0.);
1343 else if (eql(action, "insert_row"))
1344 if (eql(arg0_s, "end"))
1345 gtk_list_store_insert_before(store, &iter1, NULL);
1346 else if (iter0_valid)
1347 gtk_list_store_insert_before(store, &iter1, &iter0);
1348 else
1349 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1350 else if (eql(action, "move_row") && iter0_valid)
1351 if (eql(arg1_s, "end"))
1352 gtk_list_store_move_before(store, &iter0, NULL);
1353 else if (iter1_valid)
1354 gtk_list_store_move_before(store, &iter0, &iter1);
1355 else
1356 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1357 else if (eql(action, "remove_row") && iter0_valid)
1358 gtk_list_store_remove(store, &iter0);
1359 else
1360 ign_cmd(GTK_TYPE_TREE_VIEW, whole_msg);
1361 free(tokens);
1364 static void
1365 update_socket(GtkSocket *socket, const char *action,
1366 const char *data, const char *whole_msg)
1368 Window id;
1369 char str[BUFLEN];
1371 (void)data;
1372 if (eql(action, "id")) {
1373 id = gtk_socket_get_id(socket);
1374 snprintf(str, BUFLEN, "%lu", id);
1375 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1376 } else
1377 ign_cmd(GTK_TYPE_SOCKET, whole_msg);
1380 static void
1381 update_window(GtkWindow *window, const char *action,
1382 const char *data, const char *whole_msg)
1384 if (eql(action, "set_title"))
1385 gtk_window_set_title(window, data);
1386 else
1387 ign_cmd(GTK_TYPE_WINDOW, whole_msg);
1390 static void
1391 fake_ui_activity(GObject *obj, const char *whole_msg, GType type)
1393 if (!GTK_IS_WIDGET(obj))
1394 ign_cmd(type, whole_msg);
1395 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
1396 cb(GTK_BUILDABLE(obj), "text");
1397 else if (GTK_IS_SCALE(obj))
1398 cb(GTK_BUILDABLE(obj), "value");
1399 else if (GTK_IS_CALENDAR(obj))
1400 cb(GTK_BUILDABLE(obj), "clicked");
1401 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
1402 cb(GTK_BUILDABLE(obj), "file");
1403 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
1404 ign_cmd(type, whole_msg);
1408 * Parse command pointed to by ud, and act on ui accordingly. Set
1409 * ud->digested = true if done. Runs once per command inside
1410 * gtk_main_loop()
1412 static gboolean
1413 update_ui(struct ui_data *ud)
1415 char name[ud->msg_size], action[ud->msg_size];
1416 char *data;
1417 int data_start = strlen(ud->msg);
1418 GObject *obj = NULL;
1419 GType type = G_TYPE_INVALID;
1421 name[0] = action[0] = '\0';
1422 sscanf(ud->msg,
1423 " %[0-9a-zA-Z_]:%[0-9a-zA-Z_]%*1[ \t]%n",
1424 name, action, &data_start);
1425 if (eql(action, "main_quit")) {
1426 gtk_main_quit();
1427 goto done;
1429 if ((obj = (gtk_builder_get_object(ud->builder, name))) == NULL) {
1430 ign_cmd(type, ud->msg);
1431 goto done;
1433 type = G_TYPE_FROM_INSTANCE(obj);
1434 data = ud->msg + data_start;
1435 if (eql(action, "force"))
1436 fake_ui_activity(obj, ud->msg, type);
1437 else if (eql(action, "set_sensitive"))
1438 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1439 else if (eql(action, "set_visible"))
1440 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
1441 else if (eql(action, "style"))
1442 update_widget_style(GTK_WIDGET(obj), name, data);
1443 else if (type == GTK_TYPE_LABEL)
1444 update_label(GTK_LABEL(obj), action, data, ud->msg);
1445 else if (type == GTK_TYPE_IMAGE)
1446 update_image(GTK_IMAGE(obj), action, data, ud->msg);
1447 else if (type == GTK_TYPE_TEXT_VIEW)
1448 update_text_view(GTK_TEXT_VIEW(obj), action, data, ud->msg);
1449 else if (type == GTK_TYPE_NOTEBOOK)
1450 update_notebook(GTK_NOTEBOOK(obj), action, data, ud->msg);
1451 else if (type == GTK_TYPE_EXPANDER)
1452 update_expander(GTK_EXPANDER(obj), action, data, ud->msg);
1453 else if (type == GTK_TYPE_FRAME)
1454 update_frame(GTK_FRAME(obj), action, data, ud->msg);
1455 else if (type == GTK_TYPE_BUTTON)
1456 update_button(GTK_BUTTON(obj), action, data, ud->msg);
1457 else if (type == GTK_TYPE_FILE_CHOOSER_DIALOG)
1458 update_file_chooser_dialog(GTK_FILE_CHOOSER(obj), action, data, ud->msg);
1459 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1460 update_file_chooser_button(GTK_FILE_CHOOSER(obj), action, data, ud->msg);
1461 else if (type == GTK_TYPE_COLOR_BUTTON)
1462 update_color_button(GTK_COLOR_CHOOSER(obj), action, data, ud->msg);
1463 else if (type == GTK_TYPE_FONT_BUTTON)
1464 update_font_button(GTK_FONT_BUTTON(obj), action, data, ud->msg);
1465 else if (type == GTK_TYPE_PRINT_UNIX_DIALOG)
1466 update_print_dialog(GTK_PRINT_UNIX_DIALOG(obj), action, data, ud->msg);
1467 else if (type == GTK_TYPE_SWITCH)
1468 update_switch(GTK_SWITCH(obj), action, data, ud->msg);
1469 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
1470 update_toggle_button(GTK_TOGGLE_BUTTON(obj), action, data, ud->msg, type);
1471 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
1472 update_entry(GTK_ENTRY(obj), action, data, ud->msg, type);
1473 else if (type == GTK_TYPE_SCALE)
1474 update_scale(GTK_RANGE(obj), action, data, ud->msg);
1475 else if (type == GTK_TYPE_PROGRESS_BAR)
1476 update_progress_bar(GTK_PROGRESS_BAR(obj), action, data, ud->msg);
1477 else if (type == GTK_TYPE_SPINNER)
1478 update_spinner(GTK_SPINNER(obj), action, ud->msg);
1479 else if (type == GTK_TYPE_COMBO_BOX_TEXT)
1480 update_combo_box_text(GTK_COMBO_BOX_TEXT(obj), action, data, ud->msg);
1481 else if (type == GTK_TYPE_STATUSBAR)
1482 update_statusbar(GTK_STATUSBAR(obj), action, data, ud->msg);
1483 else if (type == GTK_TYPE_CALENDAR)
1484 update_calendar(GTK_CALENDAR(obj), action, data, ud->msg);
1485 else if (type == GTK_TYPE_TREE_VIEW)
1486 update_tree_view(GTK_TREE_VIEW(obj), action, data, ud->msg);
1487 else if (type == GTK_TYPE_DRAWING_AREA)
1488 update_drawing_area(GTK_WIDGET(obj), action, data, ud->msg);
1489 else if (type == GTK_TYPE_SOCKET)
1490 update_socket(GTK_SOCKET(obj), action, data, ud->msg);
1491 else if (type == GTK_TYPE_WINDOW)
1492 update_window(GTK_WINDOW(obj), action, data, ud->msg);
1493 else
1494 ign_cmd(type, ud->msg);
1495 done:
1496 ud->msg_digested = true;
1497 return G_SOURCE_REMOVE;
1500 static void
1501 free_at(void **mem)
1503 free(*mem);
1507 * Read lines from global stream "in" and perform the appropriate
1508 * actions on the GUI
1510 static void *
1511 digest_msg(void *builder)
1513 for (;;) {
1514 char first_char = '\0';
1515 struct ui_data ud;
1517 if ((ud.msg = malloc(ud.msg_size = 32)) == NULL )
1518 OOM_ABORT;
1519 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
1520 pthread_testcancel();
1521 read_buf(in, &ud.msg, &ud.msg_size);
1522 sscanf(ud.msg, " %c", &first_char);
1523 ud.builder = builder;
1524 if (first_char != '#') {
1525 ud.msg_digested = false;
1526 pthread_testcancel();
1527 gdk_threads_add_timeout(1, (GSourceFunc)update_ui, &ud);
1528 while (!ud.msg_digested)
1529 nanosleep(&(struct timespec){0, 1e6}, NULL);
1531 pthread_cleanup_pop(1);
1533 return NULL;
1537 * Create a fifo if necessary, and open it. Give up if the file
1538 * exists but is not a fifo
1540 static FILE *
1541 fifo(const char *name, const char *mode)
1543 struct stat sb;
1544 int fd;
1545 FILE *s = NULL;
1546 int bufmode;
1548 if (name != NULL && (stat(name, &sb), !S_ISFIFO(sb.st_mode)))
1549 if (mkfifo(name, 0666) != 0)
1550 bye(EXIT_FAILURE, stderr,
1551 "making fifo: %s\n", strerror(errno));
1552 switch (mode[0]) {
1553 case 'r':
1554 bufmode = _IONBF;
1555 if (name == NULL)
1556 s = stdin;
1557 else {
1558 fd = open(name, O_RDWR | O_NONBLOCK);
1559 if (fd < 0)
1560 bye(EXIT_FAILURE, stderr,
1561 "opening fifo: %s\n", strerror(errno));
1562 s = fdopen(fd, "r");
1564 break;
1565 case 'w':
1566 bufmode = _IOLBF;
1567 if (name == NULL)
1568 s = stdout;
1569 else
1570 s = fopen(name, "w+");
1571 break;
1572 default:
1573 abort();
1574 break;
1576 if (s == NULL)
1577 bye(EXIT_FAILURE, stderr, "opening fifo: %s\n", strerror(errno));
1578 else
1579 setvbuf(s, NULL, bufmode, 0);
1580 return s;
1584 * Remove suffix from name; find the object named like this
1586 static GObject *
1587 obj_sans_suffix(const char *suffix, const char *name, gpointer *builder)
1589 int str_l;
1590 char str[BUFLEN + 1] = {'\0'};
1592 str_l = suffix - name;
1593 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
1594 return gtk_builder_get_object(GTK_BUILDER(builder), str);
1597 static void
1598 connect_widget_signals(gpointer *obj, gpointer *builder)
1600 const char *name = NULL;
1601 char *suffix = NULL;
1602 GObject *obj2;
1603 GType type = G_TYPE_INVALID;
1605 type = G_TYPE_FROM_INSTANCE(obj);
1606 if (GTK_IS_BUILDABLE(obj))
1607 name = widget_name(obj);
1608 if (type == GTK_TYPE_TREE_VIEW_COLUMN)
1609 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1610 else if (type == GTK_TYPE_BUTTON) {
1611 /* Button associated with a GtkTextView. */
1612 if ((suffix = strstr(name, "_send_text")) != NULL &&
1613 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name, builder)))
1614 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
1615 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
1616 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
1617 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name, builder)))
1618 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
1619 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
1620 /* Buttons associated with (and part of) a GtkDialog.
1621 * (We shun response ids which could be returned from
1622 * gtk_dialog_run() because that would require the
1623 * user to define those response ids in Glade,
1624 * numerically */
1625 else if ((suffix = strstr(name, "_cancel")) != NULL &&
1626 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1627 if (eql(widget_name(obj2), MAIN_WIN))
1628 g_signal_connect(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
1629 else
1630 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
1631 else if ((suffix = strstr(name, "_ok")) != NULL &&
1632 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder))) {
1633 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
1634 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
1635 else /* generic button */
1636 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1637 if (eql(widget_name(obj2), MAIN_WIN))
1638 g_signal_connect(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
1639 else
1640 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
1641 } else if ((suffix = strstr(name, "_apply")) != NULL &&
1642 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1643 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
1644 else /* generic button */
1645 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
1647 else if (GTK_IS_MENU_ITEM(obj))
1648 if ((suffix = strstr(name, "_invoke")) != NULL &&
1649 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name, builder)))
1650 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
1651 else
1652 g_signal_connect(obj, "activate", G_CALLBACK(cb), "active");
1653 else if (GTK_IS_WINDOW(obj))
1654 if (eql(name, MAIN_WIN))
1655 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
1656 else
1657 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1658 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1659 g_signal_connect(obj, "file-set", G_CALLBACK(cb), "file");
1660 else if (type == GTK_TYPE_COLOR_BUTTON)
1661 g_signal_connect(obj, "color-set", G_CALLBACK(cb), "color");
1662 else if (type == GTK_TYPE_FONT_BUTTON)
1663 g_signal_connect(obj, "font-set", G_CALLBACK(cb), "font");
1664 else if (type == GTK_TYPE_SWITCH)
1665 g_signal_connect(obj, "notify::active", G_CALLBACK(cb), NULL);
1666 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
1667 g_signal_connect(obj, "toggled", G_CALLBACK(cb), NULL);
1668 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
1669 g_signal_connect(obj, "changed", G_CALLBACK(cb), "text");
1670 else if (type == GTK_TYPE_SCALE)
1671 g_signal_connect(obj, "value-changed", G_CALLBACK(cb), "value");
1672 else if (type == GTK_TYPE_CALENDAR) {
1673 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb), "doubleclicked");
1674 g_signal_connect(obj, "day-selected", G_CALLBACK(cb), "clicked");
1675 } else if (type == GTK_TYPE_TREE_SELECTION)
1676 g_signal_connect(obj, "changed", G_CALLBACK(cb), "clicked");
1677 else if (type == GTK_TYPE_SOCKET) {
1678 g_signal_connect(obj, "plug-added", G_CALLBACK(cb), "plug-added");
1679 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_true), "plug-removed");
1680 } else if (type == GTK_TYPE_DRAWING_AREA)
1681 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
1685 * We keep a list of one widget style provider for each widget
1687 static void
1688 add_widget_style_provider(gpointer *obj, void *data)
1690 const char *name = NULL;
1691 struct style_provider *sp = NULL, *last_sp = NULL;
1692 GtkStyleContext *context;
1694 (void)data;
1695 if (!GTK_IS_WIDGET(obj))
1696 return;
1697 if ((sp = malloc(sizeof(struct style_provider))) == NULL)
1698 OOM_ABORT;
1699 name = widget_name(obj);
1700 sp->name = name;
1701 sp->provider = gtk_css_provider_new();
1702 if ((sp->style_decl = malloc(sizeof('\0'))) == NULL)
1703 OOM_ABORT;
1704 *(sp->style_decl) = '\0';
1705 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
1706 gtk_style_context_add_provider(context,
1707 GTK_STYLE_PROVIDER(sp->provider),
1708 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1709 gtk_css_provider_load_from_data(sp->provider, sp->style_decl, -1, NULL);
1710 if (widget_style_providers == NULL) {
1711 widget_style_providers = sp;
1712 insque(widget_style_providers, NULL);
1713 } else {
1714 for (last_sp = widget_style_providers;
1715 last_sp->next != NULL;
1716 last_sp = last_sp->next);
1717 insque(sp, last_sp);
1721 static void
1722 prepare_widgets(GtkBuilder *builder)
1724 GSList *objects = NULL;
1726 objects = gtk_builder_get_objects(builder);
1727 g_slist_foreach(objects, (GFunc)connect_widget_signals, builder);
1728 g_slist_foreach(objects, (GFunc)add_widget_style_provider, NULL);
1729 g_slist_free(objects);
1733 main(int argc, char *argv[])
1735 char opt;
1736 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
1737 char *xid_s = NULL, xid_s2[BUFLEN];
1738 Window xid;
1739 GtkWidget *plug, *body;
1740 GtkBuilder *builder;
1741 pthread_t receiver;
1742 GError *error = NULL;
1743 GObject *main_window = NULL;
1745 /* Disable runtime GLIB deprecation warnings: */
1746 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
1747 in = NULL;
1748 out = NULL;
1749 while ((opt = getopt(argc, argv, "he:i:o:u:GV")) != -1) {
1750 switch (opt) {
1751 case 'e': xid_s = optarg; break;
1752 case 'i': in_fifo = optarg; break;
1753 case 'o': out_fifo = optarg; break;
1754 case 'u': ui_file = optarg; break;
1755 case 'G': bye(EXIT_SUCCESS, stdout, "GTK+ v%d.%d.%d (running v%d.%d.%d)\n",
1756 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
1757 gtk_get_major_version(), gtk_get_minor_version(),
1758 gtk_get_micro_version());
1759 break;
1760 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
1761 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
1762 case '?':
1763 default: bye(EXIT_FAILURE, stderr, USAGE); break;
1766 if (argv[optind] != NULL)
1767 bye(EXIT_FAILURE, stderr,
1768 "illegal parameter '%s'\n" USAGE, argv[optind]);
1769 if (ui_file == NULL)
1770 ui_file = "pipeglade.ui";
1771 gtk_init(0, NULL);
1772 builder = gtk_builder_new();
1773 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0)
1774 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
1775 in = fifo(in_fifo, "r");
1776 out = fifo(out_fifo, "w");
1777 pthread_create(&receiver, NULL, digest_msg, (void*)builder);
1778 main_window = gtk_builder_get_object(builder, MAIN_WIN);
1779 if (!GTK_IS_WINDOW(main_window))
1780 bye(EXIT_FAILURE, stderr,
1781 "no toplevel window named \'" MAIN_WIN "\'\n");
1782 prepare_widgets(builder);
1783 if (xid_s == NULL) /* standalone */
1784 gtk_widget_show(GTK_WIDGET(main_window));
1785 else { /* We're being XEmbedded */
1786 xid = strtoul(xid_s, NULL, 10);
1787 snprintf(xid_s2, BUFLEN, "%lu", xid);
1788 if (!eql(xid_s, xid_s2))
1789 bye(EXIT_FAILURE, stderr,
1790 "%s is not a valid XEmbed socket id\n", xid_s);
1791 body = gtk_bin_get_child(GTK_BIN(main_window));
1792 gtk_container_remove(GTK_CONTAINER(main_window), body);
1793 plug = gtk_plug_new(xid);
1794 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
1795 bye(EXIT_FAILURE, stderr,
1796 "unable to embed into XEmbed socket %s\n", xid_s);
1797 gtk_container_add(GTK_CONTAINER(plug), body);
1798 gtk_widget_show(plug);
1800 gtk_main();
1801 if (in != stdin) {
1802 fclose(in);
1803 unlink(in_fifo);
1805 if (out != stdout) {
1806 fclose(out);
1807 unlink(out_fifo);
1809 pthread_cancel(receiver);
1810 pthread_join(receiver, NULL);
1811 exit(EXIT_SUCCESS);