Change -h message to make it resemble Synopsis in manual page
[pipeglade.git] / pipeglade.c
blob40160390bb4875a0c1c13385b17f7f6f7555dce3
1 /*
2 * Copyright (c) 2014-2016 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 <libxml/xpath.h>
31 #include <locale.h>
32 #include <math.h>
33 #include <pthread.h>
34 #include <semaphore.h>
35 #include <stdio.h>
36 #include <stdarg.h>
37 #include <stdbool.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/select.h>
41 #include <sys/stat.h>
42 #include <time.h>
43 #include <unistd.h>
45 #define VERSION "4.7.0"
46 #define BUFLEN 256
47 #define WHITESPACE " \t\n"
48 #define MAIN_WIN "main"
49 #define USAGE \
50 "usage: pipeglade [[-i in-fifo] " \
51 "[-o out-fifo] " \
52 "[-u glade-file.ui] " \
53 "[-e xid]\n" \
54 " [-l log-file] " \
55 "[--display X-server]] | " \
56 "[-h | " \
57 "-G | " \
58 "-V]\n"
60 #define ABORT \
61 { \
62 fprintf(stderr, \
63 "In %s (%s:%d): ", \
64 __func__, __FILE__, __LINE__); \
65 abort(); \
68 #define OOM_ABORT \
69 { \
70 fprintf(stderr, \
71 "Out of memory in %s (%s:%d): ", \
72 __func__, __FILE__, __LINE__); \
73 abort(); \
76 static FILE *out; /* UI feedback messages */
77 static FILE *save; /* saving user data */
78 static FILE *log_out; /* logging output */
79 static GtkBuilder *builder; /* to be read from .ui file */
80 struct ui_data {
81 void (*fn)(GObject *, const char *action,
82 const char *data, const char *msg, GType type);
83 GObject *obj;
84 char *action;
85 char *data;
86 char *msg;
87 char *msg_tokens;
88 GType type;
89 sem_t msg_digested;
93 * Print a formatted message to stream s and give up with status
95 static void
96 bye(int status, FILE *s, const char *fmt, ...)
98 va_list ap;
100 va_start(ap, fmt);
101 vfprintf(s, fmt, ap);
102 va_end(ap);
103 exit(status);
107 * Check if string s1 and s2 are equal
109 static bool
110 eql(const char *s1, const char *s2)
112 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
115 static const char *
116 widget_name(void *obj)
118 return gtk_buildable_get_name(GTK_BUILDABLE(obj));
121 static void
122 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
124 char *data;
125 const char *w_name = widget_name(obj);
126 fd_set wfds;
127 int ofd = fileno(o);
128 struct timeval timeout = {1, 0};
130 FD_ZERO(&wfds);
131 FD_SET(ofd, &wfds);
132 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
133 fprintf(o, "%s:%s ", w_name, tag);
134 while ((data = va_arg(ap, char *)) != NULL) {
135 size_t i = 0;
136 char c;
138 while ((c = data[i++]) != '\0')
139 if (c == '\\')
140 fprintf(o, "\\\\");
141 else if (c == '\n')
142 fprintf(o, "\\n");
143 else
144 putc(c, o);
146 putc('\n', o);
147 } else
148 fprintf(stderr,
149 "send error; discarding feedback message %s:%s\n",
150 w_name, tag);
154 * Send GUI feedback to global stream "out". The message format is
155 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
156 * last argument must be NULL.
158 static void
159 send_msg(GtkBuildable *obj, const char *tag, ...)
161 va_list ap;
162 va_start(ap, tag);
163 send_msg_to(out, obj, tag, ap);
164 va_end(ap);
168 * Send message from GUI to global stream "save". The message format
169 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
170 * last argument must be NULL.
172 static void
173 save_msg(GtkBuildable *obj, const char *tag, ...)
175 va_list ap;
176 va_start(ap, tag);
177 send_msg_to(save, obj, tag, ap);
178 va_end(ap);
182 * Send message from GUI to global stream "save". The message format
183 * is "<origin>:set <data ...>". The variadic arguments are strings;
184 * last argument must be NULL.
186 static void
187 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
189 va_list ap;
190 va_start(ap, tag);
191 send_msg_to(save, obj, "set", ap);
192 va_end(ap);
196 * Callback that sends user's selection from a file dialog
198 static void
199 cb_send_file_chooser_dialog_selection(gpointer user_data)
201 send_msg(user_data, "file",
202 gtk_file_chooser_get_filename(user_data), NULL);
203 send_msg(user_data, "folder",
204 gtk_file_chooser_get_current_folder(user_data), NULL);
208 * Callback that sends in a message the content of the text buffer
209 * passed in user_data
211 static void
212 cb_send_text(GtkBuildable *obj, gpointer user_data)
214 GtkTextIter a, b;
216 gtk_text_buffer_get_bounds(user_data, &a, &b);
217 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
221 * Callback that sends in a message the highlighted text from the text
222 * buffer which was passed in user_data
224 static void
225 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
227 GtkTextIter a, b;
229 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
230 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
234 * Callbacks that send messages about pointer device activity in a
235 * GtkEventBox.
237 static bool
238 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
240 char data[BUFLEN];
242 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
243 e->button.button, e->button.x, e->button.y);
244 send_msg(obj, user_data, data, NULL);
245 return true;
248 static bool
249 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
251 char data[BUFLEN];
253 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
254 send_msg(obj, user_data, data, NULL);
255 return true;
259 * Callback that sends in a message the name of the key pressed when
260 * a GtkEventBox is focused.
262 static bool
263 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
265 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
266 return true;
270 * Use msg_sender() to send a message describing a particular cell.
272 static void
273 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
274 GtkTreeModel *model, const char *path_s,
275 GtkTreeIter *iter, int col, GtkBuildable *obj)
277 GValue value = G_VALUE_INIT;
278 GType col_type;
279 char str[BUFLEN];
281 gtk_tree_model_get_value(model, iter, col, &value);
282 col_type = gtk_tree_model_get_column_type(model, col);
283 switch (col_type) {
284 case G_TYPE_INT:
285 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
286 msg_sender(obj, "gint", path_s, str, NULL);
287 break;
288 case G_TYPE_LONG:
289 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
290 msg_sender(obj, "glong", path_s, str, NULL);
291 break;
292 case G_TYPE_INT64:
293 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
294 msg_sender(obj, "gint64", path_s, str, NULL);
295 break;
296 case G_TYPE_UINT:
297 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
298 msg_sender(obj, "guint", path_s, str, NULL);
299 break;
300 case G_TYPE_ULONG:
301 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
302 msg_sender(obj, "gulong", path_s, str, NULL);
303 break;
304 case G_TYPE_UINT64:
305 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
306 msg_sender(obj, "guint64", path_s, str, NULL);
307 break;
308 case G_TYPE_BOOLEAN:
309 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
310 msg_sender(obj, "gboolean", path_s, str, NULL);
311 break;
312 case G_TYPE_FLOAT:
313 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
314 msg_sender(obj, "gfloat", path_s, str, NULL);
315 break;
316 case G_TYPE_DOUBLE:
317 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
318 msg_sender(obj, "gdouble", path_s, str, NULL);
319 break;
320 case G_TYPE_STRING:
321 snprintf(str, BUFLEN, " %d ", col);
322 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
323 break;
324 default:
325 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
326 break;
328 g_value_unset(&value);
332 * Use msg_sender() to send one message per column for a single row.
334 static void
335 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
336 GtkTreeModel *model, char *path_s,
337 GtkTreeIter *iter, GtkBuildable *obj)
339 int col;
340 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
341 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
345 * send_tree_row_msg serves as an argument for
346 * gtk_tree_selection_selected_foreach()
348 static gboolean
349 send_tree_row_msg(GtkTreeModel *model,
350 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
352 char *path_s = gtk_tree_path_to_string(path);
354 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
355 g_free(path_s);
356 return FALSE;
360 * save_tree_row_msg serves as an argument for
361 * gtk_tree_model_foreach().
362 * Send message from GUI to global stream "save".
364 static gboolean
365 save_tree_row_msg(GtkTreeModel *model,
366 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
368 char *path_s = gtk_tree_path_to_string(path);
370 (void) path;
371 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
372 g_free(path_s);
373 return FALSE;
377 * Callback that sends message(s) whose nature depends on the
378 * arguments passed. A call to this function will also be initiated
379 * by the user command ...:force.
381 static void
382 cb(GtkBuildable *obj, const char *tag)
384 char str[BUFLEN];
385 GdkRGBA color;
386 GtkTreeView *view;
387 unsigned int year = 0, month = 0, day = 0;
389 if (GTK_IS_ENTRY(obj))
390 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
391 else if (GTK_IS_MENU_ITEM(obj))
392 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
393 else if (GTK_IS_RANGE(obj)) {
394 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
395 send_msg(obj, tag, str, NULL);
396 } else if (GTK_IS_SWITCH(obj))
397 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
398 else if (GTK_IS_TOGGLE_BUTTON(obj))
399 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
400 else if (GTK_IS_COLOR_BUTTON(obj)) {
401 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
402 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
403 } else if (GTK_IS_FONT_BUTTON(obj))
404 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
405 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
406 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
407 else if (GTK_IS_BUTTON(obj) || GTK_IS_TREE_VIEW_COLUMN(obj) || GTK_IS_SOCKET(obj))
408 send_msg(obj, tag, NULL);
409 else if (GTK_IS_CALENDAR(obj)) {
410 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
411 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
412 send_msg(obj, tag, str, NULL);
413 } else if (GTK_IS_TREE_SELECTION(obj)) {
414 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
415 send_msg(GTK_BUILDABLE(view), tag, NULL);
416 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
417 (GtkTreeSelectionForeachFunc) send_tree_row_msg,
418 view);
419 } else
420 fprintf(stderr, "ignoring callback from %s\n", widget_name(obj));
424 * Callback like cb(), but returning true.
426 static bool
427 cb_true(GtkBuildable *obj, const char *tag)
429 cb(obj, tag);
430 return true;
434 * Store a line from stream s into buf, which should have been malloc'd
435 * to bufsize. Enlarge buf and bufsize if necessary.
437 static size_t
438 read_buf(FILE *s, char **buf, size_t *bufsize)
440 size_t i = 0;
441 int c;
442 fd_set rfds;
443 int ifd = fileno(s);
444 bool esc = false;
446 FD_ZERO(&rfds);
447 FD_SET(ifd, &rfds);
448 for (;;) {
449 select(ifd + 1, &rfds, NULL, NULL, NULL);
450 c = getc(s);
451 if (c == '\n' || feof(s))
452 break;
453 if (i >= *bufsize - 1)
454 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
455 OOM_ABORT;
456 if (esc) {
457 esc = false;
458 switch (c) {
459 case 'n': (*buf)[i++] = '\n'; break;
460 case 'r': (*buf)[i++] = '\r'; break;
461 default: (*buf)[i++] = c; break;
463 } else if (c == '\\')
464 esc = true;
465 else
466 (*buf)[i++] = c;
468 (*buf)[i] = '\0';
469 return i;
473 * Warning message
475 static void
476 ign_cmd(GType type, const char *msg)
478 const char *name, *pad = " ";
480 if (type == G_TYPE_INVALID) {
481 name = "";
482 pad = "";
484 else
485 name = g_type_name(type);
486 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
490 * Drawing on a GtkDrawingArea
492 enum cairo_fn {
493 RECTANGLE,
494 ARC,
495 ARC_NEGATIVE,
496 CURVE_TO,
497 REL_CURVE_TO,
498 LINE_TO,
499 REL_LINE_TO,
500 MOVE_TO,
501 REL_MOVE_TO,
502 CLOSE_PATH,
503 SET_SOURCE_RGBA,
504 SET_DASH,
505 SET_LINE_CAP,
506 SET_LINE_JOIN,
507 SET_LINE_WIDTH,
508 FILL,
509 FILL_PRESERVE,
510 STROKE,
511 STROKE_PRESERVE,
512 SHOW_TEXT,
513 SET_FONT_SIZE,
517 * One single element of a drawing
519 struct draw_op {
520 struct draw_op *next;
521 struct draw_op *prev;
522 unsigned int id;
523 enum cairo_fn op;
524 void *op_args;
527 struct rectangle_args {
528 double x;
529 double y;
530 double width;
531 double height;
534 struct arc_args {
535 double x;
536 double y;
537 double radius;
538 double angle1;
539 double angle2;
542 struct curve_to_args {
543 double x1;
544 double y1;
545 double x2;
546 double y2;
547 double x3;
548 double y3;
551 struct move_to_args {
552 double x;
553 double y;
556 struct set_source_rgba_args {
557 GdkRGBA color;
560 struct set_dash_args {
561 int num_dashes;
562 double dashes[];
565 struct set_line_cap_args {
566 cairo_line_cap_t line_cap;
569 struct set_line_join_args {
570 cairo_line_join_t line_join;
573 struct set_line_width_args {
574 double width;
577 struct show_text_args {
578 int len;
579 char text[];
582 struct set_font_size_args {
583 double size;
586 static void
587 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
589 switch (op) {
590 case RECTANGLE: {
591 struct rectangle_args *args = op_args;
593 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
594 break;
596 case ARC: {
597 struct arc_args *args = op_args;
599 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
600 break;
602 case ARC_NEGATIVE: {
603 struct arc_args *args = op_args;
605 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
606 break;
608 case CURVE_TO: {
609 struct curve_to_args *args = op_args;
611 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
612 break;
614 case REL_CURVE_TO: {
615 struct curve_to_args *args = op_args;
617 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
618 break;
620 case LINE_TO: {
621 struct move_to_args *args = op_args;
623 cairo_line_to(cr, args->x, args->y);
624 break;
626 case REL_LINE_TO: {
627 struct move_to_args *args = op_args;
629 cairo_rel_line_to(cr, args->x, args->y);
630 break;
632 case MOVE_TO: {
633 struct move_to_args *args = op_args;
635 cairo_move_to(cr, args->x, args->y);
636 break;
638 case REL_MOVE_TO: {
639 struct move_to_args *args = op_args;
641 cairo_rel_move_to(cr, args->x, args->y);
642 break;
644 case CLOSE_PATH:
645 cairo_close_path(cr);
646 break;
647 case SET_SOURCE_RGBA: {
648 struct set_source_rgba_args *args = op_args;
650 gdk_cairo_set_source_rgba(cr, &args->color);
651 break;
653 case SET_DASH: {
654 struct set_dash_args *args = op_args;
656 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
657 break;
659 case SET_LINE_CAP: {
660 struct set_line_cap_args *args = op_args;
662 cairo_set_line_cap(cr, args->line_cap);
663 break;
665 case SET_LINE_JOIN: {
666 struct set_line_join_args *args = op_args;
668 cairo_set_line_join(cr, args->line_join);
669 break;
671 case SET_LINE_WIDTH: {
672 struct set_line_width_args *args = op_args;
674 cairo_set_line_width(cr, args->width);
675 break;
677 case FILL:
678 cairo_fill(cr);
679 break;
680 case FILL_PRESERVE:
681 cairo_fill_preserve(cr);
682 break;
683 case STROKE:
684 cairo_stroke(cr);
685 break;
686 case STROKE_PRESERVE:
687 cairo_stroke_preserve(cr);
688 break;
689 case SHOW_TEXT: {
690 struct show_text_args *args = op_args;
692 cairo_show_text(cr, args->text);
693 break;
695 case SET_FONT_SIZE: {
696 struct set_font_size_args *args = op_args;
698 cairo_set_font_size(cr, args->size);
699 break;
701 default:
702 ABORT;
703 break;
707 static bool
708 set_draw_op(struct draw_op *op, const char *action, const char *data)
710 if (eql(action, "rectangle")) {
711 struct rectangle_args *args;
713 if ((args = malloc(sizeof(*args))) == NULL)
714 OOM_ABORT;
715 op->op = RECTANGLE;
716 op->op_args = args;
717 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->width, &args->height) != 5)
718 return false;
719 } else if (eql(action, "arc")) {
720 struct arc_args *args;
721 double deg1, deg2;
723 if ((args = malloc(sizeof(*args))) == NULL)
724 OOM_ABORT;
725 op->op = ARC;
726 op->op_args = args;
727 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
728 return false;
729 args->angle1 = deg1 * (M_PI / 180.);
730 args->angle2 = deg2 * (M_PI / 180.);
731 } else if (eql(action, "arc_negative")) {
732 struct arc_args *args;
733 double deg1, deg2;
735 if ((args = malloc(sizeof(*args))) == NULL)
736 OOM_ABORT;
737 op->op = ARC_NEGATIVE;
738 op->op_args = args;
739 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
740 return false;
741 args->angle1 = deg1 * (M_PI / 180.);
742 args->angle2 = deg2 * (M_PI / 180.);
743 } else if (eql(action, "curve_to")) {
744 struct curve_to_args *args;
746 if ((args = malloc(sizeof(*args))) == NULL)
747 OOM_ABORT;
748 op->op = CURVE_TO;
749 op->op_args = args;
750 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)
751 return false;
752 } else if (eql(action, "rel_curve_to")) {
753 struct curve_to_args *args;
755 if ((args = malloc(sizeof(*args))) == NULL)
756 OOM_ABORT;
757 op->op = REL_CURVE_TO;
758 op->op_args = args;
759 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)
760 return false;
761 } else if (eql(action, "line_to")) {
762 struct move_to_args *args;
764 if ((args = malloc(sizeof(*args))) == NULL)
765 OOM_ABORT;
766 op->op = LINE_TO;
767 op->op_args = args;
768 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
769 return false;
770 } else if (eql(action, "rel_line_to")) {
771 struct move_to_args *args;
773 if ((args = malloc(sizeof(*args))) == NULL)
774 OOM_ABORT;
775 op->op = REL_LINE_TO;
776 op->op_args = args;
777 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
778 return false;
779 } else if (eql(action, "move_to")) {
780 struct move_to_args *args;
782 if ((args = malloc(sizeof(*args))) == NULL)
783 OOM_ABORT;
784 op->op = MOVE_TO;
785 op->op_args = args;
786 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
787 return false;
788 } else if (eql(action, "rel_move_to")) {
789 struct move_to_args *args;
791 if ((args = malloc(sizeof(*args))) == NULL)
792 OOM_ABORT;
793 op->op = REL_MOVE_TO;
794 op->op_args = args;
795 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
796 return false;
797 } else if (eql(action, "close_path")) {
798 op->op = CLOSE_PATH;
799 if (sscanf(data, "%u", &op->id) != 1)
800 return false;
801 op->op_args = NULL;
802 } else if (eql(action, "set_source_rgba")) {
803 struct set_source_rgba_args *args;
804 int c_start;
806 if ((args = malloc(sizeof(*args))) == NULL)
807 OOM_ABORT;
808 op->op = SET_SOURCE_RGBA;
809 op->op_args = args;
810 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
811 return false;;
812 gdk_rgba_parse(&args->color, data + c_start);
813 } else if (eql(action, "set_dash")) {
814 struct set_dash_args *args;
815 int d_start, n, i;
816 char data1[strlen(data) + 1];
817 char *next, *end;
819 strcpy(data1, data);
820 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
821 return false;
822 next = end = data1 + d_start;
823 n = -1;
824 do {
825 n++;
826 next = end;
827 strtod(next, &end);
828 } while (next != end);
829 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
830 OOM_ABORT;
831 op->op = SET_DASH;
832 op->op_args = args;
833 args->num_dashes = n;
834 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
835 args->dashes[i] = strtod(next, &end);
837 } else if (eql(action, "set_line_cap")) {
838 struct set_line_cap_args *args;
839 char str[6 + 1];
841 if ((args = malloc(sizeof(*args))) == NULL)
842 OOM_ABORT;
843 op->op = SET_LINE_CAP;
844 op->op_args = args;
845 if (sscanf(data, "%u %6s", &op->id, str) != 2)
846 return false;
847 if (eql(str, "butt"))
848 args->line_cap = CAIRO_LINE_CAP_BUTT;
849 else if (eql(str, "round"))
850 args->line_cap = CAIRO_LINE_CAP_ROUND;
851 else if (eql(str, "square"))
852 args->line_cap = CAIRO_LINE_CAP_SQUARE;
853 else
854 return false;
855 } else if (eql(action, "set_line_join")) {
856 struct set_line_join_args *args;
857 char str[5 + 1];
859 if ((args = malloc(sizeof(*args))) == NULL)
860 OOM_ABORT;
861 op->op = SET_LINE_JOIN;
862 op->op_args = args;
863 if (sscanf(data, "%u %5s", &op->id, str) != 2)
864 return false;
865 if (eql(str, "miter"))
866 args->line_join = CAIRO_LINE_JOIN_MITER;
867 else if (eql(str, "round"))
868 args->line_join = CAIRO_LINE_JOIN_ROUND;
869 else if (eql(str, "bevel"))
870 args->line_join = CAIRO_LINE_JOIN_BEVEL;
871 else
872 return false;
873 } else if (eql(action, "set_line_width")) {
874 struct set_line_width_args *args;
876 if ((args = malloc(sizeof(*args))) == NULL)
877 OOM_ABORT;
878 op->op = SET_LINE_WIDTH;
879 op->op_args = args;
880 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
881 return false;
882 } else if (eql(action, "fill")) {
883 op->op = FILL;
884 if (sscanf(data, "%u", &op->id) != 1)
885 return false;
886 op->op_args = NULL;
887 } else if (eql(action, "fill_preserve")) {
888 op->op = FILL_PRESERVE;
889 if (sscanf(data, "%u", &op->id) != 1)
890 return false;
891 op->op_args = NULL;
892 } else if (eql(action, "stroke")) {
893 op->op = STROKE;
894 if (sscanf(data, "%u", &op->id) != 1)
895 return false;
896 op->op_args = NULL;
897 } else if (eql(action, "stroke_preserve")) {
898 op->op = STROKE_PRESERVE;
899 if (sscanf(data, "%u", &op->id) != 1)
900 return false;
901 op->op_args = NULL;
902 } else if (eql(action, "show_text")) {
903 struct show_text_args *args;
904 int start, len;
906 if (sscanf(data, "%u %n", &op->id, &start) < 1)
907 return false;
908 len = strlen(data + start) + 1;
909 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
910 OOM_ABORT;
911 op->op = SHOW_TEXT;
912 op->op_args = args;
913 args->len = len; /* not used */
914 strncpy(args->text, (data + start), len);
915 } else if (eql(action, "set_font_size")) {
916 struct set_font_size_args *args;
918 if ((args = malloc(sizeof(*args))) == NULL)
919 OOM_ABORT;
920 op->op = SET_FONT_SIZE;
921 op->op_args = args;
922 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
923 return false;
924 } else
925 return false;
926 return true;
930 * Append another element to widget's "draw_ops" list
932 static bool
933 ins_draw_op(GObject *widget, const char *action, const char *data)
935 struct draw_op *op, *draw_ops, *last_op;
937 if ((op = malloc(sizeof(*op))) == NULL)
938 OOM_ABORT;
939 op->op_args = NULL;
940 op->next = NULL;
941 if (!set_draw_op(op, action, data)) {
942 free(op->op_args);
943 free(op);
944 return false;
946 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
947 g_object_set_data(widget, "draw_ops", op);
948 else {
949 for (last_op = draw_ops; last_op->next != NULL; last_op = last_op->next);
950 last_op->next = op;
952 return true;
956 * Remove all elements with the given id from widget's "draw_ops" list
958 static bool
959 rem_draw_op(GObject *widget, const char *data)
961 struct draw_op *op, *next_op, *prev_op = NULL;
962 unsigned int id;
964 if (sscanf(data, "%u", &id) != 1)
965 return false;
966 op = g_object_get_data(widget, "draw_ops");
967 while (op != NULL) {
968 next_op = op->next;
969 if (op->id == id) {
970 if (prev_op == NULL) /* list head */
971 g_object_set_data(widget, "draw_ops", op->next);
972 else
973 prev_op->next = op->next;
974 free(op->op_args);
975 free(op);
976 } else
977 prev_op = op;
978 op = next_op;
980 return true;
984 * Callback that draws on a GtkDrawingArea
986 static gboolean
987 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
989 struct draw_op *op;
991 (void) data;
992 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
993 op != NULL;
994 op = op->next)
995 draw(cr, op->op, op->op_args);
996 return FALSE;
1000 * Change the style of the widget passed
1002 static void
1003 update_widget_style(GObject *obj, const char *name,
1004 const char *data, const char *whole_msg, GType type)
1006 GtkStyleContext *context;
1007 GtkStyleProvider *style_provider;
1008 char *style_decl;
1009 const char *prefix = "* {", *suffix = "}";
1010 size_t sz;
1012 (void) name;
1013 (void) whole_msg;
1014 (void) type;
1015 style_provider = g_object_get_data(obj, "style_provider");
1016 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
1017 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
1018 gtk_style_context_remove_provider(context, style_provider);
1019 if ((style_decl = malloc(sz)) == NULL)
1020 OOM_ABORT;
1021 strcpy(style_decl, prefix);
1022 strcat(style_decl, data);
1023 strcat(style_decl, suffix);
1024 gtk_style_context_add_provider(context, style_provider,
1025 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1026 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
1027 style_decl, -1, NULL);
1028 free(style_decl);
1032 * Common actions for various kinds of window. Return false if
1033 * command is ignored
1035 static bool
1036 update_class_window(GObject *obj, const char *action,
1037 const char *data, const char *whole_msg, GType type)
1039 GtkWindow *window = GTK_WINDOW(obj);
1040 int x, y;
1041 bool result = true;
1043 if (eql(action, "set_title"))
1044 gtk_window_set_title(window, data);
1045 else if (eql(action, "fullscreen"))
1046 gtk_window_fullscreen(window);
1047 else if (eql(action, "unfullscreen"))
1048 gtk_window_unfullscreen(window);
1049 else if (eql(action, "resize")) {
1050 if (sscanf(data, "%d %d", &x, &y) != 2)
1051 gtk_window_get_default_size(window, &x, &y);
1052 gtk_window_resize(window, x, y);
1053 } else if (eql(action, "move")) {
1054 if (sscanf(data, "%d %d", &x, &y) == 2)
1055 gtk_window_move(window, x, y);
1056 else
1057 ign_cmd(type, whole_msg);
1058 } else
1059 result = false;
1060 return result;
1064 * Update various kinds of widgets according to the respective action
1065 * parameter
1067 static void
1068 update_button(GObject *obj, const char *action,
1069 const char *data, const char *whole_msg, GType type)
1071 if (eql(action, "set_label"))
1072 gtk_button_set_label(GTK_BUTTON(obj), data);
1073 else
1074 ign_cmd(type, whole_msg);
1077 static void
1078 update_calendar(GObject *obj, const char *action,
1079 const char *data, const char *whole_msg, GType type)
1081 GtkCalendar *calendar = GTK_CALENDAR(obj);
1082 int year = 0, month = 0, day = 0;
1084 if (eql(action, "select_date")) {
1085 sscanf(data, "%d-%d-%d", &year, &month, &day);
1086 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1087 gtk_calendar_select_month(calendar, --month, year);
1088 gtk_calendar_select_day(calendar, day);
1089 } else
1090 ign_cmd(type, whole_msg);
1091 } else if (eql(action, "mark_day")) {
1092 day = strtol(data, NULL, 10);
1093 if (day > 0 && day <= 31)
1094 gtk_calendar_mark_day(calendar, day);
1095 else
1096 ign_cmd(type, whole_msg);
1097 } else if (eql(action, "clear_marks"))
1098 gtk_calendar_clear_marks(calendar);
1099 else
1100 ign_cmd(type, whole_msg);
1103 static void
1104 update_color_button(GObject *obj, const char *action,
1105 const char *data, const char *whole_msg, GType type)
1107 GdkRGBA color;
1109 if (eql(action, "set_color")) {
1110 gdk_rgba_parse(&color, data);
1111 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1112 } else
1113 ign_cmd(type, whole_msg);
1116 static void
1117 update_combo_box_text(GObject *obj, const char *action,
1118 const char *data, const char *whole_msg, GType type)
1120 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1121 char data1[strlen(data) + 1];
1123 strcpy(data1, data);
1124 if (eql(action, "prepend_text"))
1125 gtk_combo_box_text_prepend_text(combobox, data1);
1126 else if (eql(action, "append_text"))
1127 gtk_combo_box_text_append_text(combobox, data1);
1128 else if (eql(action, "remove"))
1129 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1130 else if (eql(action, "insert_text")) {
1131 char *position = strtok(data1, WHITESPACE);
1132 char *text = strtok(NULL, WHITESPACE);
1133 gtk_combo_box_text_insert_text(combobox, strtol(position, NULL, 10), text);
1134 } else
1135 ign_cmd(type, whole_msg);
1138 static void
1139 update_frame(GObject *obj, const char *action,
1140 const char *data, const char *whole_msg, GType type)
1142 if (eql(action, "set_label"))
1143 gtk_frame_set_label(GTK_FRAME(obj), data);
1144 else
1145 ign_cmd(type, whole_msg);
1148 static void
1149 update_scrolled_window(GObject *obj, const char *action,
1150 const char *data, const char *whole_msg, GType type)
1152 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
1153 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
1154 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
1155 double d0, d1;
1157 if (eql(action, "hscroll") && sscanf(data, "%lf", &d0) == 1)
1158 gtk_adjustment_set_value(hadj, d0);
1159 else if (eql(action, "vscroll") && sscanf(data, "%lf", &d0) == 1)
1160 gtk_adjustment_set_value(vadj, d0);
1161 else if (eql(action, "hscroll_to_range") &&
1162 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1163 gtk_adjustment_clamp_page(hadj, d0, d1);
1164 else if (eql(action, "vscroll_to_range") &&
1165 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1166 gtk_adjustment_clamp_page(vadj, d0, d1);
1167 else
1168 ign_cmd(type, whole_msg);
1171 static void
1172 update_drawing_area(GObject *obj, const char *action,
1173 const char *data, const char *whole_msg, GType type)
1175 if (eql(action, "remove")) {
1176 if (!rem_draw_op(obj, data))
1177 ign_cmd(type, whole_msg);
1178 } else if (eql(action, "refresh")) {
1179 gint width = gtk_widget_get_allocated_width(GTK_WIDGET(obj));
1180 gint height = gtk_widget_get_allocated_height(GTK_WIDGET(obj));
1182 gtk_widget_queue_draw_area(GTK_WIDGET(obj), 0, 0, width, height);
1183 } else if (ins_draw_op(obj, action, data));
1184 else
1185 ign_cmd(type, whole_msg);
1188 static void
1189 update_entry(GObject *obj, const char *action,
1190 const char *data, const char *whole_msg, GType type)
1192 GtkEntry *entry = GTK_ENTRY(obj);
1194 if (eql(action, "set_text"))
1195 gtk_entry_set_text(entry, data);
1196 else if (eql(action, "set_placeholder_text"))
1197 gtk_entry_set_placeholder_text(entry, data);
1198 else
1199 ign_cmd(type, whole_msg);
1202 static void
1203 update_label(GObject *obj, const char *action,
1204 const char *data, const char *whole_msg, GType type)
1206 if (eql(action, "set_text"))
1207 gtk_label_set_text(GTK_LABEL(obj), data);
1208 else
1209 ign_cmd(type, whole_msg);
1212 static void
1213 update_expander(GObject *obj, const char *action,
1214 const char *data, const char *whole_msg, GType type)
1216 GtkExpander *expander = GTK_EXPANDER(obj);
1218 if (eql(action, "set_expanded"))
1219 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1220 else if (eql(action, "set_label"))
1221 gtk_expander_set_label(expander, data);
1222 else
1223 ign_cmd(type, whole_msg);
1226 static void
1227 update_file_chooser_button(GObject *obj, const char *action,
1228 const char *data, const char *whole_msg, GType type)
1230 if (eql(action, "set_filename"))
1231 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1232 else
1233 ign_cmd(type, whole_msg);
1236 static void
1237 update_file_chooser_dialog(GObject *obj, const char *action,
1238 const char *data, const char *whole_msg, GType type)
1240 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1242 if (eql(action, "set_filename"))
1243 gtk_file_chooser_set_filename(chooser, data);
1244 else if (eql(action, "set_current_name"))
1245 gtk_file_chooser_set_current_name(chooser, data);
1246 else if (update_class_window(obj, action, data, whole_msg, type));
1247 else
1248 ign_cmd(type, whole_msg);
1251 static void
1252 update_font_button(GObject *obj, const char *action,
1253 const char *data, const char *whole_msg, GType type)
1255 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1257 if (eql(action, "set_font_name"))
1258 gtk_font_button_set_font_name(font_button, data);
1259 else
1260 ign_cmd(type, whole_msg);
1263 static void
1264 update_print_dialog(GObject *obj, const char *action,
1265 const char *data, const char *whole_msg, GType type)
1267 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1268 gint response_id;
1269 GtkPrinter *printer;
1270 GtkPrintSettings *settings;
1271 GtkPageSetup *page_setup;
1272 GtkPrintJob *job;
1274 if (eql(action, "print")) {
1275 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1276 switch (response_id) {
1277 case GTK_RESPONSE_OK:
1278 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1279 settings = gtk_print_unix_dialog_get_settings(dialog);
1280 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1281 job = gtk_print_job_new(data, printer, settings, page_setup);
1282 if (gtk_print_job_set_source_file(job, data, NULL))
1283 gtk_print_job_send(job, NULL, NULL, NULL);
1284 else
1285 ign_cmd(type, whole_msg);
1286 g_clear_object(&settings);
1287 g_clear_object(&job);
1288 break;
1289 case GTK_RESPONSE_CANCEL:
1290 case GTK_RESPONSE_DELETE_EVENT:
1291 break;
1292 default:
1293 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1294 widget_name(GTK_WIDGET(dialog)), response_id);
1295 break;
1297 gtk_widget_hide(GTK_WIDGET(dialog));
1298 } else
1299 ign_cmd(type, whole_msg);
1302 static void
1303 update_image(GObject *obj, const char *action,
1304 const char *data, const char *whole_msg, GType type)
1306 GtkImage *image = GTK_IMAGE(obj);
1307 GtkIconSize size;
1309 gtk_image_get_icon_name(image, NULL, &size);
1310 if (eql(action, "set_from_file"))
1311 gtk_image_set_from_file(image, data);
1312 else if (eql(action, "set_from_icon_name"))
1313 gtk_image_set_from_icon_name(image, data, size);
1314 else
1315 ign_cmd(type, whole_msg);
1318 static void
1319 update_notebook(GObject *obj, const char *action,
1320 const char *data, const char *whole_msg, GType type)
1322 if (eql(action, "set_current_page"))
1323 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), strtol(data, NULL, 10));
1324 else
1325 ign_cmd(type, whole_msg);
1328 static void
1329 update_progress_bar(GObject *obj, const char *action,
1330 const char *data, const char *whole_msg, GType type)
1332 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1334 if (eql(action, "set_text"))
1335 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1336 else if (eql(action, "set_fraction"))
1337 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1338 else
1339 ign_cmd(type, whole_msg);
1342 static void
1343 update_scale(GObject *obj, const char *action,
1344 const char *data, const char *whole_msg, GType type)
1346 if (eql(action, "set_value"))
1347 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
1348 else
1349 ign_cmd(type, whole_msg);
1352 static void
1353 update_spinner(GObject *obj, const char *action,
1354 const char *data, const char *whole_msg, GType type)
1356 GtkSpinner *spinner = GTK_SPINNER(obj);
1358 (void) data;
1359 if (eql(action, "start"))
1360 gtk_spinner_start(spinner);
1361 else if (eql(action, "stop"))
1362 gtk_spinner_stop(spinner);
1363 else
1364 ign_cmd(type, whole_msg);
1367 static void
1368 update_statusbar(GObject *obj, const char *action,
1369 const char *data, const char *whole_msg, GType type)
1371 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
1372 char *ctx_msg, *msg;
1373 size_t ctx_len;
1375 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
1376 OOM_ABORT;
1377 strcpy(ctx_msg, data);
1378 ctx_len = strcspn(ctx_msg, WHITESPACE);
1379 if (ctx_len > 0) {
1380 ctx_msg[ctx_len] = '\0';
1381 msg = ctx_msg + ctx_len + 1;
1382 } else
1383 msg = ctx_msg + strlen(ctx_msg);
1384 if (eql(action, "push"))
1385 gtk_statusbar_push(statusbar,
1386 gtk_statusbar_get_context_id(statusbar, "0"),
1387 data);
1388 else if (eql(action, "push_id"))
1389 gtk_statusbar_push(statusbar,
1390 gtk_statusbar_get_context_id(statusbar, ctx_msg),
1391 msg);
1392 else if (eql(action, "pop"))
1393 gtk_statusbar_pop(statusbar,
1394 gtk_statusbar_get_context_id(statusbar, "0"));
1395 else if (eql(action, "pop_id"))
1396 gtk_statusbar_pop(statusbar,
1397 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1398 else if (eql(action, "remove_all"))
1399 gtk_statusbar_remove_all(statusbar,
1400 gtk_statusbar_get_context_id(statusbar, "0"));
1401 else if (eql(action, "remove_all_id"))
1402 gtk_statusbar_remove_all(statusbar,
1403 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1404 else
1405 ign_cmd(type, whole_msg);
1406 free(ctx_msg);
1409 static void
1410 update_switch(GObject *obj, const char *action,
1411 const char *data, const char *whole_msg, GType type)
1413 if (eql(action, "set_active"))
1414 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
1415 else
1416 ign_cmd(type, whole_msg);
1419 static void
1420 update_text_view(GObject *obj, const char *action,
1421 const char *data, const char *whole_msg, GType type)
1423 GtkTextView *view = GTK_TEXT_VIEW(obj);
1424 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1425 GtkTextIter a, b;
1427 if (eql(action, "set_text"))
1428 gtk_text_buffer_set_text(textbuf, data, -1);
1429 else if (eql(action, "delete")) {
1430 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1431 gtk_text_buffer_delete(textbuf, &a, &b);
1432 } else if (eql(action, "insert_at_cursor"))
1433 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1434 else if (eql(action, "place_cursor")) {
1435 if (eql(data, "end"))
1436 gtk_text_buffer_get_end_iter(textbuf, &a);
1437 else /* numeric offset */
1438 gtk_text_buffer_get_iter_at_offset(textbuf, &a,
1439 strtol(data, NULL, 10));
1440 gtk_text_buffer_place_cursor(textbuf, &a);
1441 } else if (eql(action, "place_cursor_at_line")) {
1442 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1443 gtk_text_buffer_place_cursor(textbuf, &a);
1444 } else if (eql(action, "scroll_to_cursor"))
1445 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
1446 0., 0, 0., 0.);
1447 else if (eql(action, "save") && data != NULL &&
1448 (save = fopen(data, "w")) != NULL) {
1449 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1450 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
1451 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
1452 fclose(save);
1453 } else
1454 ign_cmd(type, whole_msg);
1457 static void
1458 update_toggle_button(GObject *obj, const char *action,
1459 const char *data, const char *whole_msg, GType type)
1461 if (eql(action, "set_label"))
1462 gtk_button_set_label(GTK_BUTTON(obj), data);
1463 else if (eql(action, "set_active"))
1464 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
1465 else
1466 ign_cmd(type, whole_msg);
1470 * Check if s is a valid string representation of a GtkTreePath
1472 static bool
1473 is_path_string(char *s)
1475 return s != NULL &&
1476 strlen(s) == strspn(s, ":0123456789") &&
1477 strstr(s, "::") == NULL &&
1478 strcspn(s, ":") > 0;
1481 static void
1482 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
1483 GtkTreeIter *parent, GtkTreeIter *sibling)
1485 if (GTK_IS_TREE_STORE(model))
1486 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
1487 iter, parent, sibling);
1488 else if (GTK_IS_LIST_STORE(model))
1489 gtk_list_store_insert_before(GTK_LIST_STORE(model),
1490 iter, sibling);
1491 else
1492 ABORT;
1495 static void
1496 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
1497 GtkTreeIter *parent, GtkTreeIter *sibling)
1499 if (GTK_IS_TREE_STORE(model))
1500 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
1501 iter, parent, sibling);
1502 else if (GTK_IS_LIST_STORE(model))
1503 gtk_list_store_insert_after(GTK_LIST_STORE(model),
1504 iter, sibling);
1505 else
1506 ABORT;
1509 static void
1510 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
1511 GtkTreeIter *position)
1513 if (GTK_IS_TREE_STORE(model))
1514 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
1515 else if (GTK_IS_LIST_STORE(model))
1516 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
1517 else
1518 ABORT;
1521 static void
1522 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
1524 if (GTK_IS_TREE_STORE(model))
1525 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
1526 else if (GTK_IS_LIST_STORE(model))
1527 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
1528 else
1529 ABORT;
1532 static void
1533 tree_model_clear(GtkTreeModel *model)
1535 if (GTK_IS_TREE_STORE(model))
1536 gtk_tree_store_clear(GTK_TREE_STORE(model));
1537 else if (GTK_IS_LIST_STORE(model))
1538 gtk_list_store_clear(GTK_LIST_STORE(model));
1539 else
1540 ABORT;
1543 static void
1544 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
1546 va_list ap;
1548 va_start(ap, iter);
1549 if (GTK_IS_TREE_STORE(model))
1550 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
1551 else if (GTK_IS_LIST_STORE(model))
1552 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
1553 else
1554 ABORT;
1555 va_end(ap);
1559 * Create an empty row at path if it doesn't yet exist. Create older
1560 * siblings and parents as necessary.
1562 static void
1563 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
1565 GtkTreePath *path_1; /* path's predecessor */
1566 GtkTreeIter iter_1; /* iter's predecessor */
1568 if (gtk_tree_model_get_iter(model, iter, path))
1569 return;
1570 path_1 = gtk_tree_path_copy(path);
1571 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
1572 create_subtree(model, path_1, iter);
1573 iter_1 = *iter;
1574 tree_model_insert_after(model, iter, NULL, &iter_1);
1575 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
1576 create_subtree(model, path_1, iter);
1577 if (gtk_tree_path_get_depth(path_1) == 0)
1578 /* first toplevel row */
1579 tree_model_insert_after(model, iter, NULL, NULL);
1580 else { /* first row in a lower level */
1581 iter_1 = *iter;
1582 tree_model_insert_after(model, iter, &iter_1, NULL);
1584 } /* neither prev nor up mean we're at the root of an empty tree */
1585 gtk_tree_path_free(path_1);
1588 static bool
1589 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
1590 const char *path_s, int col, const char *new_text)
1592 GType col_type = gtk_tree_model_get_column_type(model, col);
1593 long long int n;
1594 double d;
1595 GtkTreePath *path;
1596 char *endptr;
1597 bool ok = false;
1599 path = gtk_tree_path_new_from_string(path_s);
1600 create_subtree(model, path, iter);
1601 gtk_tree_path_free(path);
1602 switch (col_type) {
1603 case G_TYPE_BOOLEAN:
1604 case G_TYPE_INT:
1605 case G_TYPE_LONG:
1606 case G_TYPE_INT64:
1607 case G_TYPE_UINT:
1608 case G_TYPE_ULONG:
1609 case G_TYPE_UINT64:
1610 errno = 0;
1611 endptr = NULL;
1612 n = strtoll(new_text, &endptr, 10);
1613 if (!errno && endptr != new_text) {
1614 tree_model_set(model, iter, col, n, -1);
1615 ok = true;
1617 break;
1618 case G_TYPE_FLOAT:
1619 case G_TYPE_DOUBLE:
1620 errno = 0;
1621 endptr = NULL;
1622 d = strtod(new_text, &endptr);
1623 if (!errno && endptr != new_text) {
1624 tree_model_set(model, iter, col, d, -1);
1625 ok = true;
1627 break;
1628 case G_TYPE_STRING:
1629 tree_model_set(model, iter, col, new_text, -1);
1630 ok = true;
1631 break;
1632 default:
1633 fprintf(stderr, "column %d: %s not implemented\n",
1634 col, g_type_name(col_type));
1635 ok = true;
1636 break;
1638 return ok;
1641 static void
1642 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
1644 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
1645 /* is just fine and this function need not exist. */
1646 if (path == NULL)
1647 path = gtk_tree_path_new();
1648 gtk_tree_view_set_cursor(view, path, col, false);
1651 static void
1652 update_tree_view(GObject *obj, const char *action,
1653 const char *data, const char *whole_msg, GType type)
1655 GtkTreeView *view = GTK_TREE_VIEW(obj);
1656 GtkTreeModel *model = gtk_tree_view_get_model(view);
1657 GtkTreeIter iter0, iter1;
1658 GtkTreePath *path = NULL;
1659 bool iter0_valid, iter1_valid;
1660 char *tokens, *arg0, *arg1, *arg2;
1661 int col = -1; /* invalid column number */
1663 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
1665 fprintf(stderr, "missing model/");
1666 ign_cmd(type, whole_msg);
1667 return;
1669 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1670 OOM_ABORT;
1671 strcpy(tokens, data);
1672 arg0 = strtok(tokens, WHITESPACE);
1673 arg1 = strtok(NULL, WHITESPACE);
1674 arg2 = strtok(NULL, "");
1675 iter0_valid = is_path_string(arg0) &&
1676 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
1677 iter1_valid = is_path_string(arg1) &&
1678 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
1679 if (is_path_string(arg1))
1680 col = strtol(arg1, NULL, 10);
1681 if (eql(action, "set")
1682 && col > -1 && col < gtk_tree_model_get_n_columns(model) &&
1683 is_path_string(arg0)) {
1684 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
1685 ign_cmd(type, whole_msg);
1686 } else if (eql(action, "scroll") && iter0_valid && iter1_valid) {
1687 path = gtk_tree_path_new_from_string(arg0);
1688 gtk_tree_view_scroll_to_cell (view,
1689 path,
1690 gtk_tree_view_get_column(view, col),
1691 0, 0., 0.);
1692 } else if (eql(action, "expand") && iter0_valid) {
1693 path = gtk_tree_path_new_from_string(arg0);
1694 gtk_tree_view_expand_row(view, path, false);
1695 } else if (eql(action, "expand_all") && iter0_valid) {
1696 path = gtk_tree_path_new_from_string(arg0);
1697 gtk_tree_view_expand_row(view, path, true);
1698 } else if (eql(action, "expand_all") && arg0 == NULL)
1699 gtk_tree_view_expand_all(view);
1700 else if (eql(action, "collapse") && iter0_valid) {
1701 path = gtk_tree_path_new_from_string(arg0);
1702 gtk_tree_view_collapse_row(view, path);
1703 } else if (eql(action, "collapse") && arg0 == NULL)
1704 gtk_tree_view_collapse_all(view);
1705 else if (eql(action, "set_cursor") && iter0_valid) {
1706 path = gtk_tree_path_new_from_string(arg0);
1707 tree_view_set_cursor(view, path, NULL);
1708 } else if (eql(action, "set_cursor") && arg0 == NULL) {
1709 tree_view_set_cursor(view, NULL, NULL);
1710 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1711 } else if (eql(action, "insert_row") && eql(arg0, "end"))
1712 tree_model_insert_before(model, &iter1, NULL, NULL);
1713 else if (eql(action, "insert_row") && iter0_valid && eql(arg1, "as_child"))
1714 tree_model_insert_after(model, &iter1, &iter0, NULL);
1715 else if (eql(action, "insert_row") && iter0_valid)
1716 tree_model_insert_before(model, &iter1, NULL, &iter0);
1717 else if (eql(action, "move_row") && iter0_valid && eql(arg1, "end"))
1718 tree_model_move_before(model, &iter0, NULL);
1719 else if (eql(action, "move_row") && iter0_valid && iter1_valid)
1720 tree_model_move_before(model, &iter0, &iter1);
1721 else if (eql(action, "remove_row") && iter0_valid)
1722 tree_model_remove(model, &iter0);
1723 else if (eql(action, "clear") && arg0 == NULL) {
1724 tree_view_set_cursor(view, NULL, NULL);
1725 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1726 tree_model_clear(model);
1727 } else if (eql(action, "save") && arg0 != NULL &&
1728 (save = fopen(arg0, "w")) != NULL) {
1729 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
1730 fclose(save);
1731 } else
1732 ign_cmd(type, whole_msg);
1733 free(tokens);
1734 gtk_tree_path_free(path);
1737 static void
1738 update_socket(GObject *obj, const char *action,
1739 const char *data, const char *whole_msg, GType type)
1741 GtkSocket *socket = GTK_SOCKET(obj);
1742 Window id;
1743 char str[BUFLEN];
1745 (void) data;
1746 if (eql(action, "id")) {
1747 id = gtk_socket_get_id(socket);
1748 snprintf(str, BUFLEN, "%lu", id);
1749 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1750 } else
1751 ign_cmd(type, whole_msg);
1754 static void
1755 update_window(GObject *obj, const char *action,
1756 const char *data, const char *whole_msg, GType type)
1758 if (!update_class_window(obj, action, data, whole_msg, type))
1759 ign_cmd(type, whole_msg);
1762 static void
1763 update_sensitivity(GObject *obj, const char *action,
1764 const char *data, const char *whole_msg, GType type)
1766 (void) action;
1767 (void) whole_msg;
1768 (void) type;
1769 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1772 static void
1773 update_visibility(GObject *obj, const char *action,
1774 const char *data, const char *whole_msg, GType type)
1776 (void) action;
1777 (void) whole_msg;
1778 (void) type;
1779 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
1782 static void
1783 update_focus(GObject *obj, const char *action,
1784 const char *data, const char *whole_msg, GType type)
1786 (void) action;
1787 (void) data;
1788 if (gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1789 gtk_widget_grab_focus(GTK_WIDGET(obj));
1790 else
1791 ign_cmd(type, whole_msg);
1794 static void
1795 update_size_request(GObject *obj, const char *action,
1796 const char *data, const char *whole_msg, GType type)
1798 int x, y;
1800 (void) action;
1801 (void) whole_msg;
1802 (void) type;
1803 if (sscanf(data, "%d %d", &x, &y) == 2)
1804 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
1805 else
1806 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
1809 static void
1810 update_tooltip_text(GObject *obj, const char *action,
1811 const char *data, const char *whole_msg, GType type)
1813 (void) action;
1814 (void) whole_msg;
1815 (void) type;
1816 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
1819 static void
1820 fake_ui_activity(GObject *obj, const char *action,
1821 const char *data, const char *whole_msg, GType type)
1823 (void) action;
1824 (void) data;
1825 if (!GTK_IS_WIDGET(obj))
1826 ign_cmd(type, whole_msg);
1827 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
1828 cb(GTK_BUILDABLE(obj), "text");
1829 else if (GTK_IS_SCALE(obj))
1830 cb(GTK_BUILDABLE(obj), "value");
1831 else if (GTK_IS_CALENDAR(obj))
1832 cb(GTK_BUILDABLE(obj), "clicked");
1833 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
1834 cb(GTK_BUILDABLE(obj), "file");
1835 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
1836 ign_cmd(type, whole_msg);
1839 static void
1840 main_quit(GObject *obj, const char *action,
1841 const char *data, const char *whole_msg, GType type)
1843 (void) obj;
1844 (void) action;
1845 (void) data;
1846 (void) whole_msg;
1847 (void) type;
1849 gtk_main_quit();
1852 static void
1853 complain(GObject *obj, const char *action,
1854 const char *data, const char *whole_msg, GType type)
1856 (void) obj;
1857 (void) action;
1858 (void) data;
1860 ign_cmd(type, whole_msg);
1864 * Parse command pointed to by ud, and act on ui accordingly
1866 static void
1867 update_ui(struct ui_data *ud)
1869 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
1872 static void
1873 free_at(void **mem)
1875 free(*mem);
1879 * Keep track of loading files to avoid recursive loading of the same
1880 * file. If filename = NULL, forget the most recently remembered file.
1882 static bool
1883 remember_loading_file(char *filename)
1885 static char *filenames[BUFLEN];
1886 static size_t latest = 0;
1887 size_t i;
1889 if (filename == NULL) { /* pop */
1890 if (latest < 1)
1891 ABORT;
1892 latest--;
1893 return false;
1894 } else { /* push */
1895 for (i = 1; i <= latest; i++)
1896 if (eql(filename, filenames[i]))
1897 return false;
1898 if (latest > BUFLEN -2)
1899 return false;
1900 filenames[++latest] = filename;
1901 return true;
1906 * Parse command pointed to by ud, and act on ui accordingly; post
1907 * semaphore ud.msg_digested if done. Runs once per command inside
1908 * gtk_main_loop()
1910 static gboolean
1911 update_ui_in(struct ui_data *ud)
1913 update_ui(ud);
1914 sem_post(&ud->msg_digested);
1915 return G_SOURCE_REMOVE;
1919 * Microseconds of processor time used since start
1921 static long double
1922 us_since(clock_t start)
1924 return 1e6L * (clock() - start) / CLOCKS_PER_SEC;
1928 * Write log file.
1930 static void
1931 log_msg(char *msg)
1933 static clock_t start;
1934 static char *old_msg;
1936 if (log_out == NULL) /* no logging */
1937 return;
1938 if (msg == NULL && old_msg == NULL)
1939 fprintf(log_out,
1940 "##########\t##### (New Pipeglade session) #####\n");
1941 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
1942 fprintf(log_out,
1943 "%10.Lf\t%s\n", us_since(start), old_msg);
1944 free(old_msg);
1945 old_msg = NULL;
1946 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
1947 fprintf(log_out,
1948 "%10.Lf\t### (Idle) ###\n", us_since(start));
1949 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
1950 OOM_ABORT;
1951 strcpy(old_msg, msg);
1952 } else
1953 ABORT;
1954 start = clock();
1958 * Read lines from stream cmd and perform appropriate actions on the
1959 * GUI
1961 static void *
1962 digest_msg(FILE *cmd)
1964 struct ui_data ud;
1965 FILE *load; /* restoring user data */
1966 char *name;
1967 static int recursion = -1; /* > 0 means this is a recursive call */
1969 recursion++;
1970 sem_init(&ud.msg_digested, 0, 0);
1971 for (;;) {
1972 char first_char = '\0';
1973 size_t msg_size = 32;
1974 int name_start = 0, name_end = 0;
1975 int action_start = 0, action_end = 0;
1976 int data_start;
1978 ud.type = G_TYPE_INVALID;
1979 if (feof(cmd))
1980 break;
1981 if ((ud.msg = malloc(msg_size)) == NULL)
1982 OOM_ABORT;
1983 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
1984 pthread_testcancel();
1985 if (recursion == 0)
1986 log_msg(NULL);
1987 read_buf(cmd, &ud.msg, &msg_size);
1988 if (recursion == 0)
1989 log_msg(ud.msg);
1990 data_start = strlen(ud.msg);
1991 if ((ud.msg_tokens = malloc(strlen(ud.msg) + 1)) == NULL)
1992 OOM_ABORT;
1993 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg_tokens);
1994 strcpy(ud.msg_tokens, ud.msg);
1995 sscanf(ud.msg, " %c", &first_char);
1996 if (strlen(ud.msg) == 0 || first_char == '#') /* comment */
1997 goto cleanup;
1998 sscanf(ud.msg_tokens,
1999 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2000 &name_start, &name_end, &action_start, &action_end, &data_start);
2001 ud.msg_tokens[name_end] = ud.msg_tokens[action_end] = '\0';
2002 name = ud.msg_tokens + name_start;
2003 ud.action = ud.msg_tokens + action_start;
2004 if (eql(ud.action, "main_quit")) {
2005 ud.fn = main_quit;
2006 goto exec;
2008 ud.data = ud.msg_tokens + data_start;
2009 if (eql(ud.action, "load") && strlen(ud.data) > 0 &&
2010 (load = fopen(ud.data, "r")) != NULL &&
2011 remember_loading_file(ud.data)) {
2012 digest_msg(load);
2013 fclose(load);
2014 remember_loading_file(NULL);
2015 goto cleanup;
2017 if ((ud.obj = (gtk_builder_get_object(builder, name))) == NULL) {
2018 ud.fn = complain;
2019 goto exec;
2021 ud.type = G_TYPE_FROM_INSTANCE(ud.obj);
2022 if (eql(ud.action, "force"))
2023 ud.fn = fake_ui_activity;
2024 else if (eql(ud.action, "set_sensitive"))
2025 ud.fn = update_sensitivity;
2026 else if (eql(ud.action, "set_visible"))
2027 ud.fn = update_visibility;
2028 else if (eql(ud.action, "set_size_request"))
2029 ud.fn = update_size_request;
2030 else if (eql(ud.action, "set_tooltip_text"))
2031 ud.fn = update_tooltip_text;
2032 else if (eql(ud.action, "grab_focus"))
2033 ud.fn = update_focus;
2034 else if (eql(ud.action, "style")) {
2035 ud.action = name;
2036 ud.fn = update_widget_style;
2037 } else if (ud.type == GTK_TYPE_TREE_VIEW)
2038 ud.fn = update_tree_view;
2039 else if (ud.type == GTK_TYPE_DRAWING_AREA)
2040 ud.fn = update_drawing_area;
2041 else if (ud.type == GTK_TYPE_LABEL)
2042 ud.fn = update_label;
2043 else if (ud.type == GTK_TYPE_IMAGE)
2044 ud.fn = update_image;
2045 else if (ud.type == GTK_TYPE_TEXT_VIEW)
2046 ud.fn = update_text_view;
2047 else if (ud.type == GTK_TYPE_NOTEBOOK)
2048 ud.fn = update_notebook;
2049 else if (ud.type == GTK_TYPE_EXPANDER)
2050 ud.fn = update_expander;
2051 else if (ud.type == GTK_TYPE_FRAME)
2052 ud.fn = update_frame;
2053 else if (ud.type == GTK_TYPE_SCROLLED_WINDOW)
2054 ud.fn = update_scrolled_window;
2055 else if (ud.type == GTK_TYPE_BUTTON)
2056 ud.fn = update_button;
2057 else if (ud.type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2058 ud.fn = update_file_chooser_dialog;
2059 else if (ud.type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2060 ud.fn = update_file_chooser_button;
2061 else if (ud.type == GTK_TYPE_COLOR_BUTTON)
2062 ud.fn = update_color_button;
2063 else if (ud.type == GTK_TYPE_FONT_BUTTON)
2064 ud.fn = update_font_button;
2065 else if (ud.type == GTK_TYPE_PRINT_UNIX_DIALOG)
2066 ud.fn = update_print_dialog;
2067 else if (ud.type == GTK_TYPE_SWITCH)
2068 ud.fn = update_switch;
2069 else if (ud.type == GTK_TYPE_TOGGLE_BUTTON || ud.type == GTK_TYPE_RADIO_BUTTON || ud.type == GTK_TYPE_CHECK_BUTTON)
2070 ud.fn = update_toggle_button;
2071 else if (ud.type == GTK_TYPE_SPIN_BUTTON || ud.type == GTK_TYPE_ENTRY)
2072 ud.fn = update_entry;
2073 else if (ud.type == GTK_TYPE_SCALE)
2074 ud.fn = update_scale;
2075 else if (ud.type == GTK_TYPE_PROGRESS_BAR)
2076 ud.fn = update_progress_bar;
2077 else if (ud.type == GTK_TYPE_SPINNER)
2078 ud.fn = update_spinner;
2079 else if (ud.type == GTK_TYPE_COMBO_BOX_TEXT)
2080 ud.fn = update_combo_box_text;
2081 else if (ud.type == GTK_TYPE_STATUSBAR)
2082 ud.fn = update_statusbar;
2083 else if (ud.type == GTK_TYPE_CALENDAR)
2084 ud.fn = update_calendar;
2085 else if (ud.type == GTK_TYPE_SOCKET)
2086 ud.fn = update_socket;
2087 else if (ud.type == GTK_TYPE_WINDOW || ud.type == GTK_TYPE_DIALOG)
2088 ud.fn = update_window;
2089 else
2090 ud.fn = complain;
2091 exec:
2092 pthread_testcancel();
2093 gdk_threads_add_timeout(1, (GSourceFunc)update_ui_in, &ud);
2094 sem_wait(&ud.msg_digested);
2095 cleanup:
2096 pthread_cleanup_pop(1); /* free ud.msg_tokens */
2097 pthread_cleanup_pop(1); /* free ud.msg */
2099 recursion--;
2100 return NULL;
2104 * Create a fifo if necessary, and open it. Give up if the file
2105 * exists but is not a fifo
2107 static FILE *
2108 fifo(const char *name, const char *mode)
2110 struct stat sb;
2111 int fd;
2112 FILE *s = NULL;
2113 int bufmode;
2115 if (name != NULL) {
2116 stat(name, &sb);
2117 if (S_ISFIFO(sb.st_mode)) {
2118 if (chmod(name, 0600) != 0)
2119 bye(EXIT_FAILURE, stderr,
2120 "using pre-existing fifo %s: %s\n",
2121 name, strerror(errno));
2122 } else if (mkfifo(name, 0600) != 0)
2123 bye(EXIT_FAILURE, stderr,
2124 "making fifo %s: %s\n", name, strerror(errno));
2126 switch (mode[0]) {
2127 case 'r':
2128 bufmode = _IONBF;
2129 if (name == NULL)
2130 s = stdin;
2131 else {
2132 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
2133 bye(EXIT_FAILURE, stderr,
2134 "opening fifo %s (%s): %s\n",
2135 name, mode, strerror(errno));
2136 s = fdopen(fd, "r");
2138 break;
2139 case 'w':
2140 bufmode = _IOLBF;
2141 if (name == NULL)
2142 s = stdout;
2143 else
2144 s = fopen(name, "w+");
2145 break;
2146 default:
2147 ABORT;
2148 break;
2150 if (s == NULL)
2151 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
2152 name, mode, strerror(errno));
2153 else
2154 setvbuf(s, NULL, bufmode, 0);
2155 return s;
2159 * Create a log file if necessary, and open it. A name of "-"
2160 * requests use of stderr.
2162 static FILE *
2163 log_file(const char *name)
2165 FILE *s = NULL;
2167 if (name) {
2168 if (eql(name, "-"))
2169 s = stderr;
2170 else if ((s = fopen(name, "a")) == NULL)
2171 bye(EXIT_FAILURE, stderr,
2172 "opening log file %s: %s\n",
2173 name, strerror(errno));
2175 return s;
2179 * Remove suffix from name; find the object named like this
2181 static GObject *
2182 obj_sans_suffix(const char *suffix, const char *name)
2184 int str_l;
2185 char str[BUFLEN + 1] = {'\0'};
2187 str_l = suffix - name;
2188 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
2189 return gtk_builder_get_object(builder, str);
2193 * Callback that forwards a modification of a tree view cell to the
2194 * underlying model
2196 static void
2197 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2198 const gchar *new_text, gpointer model)
2200 GtkTreeIter iter;
2201 GtkTreeView *view;
2202 void *col;
2204 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2205 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2206 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2207 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2208 new_text);
2209 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2210 GTK_BUILDABLE(view));
2213 static void
2214 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2216 GtkTreeIter iter;
2217 void *col;
2218 bool toggle_state;
2220 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2221 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2222 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2223 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2224 toggle_state? "0" : "1");
2228 * Attach to renderer key "col_number". Associate "col_number" with
2229 * the corresponding column number in the underlying model.
2230 * Due to what looks like a gap in the GTK API, renderer id and column
2231 * number are taken directly from the XML .ui file.
2233 static bool
2234 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2235 int n, GtkCellRenderer **renderer)
2237 xmlDocPtr doc;
2238 xmlXPathContextPtr xpath_ctx;
2239 xmlXPathObjectPtr xpath_obj;
2240 xmlNodeSetPtr nodes;
2241 xmlNodePtr cur;
2242 int i;
2243 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2244 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2245 const char *xpath_id = widget_name(t_col);
2246 char *xpath_base2 = "\"]/child[";
2247 size_t xpath_n_len = 3; /* Big Enough (TM) */
2248 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2249 " or @class=\"GtkCellRendererToggle\"]/";
2250 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2251 " or @name=\"active\"]";
2252 char *xpath_renderer_id = "/@id";
2253 size_t xpath_len;
2254 bool r = false;
2256 if ((doc = xmlParseFile(ui_file)) == NULL)
2257 return false;
2258 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2259 xmlFreeDoc(doc);
2260 return false;
2262 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2263 strlen(xpath_base2) + xpath_n_len +
2264 strlen(xpath_base3))
2265 + 1 /* "|" */
2266 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2267 + 1; /* '\0' */
2268 if ((xpath = malloc(xpath_len)) == NULL) {
2269 xmlFreeDoc(doc);
2270 return false;
2272 snprintf((char *)xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2273 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2274 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2275 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2276 xmlXPathFreeContext(xpath_ctx);
2277 free(xpath);
2278 xmlFreeDoc(doc);
2279 return false;
2281 if ((nodes = xpath_obj->nodesetval) != NULL) {
2282 for (i = 0; i < nodes->nodeNr; ++i) {
2283 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2284 cur = nodes->nodeTab[i];
2285 m_col_s = xmlNodeGetContent(cur);
2286 } else {
2287 cur = nodes->nodeTab[i];
2288 renderer_name = xmlNodeGetContent(cur);
2292 if (renderer_name) {
2293 *renderer = GTK_CELL_RENDERER(
2294 gtk_builder_get_object(builder, (char *)renderer_name));
2295 if (m_col_s) {
2296 g_object_set_data(G_OBJECT(*renderer), "col_number",
2297 GINT_TO_POINTER(strtol((char *)m_col_s,
2298 NULL, 10)));
2299 xmlFree(m_col_s);
2300 r = true;
2302 xmlFree(renderer_name);
2304 xmlXPathFreeObject(xpath_obj);
2305 xmlXPathFreeContext(xpath_ctx);
2306 free(xpath);
2307 xmlFreeDoc(doc);
2308 return r;
2311 static void
2312 connect_widget_signals(gpointer *obj, char *ui_file)
2314 const char *name = NULL;
2315 char *suffix = NULL;
2316 GObject *obj2;
2317 GType type = G_TYPE_INVALID;
2319 type = G_TYPE_FROM_INSTANCE(obj);
2320 if (GTK_IS_BUILDABLE(obj))
2321 name = widget_name(obj);
2322 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2323 gboolean editable = FALSE;
2324 GtkTreeView *view;
2325 GtkTreeModel *model;
2326 GtkCellRenderer *renderer;
2327 int i;
2329 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2330 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2331 model = gtk_tree_view_get_model(view);
2332 for (i = 1;; i++) {
2333 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2334 break;
2335 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2336 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2337 g_object_get(renderer, "editable", &editable, NULL);
2338 if (editable)
2339 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2340 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2341 g_object_get(renderer, "activatable", &editable, NULL);
2342 if (editable)
2343 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2347 else if (type == GTK_TYPE_BUTTON) {
2348 /* Button associated with a GtkTextView. */
2349 if ((suffix = strstr(name, "_send_text")) != NULL &&
2350 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2351 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2352 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2353 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2354 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2355 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2356 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2357 /* Buttons associated with (and part of) a GtkDialog.
2358 * (We shun response ids which could be returned from
2359 * gtk_dialog_run() because that would require the
2360 * user to define those response ids in Glade,
2361 * numerically */
2362 else if ((suffix = strstr(name, "_cancel")) != NULL &&
2363 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2364 if (eql(widget_name(obj2), MAIN_WIN))
2365 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2366 else
2367 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2368 else if ((suffix = strstr(name, "_ok")) != NULL &&
2369 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2370 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2371 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2372 else /* generic button */
2373 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2374 if (eql(widget_name(obj2), MAIN_WIN))
2375 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2376 else
2377 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2378 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2379 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2380 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2381 else /* generic button */
2382 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2383 } else if (GTK_IS_MENU_ITEM(obj))
2384 if ((suffix = strstr(name, "_invoke")) != NULL &&
2385 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2386 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2387 else
2388 g_signal_connect(obj, "activate", G_CALLBACK(cb), "active");
2389 else if (GTK_IS_WINDOW(obj))
2390 if (eql(name, MAIN_WIN))
2391 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2392 else
2393 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2394 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2395 g_signal_connect(obj, "file-set", G_CALLBACK(cb), "file");
2396 else if (type == GTK_TYPE_COLOR_BUTTON)
2397 g_signal_connect(obj, "color-set", G_CALLBACK(cb), "color");
2398 else if (type == GTK_TYPE_FONT_BUTTON)
2399 g_signal_connect(obj, "font-set", G_CALLBACK(cb), "font");
2400 else if (type == GTK_TYPE_SWITCH)
2401 g_signal_connect(obj, "notify::active", G_CALLBACK(cb), NULL);
2402 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2403 g_signal_connect(obj, "toggled", G_CALLBACK(cb), NULL);
2404 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2405 g_signal_connect(obj, "changed", G_CALLBACK(cb), "text");
2406 else if (type == GTK_TYPE_SCALE)
2407 g_signal_connect(obj, "value-changed", G_CALLBACK(cb), "value");
2408 else if (type == GTK_TYPE_CALENDAR) {
2409 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb), "doubleclicked");
2410 g_signal_connect(obj, "day-selected", G_CALLBACK(cb), "clicked");
2411 } else if (type == GTK_TYPE_TREE_SELECTION)
2412 g_signal_connect(obj, "changed", G_CALLBACK(cb), "clicked");
2413 else if (type == GTK_TYPE_SOCKET) {
2414 g_signal_connect(obj, "plug-added", G_CALLBACK(cb), "plug-added");
2415 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_true), "plug-removed");
2416 } else if (type == GTK_TYPE_DRAWING_AREA)
2417 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2418 else if (type == GTK_TYPE_EVENT_BOX) {
2419 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
2420 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
2421 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
2422 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
2423 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
2428 * We keep a style provider with each widget
2430 static void
2431 add_widget_style_provider(gpointer *obj, void *data)
2433 GtkStyleContext *context;
2434 GtkCssProvider *style_provider;
2436 (void) data;
2437 if (!GTK_IS_WIDGET(obj))
2438 return;
2439 style_provider = gtk_css_provider_new();
2440 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2441 gtk_style_context_add_provider(context,
2442 GTK_STYLE_PROVIDER(style_provider),
2443 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2444 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2447 static void
2448 prepare_widgets(char *ui_file)
2450 GSList *objects = NULL;
2452 objects = gtk_builder_get_objects(builder);
2453 g_slist_foreach(objects, (GFunc)connect_widget_signals, ui_file);
2454 g_slist_foreach(objects, (GFunc)add_widget_style_provider, NULL);
2455 g_slist_free(objects);
2459 main(int argc, char *argv[])
2461 char opt;
2462 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
2463 char *log_name = NULL;
2464 char *xid_s = NULL, xid_s2[BUFLEN];
2465 Window xid;
2466 GtkWidget *plug, *body;
2467 pthread_t receiver;
2468 GError *error = NULL;
2469 GObject *main_window = NULL;
2470 FILE *in = NULL; /* command input */
2472 /* Disable runtime GLIB deprecation warnings: */
2473 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2474 out = NULL;
2475 save = NULL;
2476 log_out = NULL;
2477 gtk_init(&argc, &argv);
2478 while ((opt = getopt(argc, argv, "he:i:l:o:u:GV")) != -1) {
2479 switch (opt) {
2480 case 'e': xid_s = optarg; break;
2481 case 'i': in_fifo = optarg; break;
2482 case 'l': log_name = optarg; break;
2483 case 'o': out_fifo = optarg; break;
2484 case 'u': ui_file = optarg; break;
2485 case 'G': bye(EXIT_SUCCESS, stdout,
2486 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
2487 "cairo v%s (running v%s)\n",
2488 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
2489 gtk_get_major_version(), gtk_get_minor_version(),
2490 gtk_get_micro_version(),
2491 CAIRO_VERSION_STRING, cairo_version_string());
2492 break;
2493 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
2494 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
2495 case '?':
2496 default: bye(EXIT_FAILURE, stderr, USAGE); break;
2499 if (argv[optind] != NULL)
2500 bye(EXIT_FAILURE, stderr,
2501 "illegal parameter '%s'\n" USAGE, argv[optind]);
2502 in = fifo(in_fifo, "r");
2503 out = fifo(out_fifo, "w");
2504 if (ui_file == NULL)
2505 ui_file = "pipeglade.ui";
2506 builder = gtk_builder_new();
2507 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0)
2508 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
2509 log_out = log_file(log_name);
2510 pthread_create(&receiver, NULL, (void *(*)(void *))digest_msg, in);
2511 main_window = gtk_builder_get_object(builder, MAIN_WIN);
2512 if (!GTK_IS_WINDOW(main_window))
2513 bye(EXIT_FAILURE, stderr,
2514 "no toplevel window named \'" MAIN_WIN "\'\n");
2515 xmlInitParser();
2516 LIBXML_TEST_VERSION;
2517 prepare_widgets(ui_file);
2518 if (xid_s == NULL) /* standalone */
2519 gtk_widget_show(GTK_WIDGET(main_window));
2520 else { /* We're being XEmbedded */
2521 xid = strtoul(xid_s, NULL, 10);
2522 snprintf(xid_s2, BUFLEN, "%lu", xid);
2523 if (!eql(xid_s, xid_s2))
2524 bye(EXIT_FAILURE, stderr,
2525 "%s is not a valid XEmbed socket id\n", xid_s);
2526 body = gtk_bin_get_child(GTK_BIN(main_window));
2527 gtk_container_remove(GTK_CONTAINER(main_window), body);
2528 plug = gtk_plug_new(xid);
2529 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
2530 bye(EXIT_FAILURE, stderr,
2531 "unable to embed into XEmbed socket %s\n", xid_s);
2532 gtk_container_add(GTK_CONTAINER(plug), body);
2533 gtk_widget_show(plug);
2535 gtk_main();
2536 if (in != stdin) {
2537 fclose(in);
2538 unlink(in_fifo);
2540 if (out != stdout) {
2541 fclose(out);
2542 unlink(out_fifo);
2544 pthread_cancel(receiver);
2545 pthread_join(receiver, NULL);
2546 xmlCleanupParser();
2547 exit(EXIT_SUCCESS);