Minor manual page edit
[pipeglade.git] / pipeglade.c
blob1b5d20c2a95edfc4bcd1c57c6c5ea73963823693
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 <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.7.0"
45 #define BUFLEN 256
46 #define WHITESPACE " \t\n"
47 #define MAIN_WIN "main"
48 #define USAGE \
49 "usage: pipeglade [[-i in-fifo] " \
50 "[-o out-fifo] " \
51 "[-b] " \
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 */
82 * ============================================================
83 * Helper functions
84 * ============================================================
88 * Check if s1 and s2 are equal strings
90 static bool
91 eql(const char *s1, const char *s2)
93 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
97 * Print a formatted message to stream s and give up with status
99 static void
100 bye(int status, FILE *s, const char *fmt, ...)
102 va_list ap;
104 va_start(ap, fmt);
105 vfprintf(s, fmt, ap);
106 va_end(ap);
107 exit(status);
111 * Print a warning about a malformed command to stderr
113 static void
114 ign_cmd(GType type, const char *msg)
116 const char *name, *pad = " ";
118 if (type == G_TYPE_INVALID) {
119 name = "";
120 pad = "";
122 else
123 name = g_type_name(type);
124 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
128 * Create a fifo if necessary, and open it. Give up if the file
129 * exists but is not a fifo
131 static FILE *
132 fifo(const char *name, const char *mode)
134 struct stat sb;
135 int fd;
136 FILE *s = NULL;
137 int bufmode;
139 if (name != NULL) {
140 stat(name, &sb);
141 if (S_ISFIFO(sb.st_mode)) {
142 if (chmod(name, 0600) != 0)
143 bye(EXIT_FAILURE, stderr,
144 "using pre-existing fifo %s: %s\n",
145 name, strerror(errno));
146 } else if (mkfifo(name, 0600) != 0)
147 bye(EXIT_FAILURE, stderr,
148 "making fifo %s: %s\n", name, strerror(errno));
150 switch (mode[0]) {
151 case 'r':
152 bufmode = _IONBF;
153 if (name == NULL)
154 s = stdin;
155 else {
156 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
157 bye(EXIT_FAILURE, stderr,
158 "opening fifo %s (%s): %s\n",
159 name, mode, strerror(errno));
160 s = fdopen(fd, "r");
162 break;
163 case 'w':
164 bufmode = _IOLBF;
165 if (name == NULL)
166 s = stdout;
167 else
168 s = fopen(name, "w+");
169 break;
170 default:
171 ABORT;
172 break;
174 if (s == NULL)
175 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
176 name, mode, strerror(errno));
177 else
178 setvbuf(s, NULL, bufmode, 0);
179 return s;
183 * Create a log file if necessary, and open it. A name of "-"
184 * requests use of stderr.
186 static FILE *
187 open_log(const char *name)
189 FILE *s = NULL;
191 if (eql(name, "-"))
192 s = stderr;
193 else if (name && (s = fopen(name, "a")) == NULL)
194 bye(EXIT_FAILURE, stderr,
195 "opening log file %s: %s\n", name, strerror(errno));
196 return s;
200 * Microseconds elapsed since start
202 static long int
203 usec_since(struct timespec *start)
205 struct timespec now;
207 clock_gettime(CLOCK_MONOTONIC, &now);
208 return (now.tv_sec - start->tv_sec) * 1e6 +
209 (now.tv_nsec - start->tv_nsec) / 1e3;
213 * Write log file
215 static void
216 log_msg(char *msg)
218 static struct timespec start;
219 static char *old_msg;
221 if (log_out == NULL) /* no logging */
222 return;
223 if (msg == NULL && old_msg == NULL)
224 fprintf(log_out,
225 "##########\t##### (New Pipeglade session) #####\n");
226 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
227 fprintf(log_out,
228 "%10ld\t%s\n", usec_since(&start), old_msg);
229 free(old_msg);
230 old_msg = NULL;
231 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
232 fprintf(log_out,
233 "%10ld\t### (Idle) ###\n", usec_since(&start));
234 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
235 OOM_ABORT;
236 strcpy(old_msg, msg);
237 } else
238 ABORT;
239 clock_gettime(CLOCK_MONOTONIC, &start);
243 * Remove suffix from name; find the object named like this
245 static GObject *
246 obj_sans_suffix(const char *suffix, const char *name)
248 int str_l;
249 char str[BUFLEN + 1] = {'\0'};
251 str_l = suffix - name;
252 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
253 return gtk_builder_get_object(builder, str);
256 static const char *
257 widget_name(GtkBuildable *obj)
259 return gtk_buildable_get_name(obj);
263 * Store a line from stream s into buf, which should have been malloc'd
264 * to bufsize. Enlarge buf and bufsize if necessary.
266 static size_t
267 read_buf(FILE *s, char **buf, size_t *bufsize)
269 size_t i = 0;
270 int c;
271 fd_set rfds;
272 int ifd = fileno(s);
273 bool esc = false;
275 FD_ZERO(&rfds);
276 FD_SET(ifd, &rfds);
277 for (;;) {
278 select(ifd + 1, &rfds, NULL, NULL, NULL);
279 c = getc(s);
280 if (c == '\n' || feof(s))
281 break;
282 if (i >= *bufsize - 1)
283 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
284 OOM_ABORT;
285 if (esc) {
286 esc = false;
287 switch (c) {
288 case 'n': (*buf)[i++] = '\n'; break;
289 case 'r': (*buf)[i++] = '\r'; break;
290 default: (*buf)[i++] = c; break;
292 } else if (c == '\\')
293 esc = true;
294 else
295 (*buf)[i++] = c;
297 (*buf)[i] = '\0';
298 return i;
302 * ============================================================
303 * Receiving feedback from the GUI
304 * ============================================================
307 static void
308 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
310 char *data;
311 const char *w_name = widget_name(obj);
312 fd_set wfds;
313 int ofd = fileno(o);
314 struct timeval timeout = {1, 0};
316 FD_ZERO(&wfds);
317 FD_SET(ofd, &wfds);
318 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
319 fprintf(o, "%s:%s ", w_name, tag);
320 while ((data = va_arg(ap, char *)) != NULL) {
321 size_t i = 0;
322 char c;
324 while ((c = data[i++]) != '\0')
325 if (c == '\\')
326 fprintf(o, "\\\\");
327 else if (c == '\n')
328 fprintf(o, "\\n");
329 else
330 putc(c, o);
332 putc('\n', o);
333 } else
334 fprintf(stderr,
335 "send error; discarding feedback message %s:%s\n",
336 w_name, tag);
340 * Send GUI feedback to global stream "out". The message format is
341 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
342 * last argument must be NULL.
344 static void
345 send_msg(GtkBuildable *obj, const char *tag, ...)
347 va_list ap;
349 va_start(ap, tag);
350 send_msg_to(out, obj, tag, ap);
351 va_end(ap);
355 * Send message from GUI to global stream "save". The message format
356 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
357 * last argument must be NULL.
359 static void
360 save_msg(GtkBuildable *obj, const char *tag, ...)
362 va_list ap;
364 va_start(ap, tag);
365 send_msg_to(save, obj, tag, ap);
366 va_end(ap);
370 * Send message from GUI to global stream "save". The message format
371 * is "<origin>:set <data ...>". The variadic arguments are strings;
372 * last argument must be NULL.
374 static void
375 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
377 va_list ap;
379 va_start(ap, tag);
380 send_msg_to(save, obj, "set", ap);
381 va_end(ap);
385 * Use msg_sender() to send a message describing a particular cell
387 static void
388 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
389 GtkTreeModel *model, const char *path_s,
390 GtkTreeIter *iter, int col, GtkBuildable *obj)
392 GValue value = G_VALUE_INIT;
393 GType col_type;
394 char str[BUFLEN];
396 gtk_tree_model_get_value(model, iter, col, &value);
397 col_type = gtk_tree_model_get_column_type(model, col);
398 switch (col_type) {
399 case G_TYPE_INT:
400 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
401 msg_sender(obj, "gint", path_s, str, NULL);
402 break;
403 case G_TYPE_LONG:
404 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
405 msg_sender(obj, "glong", path_s, str, NULL);
406 break;
407 case G_TYPE_INT64:
408 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
409 msg_sender(obj, "gint64", path_s, str, NULL);
410 break;
411 case G_TYPE_UINT:
412 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
413 msg_sender(obj, "guint", path_s, str, NULL);
414 break;
415 case G_TYPE_ULONG:
416 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
417 msg_sender(obj, "gulong", path_s, str, NULL);
418 break;
419 case G_TYPE_UINT64:
420 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
421 msg_sender(obj, "guint64", path_s, str, NULL);
422 break;
423 case G_TYPE_BOOLEAN:
424 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
425 msg_sender(obj, "gboolean", path_s, str, NULL);
426 break;
427 case G_TYPE_FLOAT:
428 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
429 msg_sender(obj, "gfloat", path_s, str, NULL);
430 break;
431 case G_TYPE_DOUBLE:
432 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
433 msg_sender(obj, "gdouble", path_s, str, NULL);
434 break;
435 case G_TYPE_STRING:
436 snprintf(str, BUFLEN, " %d ", col);
437 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
438 break;
439 default:
440 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
441 break;
443 g_value_unset(&value);
447 * Use msg_sender() to send one message per column for a single row
449 static void
450 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
451 GtkTreeModel *model, char *path_s,
452 GtkTreeIter *iter, GtkBuildable *obj)
454 int col;
456 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
457 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
461 * send_tree_row_msg serves as an argument for
462 * gtk_tree_selection_selected_foreach()
464 static gboolean
465 send_tree_row_msg(GtkTreeModel *model,
466 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
468 char *path_s = gtk_tree_path_to_string(path);
470 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
471 g_free(path_s);
472 return FALSE;
476 * save_tree_row_msg serves as an argument for
477 * gtk_tree_model_foreach().
478 * Send message from GUI to global stream "save".
480 static gboolean
481 save_tree_row_msg(GtkTreeModel *model,
482 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
484 char *path_s = gtk_tree_path_to_string(path);
486 (void) path;
487 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
488 g_free(path_s);
489 return FALSE;
492 static void
493 cb_calendar(GtkBuildable *obj, const char *tag)
495 char str[BUFLEN];
496 unsigned int year = 0, month = 0, day = 0;
498 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
499 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
500 send_msg(obj, tag, str, NULL);
503 static void
504 cb_color_button(GtkBuildable *obj, const char *tag)
506 GdkRGBA color;
508 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
509 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
512 static void
513 cb_editable(GtkBuildable *obj, const char *tag)
515 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
519 * Callback that sends a message about a pointer device button press
520 * in a GtkEventBox
522 static bool
523 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
525 char data[BUFLEN];
527 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
528 e->button.button, e->button.x, e->button.y);
529 send_msg(obj, user_data, data, NULL);
530 return true;
534 * Callback that sends in a message the name of the key pressed when
535 * a GtkEventBox is focused
537 static bool
538 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
540 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
541 return true;
545 * Callback that sends a message about pointer device motion in a
546 * GtkEventBox
548 static bool
549 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
551 char data[BUFLEN];
553 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
554 send_msg(obj, user_data, data, NULL);
555 return true;
559 * Callback that only sends "name:tag" and returns false
561 static bool
562 cb_event_simple(GtkBuildable *obj, GdkEvent *e, const char *tag)
564 (void) e;
565 send_msg(obj, tag, NULL);
566 return false;
569 static void
570 cb_file_chooser_button(GtkBuildable *obj, const char *tag)
572 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
575 static void
576 cb_font_button(GtkBuildable *obj, const char *tag)
578 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
581 static void
582 cb_menu_item(GtkBuildable *obj, const char *tag)
584 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
587 static void
588 cb_range(GtkBuildable *obj, const char *tag)
590 char str[BUFLEN];
592 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
593 send_msg(obj, tag, str, NULL);
597 * Callback that sends user's selection from a file dialog
599 static void
600 cb_send_file_chooser_dialog_selection(gpointer user_data)
602 send_msg(user_data, "file",
603 gtk_file_chooser_get_filename(user_data), NULL);
604 send_msg(user_data, "folder",
605 gtk_file_chooser_get_current_folder(user_data), NULL);
609 * Callback that sends in a message the content of the text buffer
610 * passed in user_data
612 static void
613 cb_send_text(GtkBuildable *obj, gpointer user_data)
615 GtkTextIter a, b;
617 gtk_text_buffer_get_bounds(user_data, &a, &b);
618 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
622 * Callback that sends in a message the highlighted text from the text
623 * buffer which was passed in user_data
625 static void
626 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
628 GtkTextIter a, b;
630 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
631 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
635 * Callback that only sends "name:tag" and returns true
637 static bool
638 cb_simple(GtkBuildable *obj, const char *tag)
640 send_msg(obj, tag, NULL);
641 return true;
644 static void
645 cb_switch(GtkBuildable *obj, void *pspec, void *user_data)
647 (void) pspec;
648 (void) user_data;
649 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
652 static void
653 cb_toggle_button(GtkBuildable *obj, const char *tag)
655 (void) tag;
656 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
659 static void
660 cb_tree_selection(GtkBuildable *obj, const char *tag)
662 GtkTreeView *view;
664 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
665 send_msg(GTK_BUILDABLE(view), tag, NULL);
666 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
667 (GtkTreeSelectionForeachFunc) send_tree_row_msg,
668 view);
672 * ============================================================
673 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
674 * helper functions
675 * ============================================================
679 * The set of supported drawing operations
681 enum cairo_fn {
682 ARC,
683 ARC_NEGATIVE,
684 CLOSE_PATH,
685 CURVE_TO,
686 FILL,
687 FILL_PRESERVE,
688 LINE_TO,
689 MOVE_TO,
690 RECTANGLE,
691 REL_CURVE_TO,
692 REL_LINE_TO,
693 REL_MOVE_TO,
694 REL_MOVE_FOR,
695 SET_DASH,
696 SET_FONT_FACE,
697 SET_FONT_SIZE,
698 SET_LINE_CAP,
699 SET_LINE_JOIN,
700 SET_LINE_WIDTH,
701 SET_SOURCE_RGBA,
702 SHOW_TEXT,
703 STROKE,
704 STROKE_PRESERVE,
708 * Text placement mode for rel_move_for()
710 enum ref_point {
723 * One single element of a drawing
725 struct draw_op {
726 struct draw_op *next;
727 struct draw_op *prev;
728 unsigned int id;
729 enum cairo_fn op;
730 void *op_args;
734 * Argument sets for the various drawing operations
736 struct arc_args {
737 double x;
738 double y;
739 double radius;
740 double angle1;
741 double angle2;
744 struct curve_to_args {
745 double x1;
746 double y1;
747 double x2;
748 double y2;
749 double x3;
750 double y3;
753 struct move_to_args {
754 double x;
755 double y;
758 struct rectangle_args {
759 double x;
760 double y;
761 double width;
762 double height;
765 struct rel_move_for_args {
766 enum ref_point ref;
767 int len;
768 char text[];
771 struct set_dash_args {
772 int num_dashes;
773 double dashes[];
776 struct set_font_face_args {
777 cairo_font_slant_t slant;
778 cairo_font_weight_t weight;
779 char family[];
782 struct set_font_size_args {
783 double size;
786 struct set_line_cap_args {
787 cairo_line_cap_t line_cap;
790 struct set_line_join_args {
791 cairo_line_join_t line_join;
794 struct set_line_width_args {
795 double width;
798 struct set_source_rgba_args {
799 GdkRGBA color;
802 struct show_text_args {
803 int len;
804 char text[];
807 static void
808 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
810 switch (op) {
811 case LINE_TO: {
812 struct move_to_args *args = op_args;
814 cairo_line_to(cr, args->x, args->y);
815 break;
817 case REL_LINE_TO: {
818 struct move_to_args *args = op_args;
820 cairo_rel_line_to(cr, args->x, args->y);
821 break;
823 case MOVE_TO: {
824 struct move_to_args *args = op_args;
826 cairo_move_to(cr, args->x, args->y);
827 break;
829 case REL_MOVE_TO: {
830 struct move_to_args *args = op_args;
832 cairo_rel_move_to(cr, args->x, args->y);
833 break;
835 case ARC: {
836 struct arc_args *args = op_args;
838 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
839 break;
841 case ARC_NEGATIVE: {
842 struct arc_args *args = op_args;
844 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
845 break;
847 case CURVE_TO: {
848 struct curve_to_args *args = op_args;
850 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
851 break;
853 case REL_CURVE_TO: {
854 struct curve_to_args *args = op_args;
856 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
857 break;
859 case RECTANGLE: {
860 struct rectangle_args *args = op_args;
862 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
863 break;
865 case CLOSE_PATH:
866 cairo_close_path(cr);
867 break;
868 case SHOW_TEXT: {
869 struct show_text_args *args = op_args;
871 cairo_show_text(cr, args->text);
872 break;
874 case REL_MOVE_FOR: {
875 struct rel_move_for_args *args = op_args;
876 cairo_text_extents_t e;
877 double dx = 0.0, dy = 0.0;
879 cairo_text_extents(cr, args->text, &e);
880 switch (args->ref) {
881 case C: dx = -e.width / 2; dy = e.height / 2; break;
882 case E: dx = -e.width; dy = e.height / 2; break;
883 case N: dx = -e.width / 2; dy = e.height; break;
884 case NE: dx = -e.width; dy = e.height; break;
885 case NW: dy = e.height; break;
886 case S: dx = -e.width / 2; break;
887 case SE: dx = -e.width; break;
888 case SW: break;
889 case W: dy = e.height / 2; break;
890 default: ABORT; break;
892 cairo_rel_move_to(cr, dx, dy);
893 break;
895 case STROKE:
896 cairo_stroke(cr);
897 break;
898 case STROKE_PRESERVE:
899 cairo_stroke_preserve(cr);
900 break;
901 case FILL:
902 cairo_fill(cr);
903 break;
904 case FILL_PRESERVE:
905 cairo_fill_preserve(cr);
906 break;
907 case SET_DASH: {
908 struct set_dash_args *args = op_args;
910 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
911 break;
913 case SET_FONT_FACE: {
914 struct set_font_face_args *args = op_args;
916 cairo_select_font_face(cr, args->family, args->slant, args->weight);
917 break;
919 case SET_FONT_SIZE: {
920 struct set_font_size_args *args = op_args;
922 cairo_set_font_size(cr, args->size);
923 break;
925 case SET_LINE_CAP: {
926 struct set_line_cap_args *args = op_args;
928 cairo_set_line_cap(cr, args->line_cap);
929 break;
931 case SET_LINE_JOIN: {
932 struct set_line_join_args *args = op_args;
934 cairo_set_line_join(cr, args->line_join);
935 break;
937 case SET_LINE_WIDTH: {
938 struct set_line_width_args *args = op_args;
940 cairo_set_line_width(cr, args->width);
941 break;
943 case SET_SOURCE_RGBA: {
944 struct set_source_rgba_args *args = op_args;
946 gdk_cairo_set_source_rgba(cr, &args->color);
947 break;
949 default:
950 ABORT;
951 break;
956 * Callback that draws on a GtkDrawingArea
958 static gboolean
959 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
961 struct draw_op *op;
963 (void) data;
964 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
965 op != NULL;
966 op = op->next)
967 draw(cr, op->op, op->op_args);
968 return FALSE;
972 * ============================================================
973 * Manipulating the GUI
974 * ============================================================
977 static void
978 update_button(GObject *obj, const char *action,
979 const char *data, const char *whole_msg, GType type)
981 if (eql(action, "set_label"))
982 gtk_button_set_label(GTK_BUTTON(obj), data);
983 else
984 ign_cmd(type, whole_msg);
987 static void
988 update_calendar(GObject *obj, const char *action,
989 const char *data, const char *whole_msg, GType type)
991 GtkCalendar *calendar = GTK_CALENDAR(obj);
992 int year = 0, month = 0, day = 0;
994 if (eql(action, "select_date")) {
995 sscanf(data, "%d-%d-%d", &year, &month, &day);
996 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
997 gtk_calendar_select_month(calendar, --month, year);
998 gtk_calendar_select_day(calendar, day);
999 } else
1000 ign_cmd(type, whole_msg);
1001 } else if (eql(action, "mark_day")) {
1002 day = strtol(data, NULL, 10);
1003 if (day > 0 && day <= 31)
1004 gtk_calendar_mark_day(calendar, day);
1005 else
1006 ign_cmd(type, whole_msg);
1007 } else if (eql(action, "clear_marks"))
1008 gtk_calendar_clear_marks(calendar);
1009 else
1010 ign_cmd(type, whole_msg);
1014 * Common actions for various kinds of window. Return false if
1015 * command is ignored
1017 static bool
1018 update_class_window(GObject *obj, const char *action,
1019 const char *data, const char *whole_msg, GType type)
1021 GtkWindow *window = GTK_WINDOW(obj);
1022 int x, y;
1023 bool result = true;
1025 if (eql(action, "set_title"))
1026 gtk_window_set_title(window, data);
1027 else if (eql(action, "fullscreen"))
1028 gtk_window_fullscreen(window);
1029 else if (eql(action, "unfullscreen"))
1030 gtk_window_unfullscreen(window);
1031 else if (eql(action, "resize")) {
1032 if (sscanf(data, "%d %d", &x, &y) != 2)
1033 gtk_window_get_default_size(window, &x, &y);
1034 gtk_window_resize(window, x, y);
1035 } else if (eql(action, "move")) {
1036 if (sscanf(data, "%d %d", &x, &y) == 2)
1037 gtk_window_move(window, x, y);
1038 else
1039 ign_cmd(type, whole_msg);
1040 } else
1041 result = false;
1042 return result;
1045 static void
1046 update_color_button(GObject *obj, const char *action,
1047 const char *data, const char *whole_msg, GType type)
1049 GdkRGBA color;
1051 if (eql(action, "set_color")) {
1052 gdk_rgba_parse(&color, data);
1053 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1054 } else
1055 ign_cmd(type, whole_msg);
1058 static void
1059 update_combo_box_text(GObject *obj, const char *action,
1060 const char *data, const char *whole_msg, GType type)
1062 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1063 char data1[strlen(data) + 1];
1065 strcpy(data1, data);
1066 if (eql(action, "prepend_text"))
1067 gtk_combo_box_text_prepend_text(combobox, data1);
1068 else if (eql(action, "append_text"))
1069 gtk_combo_box_text_append_text(combobox, data1);
1070 else if (eql(action, "remove"))
1071 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1072 else if (eql(action, "insert_text")) {
1073 char *position = strtok(data1, WHITESPACE);
1074 char *text = strtok(NULL, WHITESPACE);
1075 gtk_combo_box_text_insert_text(combobox,
1076 strtol(position, NULL, 10), text);
1077 } else
1078 ign_cmd(type, whole_msg);
1082 * Maintaining a list of drawing operations. It is the responsibility
1083 * of cb_draw() to actually draw them. update_drawing_area() needs a
1084 * few helper functions.
1088 * Fill structure *op with the drawing operation according to action
1089 * and with the appropriate set of arguments
1091 static bool
1092 set_draw_op(struct draw_op *op, const char *action, const char *data)
1094 if (eql(action, "line_to")) {
1095 struct move_to_args *args;
1097 if ((args = malloc(sizeof(*args))) == NULL)
1098 OOM_ABORT;
1099 op->op = LINE_TO;
1100 op->op_args = args;
1101 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1102 return false;
1103 } else if (eql(action, "rel_line_to")) {
1104 struct move_to_args *args;
1106 if ((args = malloc(sizeof(*args))) == NULL)
1107 OOM_ABORT;
1108 op->op = REL_LINE_TO;
1109 op->op_args = args;
1110 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1111 return false;
1112 } else if (eql(action, "move_to")) {
1113 struct move_to_args *args;
1115 if ((args = malloc(sizeof(*args))) == NULL)
1116 OOM_ABORT;
1117 op->op = MOVE_TO;
1118 op->op_args = args;
1119 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1120 return false;
1121 } else if (eql(action, "rel_move_to")) {
1122 struct move_to_args *args;
1124 if ((args = malloc(sizeof(*args))) == NULL)
1125 OOM_ABORT;
1126 op->op = REL_MOVE_TO;
1127 op->op_args = args;
1128 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1129 return false;
1130 } else if (eql(action, "arc")) {
1131 struct arc_args *args;
1132 double deg1, deg2;
1134 if ((args = malloc(sizeof(*args))) == NULL)
1135 OOM_ABORT;
1136 op->op = ARC;
1137 op->op_args = args;
1138 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id,
1139 &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
1140 return false;
1141 args->angle1 = deg1 * (M_PI / 180.);
1142 args->angle2 = deg2 * (M_PI / 180.);
1143 } else if (eql(action, "arc_negative")) {
1144 struct arc_args *args;
1145 double deg1, deg2;
1147 if ((args = malloc(sizeof(*args))) == NULL)
1148 OOM_ABORT;
1149 op->op = ARC_NEGATIVE;
1150 op->op_args = args;
1151 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id,
1152 &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
1153 return false;
1154 args->angle1 = deg1 * (M_PI / 180.);
1155 args->angle2 = deg2 * (M_PI / 180.);
1156 } else if (eql(action, "curve_to")) {
1157 struct curve_to_args *args;
1159 if ((args = malloc(sizeof(*args))) == NULL)
1160 OOM_ABORT;
1161 op->op = CURVE_TO;
1162 op->op_args = args;
1163 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id,
1164 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
1165 return false;
1166 } else if (eql(action, "rel_curve_to")) {
1167 struct curve_to_args *args;
1169 if ((args = malloc(sizeof(*args))) == NULL)
1170 OOM_ABORT;
1171 op->op = REL_CURVE_TO;
1172 op->op_args = args;
1173 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id,
1174 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
1175 return false;
1176 } else if (eql(action, "rectangle")) {
1177 struct rectangle_args *args;
1179 if ((args = malloc(sizeof(*args))) == NULL)
1180 OOM_ABORT;
1181 op->op = RECTANGLE;
1182 op->op_args = args;
1183 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id,
1184 &args->x, &args->y, &args->width, &args->height) != 5)
1185 return false;
1186 } else if (eql(action, "close_path")) {
1187 op->op = CLOSE_PATH;
1188 if (sscanf(data, "%u", &op->id) != 1)
1189 return false;
1190 op->op_args = NULL;
1191 } else if (eql(action, "show_text")) {
1192 struct show_text_args *args;
1193 int start, len;
1195 if (sscanf(data, "%u %n", &op->id, &start) < 1)
1196 return false;
1197 len = strlen(data + start) + 1;
1198 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1199 OOM_ABORT;
1200 op->op = SHOW_TEXT;
1201 op->op_args = args;
1202 args->len = len; /* not used */
1203 strncpy(args->text, (data + start), len);
1204 } else if (eql(action, "rel_move_for")) {
1205 struct rel_move_for_args *args;
1206 char ref_point[2 + 1];
1207 int start, len;
1209 if (sscanf(data, "%u %2s %n", &op->id, ref_point, &start) < 2)
1210 return false;
1211 len = strlen(data + start) + 1;
1212 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1213 OOM_ABORT;
1214 if (eql(ref_point, "c"))
1215 args->ref = C;
1216 else if (eql(ref_point, "e"))
1217 args->ref = E;
1218 else if (eql(ref_point, "n"))
1219 args->ref = N;
1220 else if (eql(ref_point, "ne"))
1221 args->ref = NE;
1222 else if (eql(ref_point, "nw"))
1223 args->ref = NW;
1224 else if (eql(ref_point, "s"))
1225 args->ref = S;
1226 else if (eql(ref_point, "se"))
1227 args->ref = SE;
1228 else if (eql(ref_point, "sw"))
1229 args->ref = SW;
1230 else if (eql(ref_point, "w"))
1231 args->ref = W;
1232 else
1233 return false;
1234 op->op = REL_MOVE_FOR;
1235 op->op_args = args;
1236 args->len = len; /* not used */
1237 strncpy(args->text, (data + start), len);
1238 } else if (eql(action, "stroke")) {
1239 op->op = STROKE;
1240 if (sscanf(data, "%u", &op->id) != 1)
1241 return false;
1242 op->op_args = NULL;
1243 } else if (eql(action, "stroke_preserve")) {
1244 op->op = STROKE_PRESERVE;
1245 if (sscanf(data, "%u", &op->id) != 1)
1246 return false;
1247 op->op_args = NULL;
1248 } else if (eql(action, "fill")) {
1249 op->op = FILL;
1250 if (sscanf(data, "%u", &op->id) != 1)
1251 return false;
1252 op->op_args = NULL;
1253 } else if (eql(action, "fill_preserve")) {
1254 op->op = FILL_PRESERVE;
1255 if (sscanf(data, "%u", &op->id) != 1)
1256 return false;
1257 op->op_args = NULL;
1258 } else if (eql(action, "set_dash")) {
1259 struct set_dash_args *args;
1260 int d_start, n, i;
1261 char data1[strlen(data) + 1];
1262 char *next, *end;
1264 strcpy(data1, data);
1265 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
1266 return false;
1267 next = end = data1 + d_start;
1268 n = -1;
1269 do {
1270 n++;
1271 next = end;
1272 strtod(next, &end);
1273 } while (next != end);
1274 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1275 OOM_ABORT;
1276 op->op = SET_DASH;
1277 op->op_args = args;
1278 args->num_dashes = n;
1279 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
1280 args->dashes[i] = strtod(next, &end);
1282 } else if (eql(action, "set_font_face")) {
1283 struct set_font_face_args *args;
1284 int family_start, family_len;
1285 char slant[7 + 1];
1286 char weight[6 + 1];
1288 if (sscanf(data, "%u %s %s %n%*s", &op->id, slant, weight, &family_start) != 3)
1289 return false;
1290 family_len = strlen(data + family_start) + 1;
1291 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1292 OOM_ABORT;
1293 op->op = SET_FONT_FACE;
1294 op->op_args = args;
1295 strncpy(args->family, data + family_start, family_len);
1296 if (eql(slant, "normal"))
1297 args->slant = CAIRO_FONT_SLANT_NORMAL;
1298 else if (eql(slant, "italic"))
1299 args->slant = CAIRO_FONT_SLANT_ITALIC;
1300 else if (eql(slant, "oblique"))
1301 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1302 else
1303 return false;
1304 if (eql(weight, "normal"))
1305 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1306 else if (eql(weight, "bold"))
1307 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1308 else
1309 return false;
1310 } else if (eql(action, "set_font_size")) {
1311 struct set_font_size_args *args;
1313 if ((args = malloc(sizeof(*args))) == NULL)
1314 OOM_ABORT;
1315 op->op = SET_FONT_SIZE;
1316 op->op_args = args;
1317 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
1318 return false;
1319 } else if (eql(action, "set_line_cap")) {
1320 struct set_line_cap_args *args;
1321 char str[6 + 1];
1323 if ((args = malloc(sizeof(*args))) == NULL)
1324 OOM_ABORT;
1325 op->op = SET_LINE_CAP;
1326 op->op_args = args;
1327 if (sscanf(data, "%u %6s", &op->id, str) != 2)
1328 return false;
1329 if (eql(str, "butt"))
1330 args->line_cap = CAIRO_LINE_CAP_BUTT;
1331 else if (eql(str, "round"))
1332 args->line_cap = CAIRO_LINE_CAP_ROUND;
1333 else if (eql(str, "square"))
1334 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1335 else
1336 return false;
1337 } else if (eql(action, "set_line_join")) {
1338 struct set_line_join_args *args;
1339 char str[5 + 1];
1341 if ((args = malloc(sizeof(*args))) == NULL)
1342 OOM_ABORT;
1343 op->op = SET_LINE_JOIN;
1344 op->op_args = args;
1345 if (sscanf(data, "%u %5s", &op->id, str) != 2)
1346 return false;
1347 if (eql(str, "miter"))
1348 args->line_join = CAIRO_LINE_JOIN_MITER;
1349 else if (eql(str, "round"))
1350 args->line_join = CAIRO_LINE_JOIN_ROUND;
1351 else if (eql(str, "bevel"))
1352 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1353 else
1354 return false;
1355 } else if (eql(action, "set_line_width")) {
1356 struct set_line_width_args *args;
1358 if ((args = malloc(sizeof(*args))) == NULL)
1359 OOM_ABORT;
1360 op->op = SET_LINE_WIDTH;
1361 op->op_args = args;
1362 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
1363 return false;
1364 } else if (eql(action, "set_source_rgba")) {
1365 struct set_source_rgba_args *args;
1366 int c_start;
1368 if ((args = malloc(sizeof(*args))) == NULL)
1369 OOM_ABORT;
1370 op->op = SET_SOURCE_RGBA;
1371 op->op_args = args;
1372 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
1373 return false;;
1374 gdk_rgba_parse(&args->color, data + c_start);
1375 } else
1376 return false;
1377 return true;
1381 * Append another element to widget's "draw_ops" list
1383 static bool
1384 ins_draw_op(GObject *widget, const char *action, const char *data)
1386 struct draw_op *op, *draw_ops, *last_op;
1388 if ((op = malloc(sizeof(*op))) == NULL)
1389 OOM_ABORT;
1390 op->op_args = NULL;
1391 op->next = NULL;
1392 if (!set_draw_op(op, action, data)) {
1393 free(op->op_args);
1394 free(op);
1395 return false;
1397 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1398 g_object_set_data(widget, "draw_ops", op);
1399 else {
1400 for (last_op = draw_ops;
1401 last_op->next != NULL;
1402 last_op = last_op->next);
1403 last_op->next = op;
1405 return true;
1409 * Remove all elements with the given id from widget's "draw_ops" list
1411 static bool
1412 rem_draw_op(GObject *widget, const char *data)
1414 struct draw_op *op, *next_op, *prev_op = NULL;
1415 unsigned int id;
1417 if (sscanf(data, "%u", &id) != 1)
1418 return false;
1419 op = g_object_get_data(widget, "draw_ops");
1420 while (op != NULL) {
1421 next_op = op->next;
1422 if (op->id == id) {
1423 if (prev_op == NULL) /* list head */
1424 g_object_set_data(widget, "draw_ops", op->next);
1425 else
1426 prev_op->next = op->next;
1427 free(op->op_args);
1428 free(op);
1429 } else
1430 prev_op = op;
1431 op = next_op;
1433 return true;
1436 static void
1437 update_drawing_area(GObject *obj, const char *action,
1438 const char *data, const char *whole_msg, GType type)
1440 if (eql(action, "remove")) {
1441 if (!rem_draw_op(obj, data))
1442 ign_cmd(type, whole_msg);
1443 } else if (eql(action, "refresh")) {
1444 gint width = gtk_widget_get_allocated_width(GTK_WIDGET(obj));
1445 gint height = gtk_widget_get_allocated_height(GTK_WIDGET(obj));
1447 gtk_widget_queue_draw_area(GTK_WIDGET(obj), 0, 0, width, height);
1448 } else if (ins_draw_op(obj, action, data));
1449 else
1450 ign_cmd(type, whole_msg);
1453 static void
1454 update_entry(GObject *obj, const char *action,
1455 const char *data, const char *whole_msg, GType type)
1457 GtkEntry *entry = GTK_ENTRY(obj);
1459 if (eql(action, "set_text"))
1460 gtk_entry_set_text(entry, data);
1461 else if (eql(action, "set_placeholder_text"))
1462 gtk_entry_set_placeholder_text(entry, data);
1463 else
1464 ign_cmd(type, whole_msg);
1467 static void
1468 update_expander(GObject *obj, const char *action,
1469 const char *data, const char *whole_msg, GType type)
1471 GtkExpander *expander = GTK_EXPANDER(obj);
1473 if (eql(action, "set_expanded"))
1474 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1475 else if (eql(action, "set_label"))
1476 gtk_expander_set_label(expander, data);
1477 else
1478 ign_cmd(type, whole_msg);
1481 static void
1482 update_file_chooser_button(GObject *obj, const char *action,
1483 const char *data, const char *whole_msg, GType type)
1485 if (eql(action, "set_filename"))
1486 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1487 else
1488 ign_cmd(type, whole_msg);
1491 static void
1492 update_file_chooser_dialog(GObject *obj, const char *action,
1493 const char *data, const char *whole_msg, GType type)
1495 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1497 if (eql(action, "set_filename"))
1498 gtk_file_chooser_set_filename(chooser, data);
1499 else if (eql(action, "set_current_name"))
1500 gtk_file_chooser_set_current_name(chooser, data);
1501 else if (update_class_window(obj, action, data, whole_msg, type));
1502 else
1503 ign_cmd(type, whole_msg);
1506 static void
1507 update_focus(GObject *obj, const char *action,
1508 const char *data, const char *whole_msg, GType type)
1510 (void) action;
1511 (void) data;
1512 if (gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1513 gtk_widget_grab_focus(GTK_WIDGET(obj));
1514 else
1515 ign_cmd(type, whole_msg);
1518 static void
1519 update_font_button(GObject *obj, const char *action,
1520 const char *data, const char *whole_msg, GType type)
1522 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1524 if (eql(action, "set_font_name"))
1525 gtk_font_button_set_font_name(font_button, data);
1526 else
1527 ign_cmd(type, whole_msg);
1530 static void
1531 update_frame(GObject *obj, const char *action,
1532 const char *data, const char *whole_msg, GType type)
1534 if (eql(action, "set_label"))
1535 gtk_frame_set_label(GTK_FRAME(obj), data);
1536 else
1537 ign_cmd(type, whole_msg);
1540 static void
1541 update_image(GObject *obj, const char *action,
1542 const char *data, const char *whole_msg, GType type)
1544 GtkImage *image = GTK_IMAGE(obj);
1545 GtkIconSize size;
1547 gtk_image_get_icon_name(image, NULL, &size);
1548 if (eql(action, "set_from_file"))
1549 gtk_image_set_from_file(image, data);
1550 else if (eql(action, "set_from_icon_name"))
1551 gtk_image_set_from_icon_name(image, data, size);
1552 else
1553 ign_cmd(type, whole_msg);
1556 static void
1557 update_label(GObject *obj, const char *action,
1558 const char *data, const char *whole_msg, GType type)
1560 if (eql(action, "set_text"))
1561 gtk_label_set_text(GTK_LABEL(obj), data);
1562 else
1563 ign_cmd(type, whole_msg);
1566 static void
1567 update_notebook(GObject *obj, const char *action,
1568 const char *data, const char *whole_msg, GType type)
1570 if (eql(action, "set_current_page"))
1571 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), strtol(data, NULL, 10));
1572 else
1573 ign_cmd(type, whole_msg);
1576 static void
1577 update_nothing(GObject *obj, const char *action,
1578 const char *data, const char *whole_msg, GType type)
1580 (void) obj;
1581 (void) action;
1582 (void) data;
1583 (void) whole_msg;
1584 (void) type;
1587 static void
1588 update_print_dialog(GObject *obj, const char *action,
1589 const char *data, const char *whole_msg, GType type)
1591 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1592 gint response_id;
1593 GtkPrinter *printer;
1594 GtkPrintSettings *settings;
1595 GtkPageSetup *page_setup;
1596 GtkPrintJob *job;
1598 if (eql(action, "print")) {
1599 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1600 switch (response_id) {
1601 case GTK_RESPONSE_OK:
1602 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1603 settings = gtk_print_unix_dialog_get_settings(dialog);
1604 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1605 job = gtk_print_job_new(data, printer, settings, page_setup);
1606 if (gtk_print_job_set_source_file(job, data, NULL))
1607 gtk_print_job_send(job, NULL, NULL, NULL);
1608 else
1609 ign_cmd(type, whole_msg);
1610 g_clear_object(&settings);
1611 g_clear_object(&job);
1612 break;
1613 case GTK_RESPONSE_CANCEL:
1614 case GTK_RESPONSE_DELETE_EVENT:
1615 break;
1616 default:
1617 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1618 widget_name(GTK_BUILDABLE(dialog)), response_id);
1619 break;
1621 gtk_widget_hide(GTK_WIDGET(dialog));
1622 } else
1623 ign_cmd(type, whole_msg);
1626 static void
1627 update_progress_bar(GObject *obj, const char *action,
1628 const char *data, const char *whole_msg, GType type)
1630 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1632 if (eql(action, "set_text"))
1633 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1634 else if (eql(action, "set_fraction"))
1635 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1636 else
1637 ign_cmd(type, whole_msg);
1640 static void
1641 update_scale(GObject *obj, const char *action,
1642 const char *data, const char *whole_msg, GType type)
1644 if (eql(action, "set_value"))
1645 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
1646 else
1647 ign_cmd(type, whole_msg);
1650 static void
1651 update_scrolled_window(GObject *obj, const char *action,
1652 const char *data, const char *whole_msg, GType type)
1654 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
1655 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
1656 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
1657 double d0, d1;
1659 if (eql(action, "hscroll") && sscanf(data, "%lf", &d0) == 1)
1660 gtk_adjustment_set_value(hadj, d0);
1661 else if (eql(action, "vscroll") && sscanf(data, "%lf", &d0) == 1)
1662 gtk_adjustment_set_value(vadj, d0);
1663 else if (eql(action, "hscroll_to_range") &&
1664 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1665 gtk_adjustment_clamp_page(hadj, d0, d1);
1666 else if (eql(action, "vscroll_to_range") &&
1667 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1668 gtk_adjustment_clamp_page(vadj, d0, d1);
1669 else
1670 ign_cmd(type, whole_msg);
1673 static void
1674 update_sensitivity(GObject *obj, const char *action,
1675 const char *data, const char *whole_msg, GType type)
1677 (void) action;
1678 (void) whole_msg;
1679 (void) type;
1680 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1683 static void
1684 update_size_request(GObject *obj, const char *action,
1685 const char *data, const char *whole_msg, GType type)
1687 int x, y;
1689 (void) action;
1690 (void) whole_msg;
1691 (void) type;
1692 if (sscanf(data, "%d %d", &x, &y) == 2)
1693 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
1694 else
1695 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
1698 static void
1699 update_socket(GObject *obj, const char *action,
1700 const char *data, const char *whole_msg, GType type)
1702 GtkSocket *socket = GTK_SOCKET(obj);
1703 Window id;
1704 char str[BUFLEN];
1706 (void) data;
1707 if (eql(action, "id")) {
1708 id = gtk_socket_get_id(socket);
1709 snprintf(str, BUFLEN, "%lu", id);
1710 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1711 } else
1712 ign_cmd(type, whole_msg);
1715 static void
1716 update_spinner(GObject *obj, const char *action,
1717 const char *data, const char *whole_msg, GType type)
1719 GtkSpinner *spinner = GTK_SPINNER(obj);
1721 (void) data;
1722 if (eql(action, "start"))
1723 gtk_spinner_start(spinner);
1724 else if (eql(action, "stop"))
1725 gtk_spinner_stop(spinner);
1726 else
1727 ign_cmd(type, whole_msg);
1730 static void
1731 update_statusbar(GObject *obj, const char *action,
1732 const char *data, const char *whole_msg, GType type)
1734 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
1735 char *ctx_msg, *msg;
1736 size_t ctx_len;
1738 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
1739 OOM_ABORT;
1740 strcpy(ctx_msg, data);
1741 ctx_len = strcspn(ctx_msg, WHITESPACE);
1742 if (ctx_len > 0) {
1743 ctx_msg[ctx_len] = '\0';
1744 msg = ctx_msg + ctx_len + 1;
1745 } else
1746 msg = ctx_msg + strlen(ctx_msg);
1747 if (eql(action, "push"))
1748 gtk_statusbar_push(statusbar,
1749 gtk_statusbar_get_context_id(statusbar, "0"),
1750 data);
1751 else if (eql(action, "push_id"))
1752 gtk_statusbar_push(statusbar,
1753 gtk_statusbar_get_context_id(statusbar, ctx_msg),
1754 msg);
1755 else if (eql(action, "pop"))
1756 gtk_statusbar_pop(statusbar,
1757 gtk_statusbar_get_context_id(statusbar, "0"));
1758 else if (eql(action, "pop_id"))
1759 gtk_statusbar_pop(statusbar,
1760 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1761 else if (eql(action, "remove_all"))
1762 gtk_statusbar_remove_all(statusbar,
1763 gtk_statusbar_get_context_id(statusbar, "0"));
1764 else if (eql(action, "remove_all_id"))
1765 gtk_statusbar_remove_all(statusbar,
1766 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1767 else
1768 ign_cmd(type, whole_msg);
1769 free(ctx_msg);
1772 static void
1773 update_switch(GObject *obj, const char *action,
1774 const char *data, const char *whole_msg, GType type)
1776 if (eql(action, "set_active"))
1777 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
1778 else
1779 ign_cmd(type, whole_msg);
1782 static void
1783 update_text_view(GObject *obj, const char *action,
1784 const char *data, const char *whole_msg, GType type)
1786 GtkTextView *view = GTK_TEXT_VIEW(obj);
1787 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1788 GtkTextIter a, b;
1790 if (eql(action, "set_text"))
1791 gtk_text_buffer_set_text(textbuf, data, -1);
1792 else if (eql(action, "delete")) {
1793 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1794 gtk_text_buffer_delete(textbuf, &a, &b);
1795 } else if (eql(action, "insert_at_cursor"))
1796 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1797 else if (eql(action, "place_cursor")) {
1798 if (eql(data, "end"))
1799 gtk_text_buffer_get_end_iter(textbuf, &a);
1800 else /* numeric offset */
1801 gtk_text_buffer_get_iter_at_offset(textbuf, &a,
1802 strtol(data, NULL, 10));
1803 gtk_text_buffer_place_cursor(textbuf, &a);
1804 } else if (eql(action, "place_cursor_at_line")) {
1805 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1806 gtk_text_buffer_place_cursor(textbuf, &a);
1807 } else if (eql(action, "scroll_to_cursor"))
1808 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
1809 0., 0, 0., 0.);
1810 else if (eql(action, "save") && data != NULL &&
1811 (save = fopen(data, "w")) != NULL) {
1812 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1813 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
1814 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
1815 fclose(save);
1816 } else
1817 ign_cmd(type, whole_msg);
1820 static void
1821 update_toggle_button(GObject *obj, const char *action,
1822 const char *data, const char *whole_msg, GType type)
1824 if (eql(action, "set_label"))
1825 gtk_button_set_label(GTK_BUTTON(obj), data);
1826 else if (eql(action, "set_active"))
1827 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
1828 else
1829 ign_cmd(type, whole_msg);
1832 static void
1833 update_tooltip_text(GObject *obj, const char *action,
1834 const char *data, const char *whole_msg, GType type)
1836 (void) action;
1837 (void) whole_msg;
1838 (void) type;
1839 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
1843 * update_tree_view() needs a few helper functions
1847 * Check if s is a valid string representation of a GtkTreePath
1849 static bool
1850 is_path_string(char *s)
1852 return s != NULL &&
1853 strlen(s) == strspn(s, ":0123456789") &&
1854 strstr(s, "::") == NULL &&
1855 strcspn(s, ":") > 0;
1858 static void
1859 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
1860 GtkTreeIter *parent, GtkTreeIter *sibling)
1862 if (GTK_IS_TREE_STORE(model))
1863 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
1864 iter, parent, sibling);
1865 else if (GTK_IS_LIST_STORE(model))
1866 gtk_list_store_insert_before(GTK_LIST_STORE(model),
1867 iter, sibling);
1868 else
1869 ABORT;
1872 static void
1873 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
1874 GtkTreeIter *parent, GtkTreeIter *sibling)
1876 if (GTK_IS_TREE_STORE(model))
1877 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
1878 iter, parent, sibling);
1879 else if (GTK_IS_LIST_STORE(model))
1880 gtk_list_store_insert_after(GTK_LIST_STORE(model),
1881 iter, sibling);
1882 else
1883 ABORT;
1886 static void
1887 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
1888 GtkTreeIter *position)
1890 if (GTK_IS_TREE_STORE(model))
1891 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
1892 else if (GTK_IS_LIST_STORE(model))
1893 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
1894 else
1895 ABORT;
1898 static void
1899 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
1901 if (GTK_IS_TREE_STORE(model))
1902 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
1903 else if (GTK_IS_LIST_STORE(model))
1904 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
1905 else
1906 ABORT;
1909 static void
1910 tree_model_clear(GtkTreeModel *model)
1912 if (GTK_IS_TREE_STORE(model))
1913 gtk_tree_store_clear(GTK_TREE_STORE(model));
1914 else if (GTK_IS_LIST_STORE(model))
1915 gtk_list_store_clear(GTK_LIST_STORE(model));
1916 else
1917 ABORT;
1920 static void
1921 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
1923 va_list ap;
1925 va_start(ap, iter);
1926 if (GTK_IS_TREE_STORE(model))
1927 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
1928 else if (GTK_IS_LIST_STORE(model))
1929 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
1930 else
1931 ABORT;
1932 va_end(ap);
1936 * Create an empty row at path if it doesn't yet exist. Create older
1937 * siblings and parents as necessary.
1939 static void
1940 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
1942 GtkTreePath *path_1; /* path's predecessor */
1943 GtkTreeIter iter_1; /* iter's predecessor */
1945 if (gtk_tree_model_get_iter(model, iter, path))
1946 return;
1947 path_1 = gtk_tree_path_copy(path);
1948 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
1949 create_subtree(model, path_1, iter);
1950 iter_1 = *iter;
1951 tree_model_insert_after(model, iter, NULL, &iter_1);
1952 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
1953 create_subtree(model, path_1, iter);
1954 if (gtk_tree_path_get_depth(path_1) == 0)
1955 /* first toplevel row */
1956 tree_model_insert_after(model, iter, NULL, NULL);
1957 else { /* first row in a lower level */
1958 iter_1 = *iter;
1959 tree_model_insert_after(model, iter, &iter_1, NULL);
1961 } /* neither prev nor up mean we're at the root of an empty tree */
1962 gtk_tree_path_free(path_1);
1965 static bool
1966 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
1967 const char *path_s, int col, const char *new_text)
1969 GType col_type = gtk_tree_model_get_column_type(model, col);
1970 long long int n;
1971 double d;
1972 GtkTreePath *path;
1973 char *endptr;
1974 bool ok = false;
1976 path = gtk_tree_path_new_from_string(path_s);
1977 create_subtree(model, path, iter);
1978 gtk_tree_path_free(path);
1979 switch (col_type) {
1980 case G_TYPE_BOOLEAN:
1981 case G_TYPE_INT:
1982 case G_TYPE_LONG:
1983 case G_TYPE_INT64:
1984 case G_TYPE_UINT:
1985 case G_TYPE_ULONG:
1986 case G_TYPE_UINT64:
1987 errno = 0;
1988 endptr = NULL;
1989 n = strtoll(new_text, &endptr, 10);
1990 if (!errno && endptr != new_text) {
1991 tree_model_set(model, iter, col, n, -1);
1992 ok = true;
1994 break;
1995 case G_TYPE_FLOAT:
1996 case G_TYPE_DOUBLE:
1997 errno = 0;
1998 endptr = NULL;
1999 d = strtod(new_text, &endptr);
2000 if (!errno && endptr != new_text) {
2001 tree_model_set(model, iter, col, d, -1);
2002 ok = true;
2004 break;
2005 case G_TYPE_STRING:
2006 tree_model_set(model, iter, col, new_text, -1);
2007 ok = true;
2008 break;
2009 default:
2010 fprintf(stderr, "column %d: %s not implemented\n",
2011 col, g_type_name(col_type));
2012 ok = true;
2013 break;
2015 return ok;
2018 static void
2019 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2021 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2022 /* is just fine and this function need not exist. */
2023 if (path == NULL)
2024 path = gtk_tree_path_new();
2025 gtk_tree_view_set_cursor(view, path, col, false);
2028 static void
2029 update_tree_view(GObject *obj, const char *action,
2030 const char *data, const char *whole_msg, GType type)
2032 GtkTreeView *view = GTK_TREE_VIEW(obj);
2033 GtkTreeModel *model = gtk_tree_view_get_model(view);
2034 GtkTreeIter iter0, iter1;
2035 GtkTreePath *path = NULL;
2036 bool iter0_valid, iter1_valid;
2037 char *tokens, *arg0, *arg1, *arg2;
2038 int col = -1; /* invalid column number */
2040 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2042 fprintf(stderr, "missing model/");
2043 ign_cmd(type, whole_msg);
2044 return;
2046 if ((tokens = malloc(strlen(data) + 1)) == NULL)
2047 OOM_ABORT;
2048 strcpy(tokens, data);
2049 arg0 = strtok(tokens, WHITESPACE);
2050 arg1 = strtok(NULL, WHITESPACE);
2051 arg2 = strtok(NULL, "");
2052 iter0_valid = is_path_string(arg0) &&
2053 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2054 iter1_valid = is_path_string(arg1) &&
2055 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2056 if (is_path_string(arg1))
2057 col = strtol(arg1, NULL, 10);
2058 if (eql(action, "set") &&
2059 col > -1 && col < gtk_tree_model_get_n_columns(model) &&
2060 is_path_string(arg0)) {
2061 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2062 ign_cmd(type, whole_msg);
2063 } else if (eql(action, "scroll") && iter0_valid && iter1_valid) {
2064 path = gtk_tree_path_new_from_string(arg0);
2065 gtk_tree_view_scroll_to_cell (view,
2066 path,
2067 gtk_tree_view_get_column(view, col),
2068 0, 0., 0.);
2069 } else if (eql(action, "expand") && iter0_valid) {
2070 path = gtk_tree_path_new_from_string(arg0);
2071 gtk_tree_view_expand_row(view, path, false);
2072 } else if (eql(action, "expand_all") && iter0_valid) {
2073 path = gtk_tree_path_new_from_string(arg0);
2074 gtk_tree_view_expand_row(view, path, true);
2075 } else if (eql(action, "expand_all") && arg0 == NULL)
2076 gtk_tree_view_expand_all(view);
2077 else if (eql(action, "collapse") && iter0_valid) {
2078 path = gtk_tree_path_new_from_string(arg0);
2079 gtk_tree_view_collapse_row(view, path);
2080 } else if (eql(action, "collapse") && arg0 == NULL)
2081 gtk_tree_view_collapse_all(view);
2082 else if (eql(action, "set_cursor") && iter0_valid) {
2083 path = gtk_tree_path_new_from_string(arg0);
2084 tree_view_set_cursor(view, path, NULL);
2085 } else if (eql(action, "set_cursor") && arg0 == NULL) {
2086 tree_view_set_cursor(view, NULL, NULL);
2087 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2088 } else if (eql(action, "insert_row") && eql(arg0, "end"))
2089 tree_model_insert_before(model, &iter1, NULL, NULL);
2090 else if (eql(action, "insert_row") && iter0_valid && eql(arg1, "as_child"))
2091 tree_model_insert_after(model, &iter1, &iter0, NULL);
2092 else if (eql(action, "insert_row") && iter0_valid)
2093 tree_model_insert_before(model, &iter1, NULL, &iter0);
2094 else if (eql(action, "move_row") && iter0_valid && eql(arg1, "end"))
2095 tree_model_move_before(model, &iter0, NULL);
2096 else if (eql(action, "move_row") && iter0_valid && iter1_valid)
2097 tree_model_move_before(model, &iter0, &iter1);
2098 else if (eql(action, "remove_row") && iter0_valid)
2099 tree_model_remove(model, &iter0);
2100 else if (eql(action, "clear") && arg0 == NULL) {
2101 tree_view_set_cursor(view, NULL, NULL);
2102 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2103 tree_model_clear(model);
2104 } else if (eql(action, "save") && arg0 != NULL &&
2105 (save = fopen(arg0, "w")) != NULL) {
2106 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
2107 fclose(save);
2108 } else
2109 ign_cmd(type, whole_msg);
2110 free(tokens);
2111 gtk_tree_path_free(path);
2114 static void
2115 update_visibility(GObject *obj, const char *action,
2116 const char *data, const char *whole_msg, GType type)
2118 (void) action;
2119 (void) whole_msg;
2120 (void) type;
2121 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
2125 * Change the style of the widget passed
2127 static void
2128 update_widget_style(GObject *obj, const char *name,
2129 const char *data, const char *whole_msg, GType type)
2131 GtkStyleContext *context;
2132 GtkStyleProvider *style_provider;
2133 char *style_decl;
2134 const char *prefix = "* {", *suffix = "}";
2135 size_t sz;
2137 (void) name;
2138 (void) whole_msg;
2139 (void) type;
2140 style_provider = g_object_get_data(obj, "style_provider");
2141 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2142 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2143 gtk_style_context_remove_provider(context, style_provider);
2144 if ((style_decl = malloc(sz)) == NULL)
2145 OOM_ABORT;
2146 strcpy(style_decl, prefix);
2147 strcat(style_decl, data);
2148 strcat(style_decl, suffix);
2149 gtk_style_context_add_provider(context, style_provider,
2150 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2151 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2152 style_decl, -1, NULL);
2153 free(style_decl);
2156 static void
2157 update_window(GObject *obj, const char *action,
2158 const char *data, const char *whole_msg, GType type)
2160 if (!update_class_window(obj, action, data, whole_msg, type))
2161 ign_cmd(type, whole_msg);
2165 * Simulate user activity on various widgets
2167 static void
2168 fake_ui_activity(GObject *obj, const char *action,
2169 const char *data, const char *whole_msg, GType type)
2171 (void) action;
2172 (void) data;
2173 if (!GTK_IS_WIDGET(obj))
2174 ign_cmd(type, whole_msg);
2175 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
2176 cb_editable(GTK_BUILDABLE(obj), "text");
2177 else if (GTK_IS_SCALE(obj))
2178 cb_range(GTK_BUILDABLE(obj), "value");
2179 else if (GTK_IS_CALENDAR(obj))
2180 cb_calendar(GTK_BUILDABLE(obj), "clicked");
2181 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
2182 cb_file_chooser_button(GTK_BUILDABLE(obj), "file");
2183 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2184 ign_cmd(type, whole_msg);
2188 * The final UI update
2190 static void
2191 main_quit(GObject *obj, const char *action,
2192 const char *data, const char *whole_msg, GType type)
2194 (void) obj;
2195 (void) action;
2196 (void) data;
2197 (void) whole_msg;
2198 (void) type;
2199 gtk_main_quit();
2203 * Don't update anything; just complain
2205 static void
2206 complain(GObject *obj, const char *action,
2207 const char *data, const char *whole_msg, GType type)
2209 (void) obj;
2210 (void) action;
2211 (void) data;
2212 ign_cmd(type, whole_msg);
2216 * Data to be passed to and from the GTK main loop
2218 struct ui_data {
2219 void (*fn)(GObject *, const char *action,
2220 const char *data, const char *msg, GType type);
2221 GObject *obj;
2222 char *action;
2223 char *data;
2224 char *msg;
2225 char *msg_tokens;
2226 GType type;
2230 * Parse command pointed to by ud, and act on ui accordingly; post
2231 * semaphore ud.msg_digested if done. Runs once per command inside
2232 * gtk_main_loop().
2234 static gboolean
2235 update_ui(struct ui_data *ud)
2237 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
2238 free(ud->msg_tokens);
2239 free(ud->msg);
2240 free(ud);
2241 return G_SOURCE_REMOVE;
2245 * Keep track of loading files to avoid recursive loading of the same
2246 * file. If filename = NULL, forget the most recently remembered file.
2248 static bool
2249 remember_loading_file(char *filename)
2251 static char *filenames[BUFLEN];
2252 static size_t latest = 0;
2253 size_t i;
2255 if (filename == NULL) { /* pop */
2256 if (latest < 1)
2257 ABORT;
2258 latest--;
2259 return false;
2260 } else { /* push */
2261 for (i = 1; i <= latest; i++)
2262 if (eql(filename, filenames[i]))
2263 return false;
2264 if (latest > BUFLEN -2)
2265 return false;
2266 filenames[++latest] = filename;
2267 return true;
2272 * Read lines from stream cmd and perform appropriate actions on the
2273 * GUI
2275 static void *
2276 digest_msg(FILE *cmd)
2278 FILE *load; /* restoring user data */
2279 char *name;
2280 static int recursion = -1; /* > 0 means this is a recursive call */
2282 recursion++;
2283 for (;;) {
2284 struct ui_data *ud;
2285 char first_char = '\0';
2286 size_t msg_size = 32;
2287 int name_start = 0, name_end = 0;
2288 int action_start = 0, action_end = 0;
2289 int data_start;
2291 if (feof(cmd))
2292 break;
2293 if ((ud = malloc(sizeof(*ud))) == NULL)
2294 OOM_ABORT;
2295 if ((ud->msg = malloc(msg_size)) == NULL)
2296 OOM_ABORT;
2297 ud->type = G_TYPE_INVALID;
2298 pthread_testcancel();
2299 if (recursion == 0)
2300 log_msg(NULL);
2301 read_buf(cmd, &ud->msg, &msg_size);
2302 if (recursion == 0)
2303 log_msg(ud->msg);
2304 data_start = strlen(ud->msg);
2305 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2306 OOM_ABORT;
2307 strcpy(ud->msg_tokens, ud->msg);
2308 sscanf(ud->msg, " %c", &first_char);
2309 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2310 ud->fn = update_nothing;
2311 goto exec;
2313 sscanf(ud->msg_tokens,
2314 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2315 &name_start, &name_end, &action_start, &action_end, &data_start);
2316 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2317 name = ud->msg_tokens + name_start;
2318 ud->action = ud->msg_tokens + action_start;
2319 if (eql(ud->action, "main_quit")) {
2320 ud->fn = main_quit;
2321 goto exec;
2323 ud->data = ud->msg_tokens + data_start;
2324 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2325 (load = fopen(ud->data, "r")) != NULL &&
2326 remember_loading_file(ud->data)) {
2327 digest_msg(load);
2328 fclose(load);
2329 remember_loading_file(NULL);
2330 ud->fn = update_nothing;
2331 goto exec;
2333 if ((ud->obj = (gtk_builder_get_object(builder, name))) == NULL) {
2334 ud->fn = complain;
2335 goto exec;
2337 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2338 if (eql(ud->action, "force"))
2339 ud->fn = fake_ui_activity;
2340 else if (eql(ud->action, "set_sensitive"))
2341 ud->fn = update_sensitivity;
2342 else if (eql(ud->action, "set_visible"))
2343 ud->fn = update_visibility;
2344 else if (eql(ud->action, "set_size_request"))
2345 ud->fn = update_size_request;
2346 else if (eql(ud->action, "set_tooltip_text"))
2347 ud->fn = update_tooltip_text;
2348 else if (eql(ud->action, "grab_focus"))
2349 ud->fn = update_focus;
2350 else if (eql(ud->action, "style")) {
2351 ud->action = name;
2352 ud->fn = update_widget_style;
2353 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2354 ud->fn = update_drawing_area;
2355 else if (ud->type == GTK_TYPE_TREE_VIEW)
2356 ud->fn = update_tree_view;
2357 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2358 ud->fn = update_combo_box_text;
2359 else if (ud->type == GTK_TYPE_LABEL)
2360 ud->fn = update_label;
2361 else if (ud->type == GTK_TYPE_IMAGE)
2362 ud->fn = update_image;
2363 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2364 ud->fn = update_text_view;
2365 else if (ud->type == GTK_TYPE_NOTEBOOK)
2366 ud->fn = update_notebook;
2367 else if (ud->type == GTK_TYPE_EXPANDER)
2368 ud->fn = update_expander;
2369 else if (ud->type == GTK_TYPE_FRAME)
2370 ud->fn = update_frame;
2371 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2372 ud->fn = update_scrolled_window;
2373 else if (ud->type == GTK_TYPE_BUTTON)
2374 ud->fn = update_button;
2375 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2376 ud->fn = update_file_chooser_dialog;
2377 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2378 ud->fn = update_file_chooser_button;
2379 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2380 ud->fn = update_color_button;
2381 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2382 ud->fn = update_font_button;
2383 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2384 ud->fn = update_print_dialog;
2385 else if (ud->type == GTK_TYPE_SWITCH)
2386 ud->fn = update_switch;
2387 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2388 ud->type == GTK_TYPE_RADIO_BUTTON ||
2389 ud->type == GTK_TYPE_CHECK_BUTTON)
2390 ud->fn = update_toggle_button;
2391 else if (ud->type == GTK_TYPE_SPIN_BUTTON ||
2392 ud->type == GTK_TYPE_ENTRY)
2393 ud->fn = update_entry;
2394 else if (ud->type == GTK_TYPE_SCALE)
2395 ud->fn = update_scale;
2396 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2397 ud->fn = update_progress_bar;
2398 else if (ud->type == GTK_TYPE_SPINNER)
2399 ud->fn = update_spinner;
2400 else if (ud->type == GTK_TYPE_STATUSBAR)
2401 ud->fn = update_statusbar;
2402 else if (ud->type == GTK_TYPE_CALENDAR)
2403 ud->fn = update_calendar;
2404 else if (ud->type == GTK_TYPE_SOCKET)
2405 ud->fn = update_socket;
2406 else if (ud->type == GTK_TYPE_WINDOW ||
2407 ud->type == GTK_TYPE_DIALOG)
2408 ud->fn = update_window;
2409 else
2410 ud->fn = complain;
2411 exec:
2412 pthread_testcancel();
2413 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2415 recursion--;
2416 return NULL;
2420 * ============================================================
2421 * Initialization
2422 * ============================================================
2426 * Callbacks that forward a modification of a tree view cell to the
2427 * underlying model
2429 static void
2430 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2431 const gchar *new_text, gpointer model)
2433 GtkTreeIter iter;
2434 GtkTreeView *view;
2435 void *col;
2437 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2438 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2439 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2440 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2441 new_text);
2442 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2443 GTK_BUILDABLE(view));
2446 static void
2447 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2449 GtkTreeIter iter;
2450 void *col;
2451 bool toggle_state;
2453 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2454 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2455 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2456 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2457 toggle_state? "0" : "1");
2461 * Attach to renderer key "col_number". Associate "col_number" with
2462 * the corresponding column number in the underlying model.
2463 * Due to what looks like a gap in the GTK API, renderer id and column
2464 * number are taken directly from the XML .ui file.
2466 static bool
2467 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2468 int n, GtkCellRenderer **renderer)
2470 xmlDocPtr doc;
2471 xmlXPathContextPtr xpath_ctx;
2472 xmlXPathObjectPtr xpath_obj;
2473 xmlNodeSetPtr nodes;
2474 xmlNodePtr cur;
2475 int i;
2476 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2477 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2478 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
2479 char *xpath_base2 = "\"]/child[";
2480 size_t xpath_n_len = 3; /* Big Enough (TM) */
2481 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2482 " or @class=\"GtkCellRendererToggle\"]/";
2483 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2484 " or @name=\"active\"]";
2485 char *xpath_renderer_id = "/@id";
2486 size_t xpath_len;
2487 bool r = false;
2489 if ((doc = xmlParseFile(ui_file)) == NULL)
2490 return false;
2491 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2492 xmlFreeDoc(doc);
2493 return false;
2495 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2496 strlen(xpath_base2) + xpath_n_len +
2497 strlen(xpath_base3))
2498 + 1 /* "|" */
2499 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2500 + 1; /* '\0' */
2501 if ((xpath = malloc(xpath_len)) == NULL) {
2502 xmlFreeDoc(doc);
2503 return false;
2505 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2506 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2507 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2508 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2509 xmlXPathFreeContext(xpath_ctx);
2510 free(xpath);
2511 xmlFreeDoc(doc);
2512 return false;
2514 if ((nodes = xpath_obj->nodesetval) != NULL) {
2515 for (i = 0; i < nodes->nodeNr; ++i) {
2516 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2517 cur = nodes->nodeTab[i];
2518 m_col_s = xmlNodeGetContent(cur);
2519 } else {
2520 cur = nodes->nodeTab[i];
2521 renderer_name = xmlNodeGetContent(cur);
2525 if (renderer_name) {
2526 *renderer = GTK_CELL_RENDERER(
2527 gtk_builder_get_object(builder, (char *) renderer_name));
2528 if (m_col_s) {
2529 g_object_set_data(G_OBJECT(*renderer), "col_number",
2530 GINT_TO_POINTER(strtol((char *) m_col_s,
2531 NULL, 10)));
2532 xmlFree(m_col_s);
2533 r = true;
2535 xmlFree(renderer_name);
2537 xmlXPathFreeObject(xpath_obj);
2538 xmlXPathFreeContext(xpath_ctx);
2539 free(xpath);
2540 xmlFreeDoc(doc);
2541 return r;
2544 static void
2545 connect_widget_signals(gpointer *obj, char *ui_file)
2547 const char *name = NULL;
2548 char *suffix = NULL;
2549 GObject *obj2;
2550 GType type = G_TYPE_INVALID;
2552 type = G_TYPE_FROM_INSTANCE(obj);
2553 if (GTK_IS_BUILDABLE(obj))
2554 name = widget_name(GTK_BUILDABLE(obj));
2555 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2556 gboolean editable = FALSE;
2557 GtkTreeView *view;
2558 GtkTreeModel *model;
2559 GtkCellRenderer *renderer;
2560 int i;
2562 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2563 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2564 model = gtk_tree_view_get_model(view);
2565 for (i = 1;; i++) {
2566 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2567 break;
2568 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2569 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2570 g_object_get(renderer, "editable", &editable, NULL);
2571 if (editable)
2572 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2573 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2574 g_object_get(renderer, "activatable", &editable, NULL);
2575 if (editable)
2576 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2580 else if (type == GTK_TYPE_BUTTON) {
2581 /* Button associated with a GtkTextView. */
2582 if ((suffix = strstr(name, "_send_text")) != NULL &&
2583 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2584 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2585 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2586 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2587 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2588 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2589 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2590 else {
2591 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2592 /* Buttons associated with (and part of) a GtkDialog.
2593 * (We shun response ids which could be returned from
2594 * gtk_dialog_run() because that would require the
2595 * user to define those response ids in Glade,
2596 * numerically */
2597 if ((suffix = strstr(name, "_cancel")) != NULL &&
2598 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2599 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2600 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2601 else
2602 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2603 else if ((suffix = strstr(name, "_ok")) != NULL &&
2604 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2605 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2606 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2607 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2608 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2609 else
2610 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2611 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2612 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2613 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2615 } else if (GTK_IS_MENU_ITEM(obj))
2616 if ((suffix = strstr(name, "_invoke")) != NULL &&
2617 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2618 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2619 else
2620 g_signal_connect(obj, "activate", G_CALLBACK(cb_menu_item), "active");
2621 else if (GTK_IS_WINDOW(obj)) {
2622 g_signal_connect(obj, "delete-event", G_CALLBACK(cb_event_simple), "closed");
2623 if (eql(name, MAIN_WIN))
2624 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2625 else
2626 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2627 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2628 g_signal_connect(obj, "file-set", G_CALLBACK(cb_file_chooser_button), "file");
2629 else if (type == GTK_TYPE_COLOR_BUTTON)
2630 g_signal_connect(obj, "color-set", G_CALLBACK(cb_color_button), "color");
2631 else if (type == GTK_TYPE_FONT_BUTTON)
2632 g_signal_connect(obj, "font-set", G_CALLBACK(cb_font_button), "font");
2633 else if (type == GTK_TYPE_SWITCH)
2634 g_signal_connect(obj, "notify::active", G_CALLBACK(cb_switch), NULL);
2635 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2636 g_signal_connect(obj, "toggled", G_CALLBACK(cb_toggle_button), NULL);
2637 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2638 g_signal_connect(obj, "changed", G_CALLBACK(cb_editable), "text");
2639 else if (type == GTK_TYPE_SCALE)
2640 g_signal_connect(obj, "value-changed", G_CALLBACK(cb_range), "value");
2641 else if (type == GTK_TYPE_CALENDAR) {
2642 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb_calendar), "doubleclicked");
2643 g_signal_connect(obj, "day-selected", G_CALLBACK(cb_calendar), "clicked");
2644 } else if (type == GTK_TYPE_TREE_SELECTION)
2645 g_signal_connect(obj, "changed", G_CALLBACK(cb_tree_selection), "clicked");
2646 else if (type == GTK_TYPE_SOCKET) {
2647 g_signal_connect(obj, "plug-added", G_CALLBACK(cb_simple), "plug-added");
2648 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_simple), "plug-removed");
2649 } else if (type == GTK_TYPE_DRAWING_AREA)
2650 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2651 else if (type == GTK_TYPE_EVENT_BOX) {
2652 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
2653 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
2654 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
2655 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
2656 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
2661 * We keep a style provider with each widget
2663 static void
2664 add_widget_style_provider(gpointer *obj, void *data)
2666 GtkStyleContext *context;
2667 GtkCssProvider *style_provider;
2669 (void) data;
2670 if (!GTK_IS_WIDGET(obj))
2671 return;
2672 style_provider = gtk_css_provider_new();
2673 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2674 gtk_style_context_add_provider(context,
2675 GTK_STYLE_PROVIDER(style_provider),
2676 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2677 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2680 static void
2681 prepare_widgets(char *ui_file)
2683 GSList *objects = NULL;
2685 objects = gtk_builder_get_objects(builder);
2686 g_slist_foreach(objects, (GFunc) connect_widget_signals, ui_file);
2687 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
2688 g_slist_free(objects);
2692 main(int argc, char *argv[])
2694 char opt;
2695 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
2696 char *log_file = NULL;
2697 char *xid_s = NULL, xid_s2[BUFLEN];
2698 bool bg = false;
2699 pid_t pid = 0;
2700 Window xid;
2701 GtkWidget *plug, *body;
2702 pthread_t receiver;
2703 GError *error = NULL;
2704 GObject *main_window = NULL;
2705 FILE *in = NULL; /* command input */
2707 /* Disable runtime GLIB deprecation warnings: */
2708 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2709 out = NULL;
2710 save = NULL;
2711 log_out = NULL;
2712 gtk_init(&argc, &argv);
2713 while ((opt = getopt(argc, argv, "bhe:i:l:o:u:GV")) != -1) {
2714 switch (opt) {
2715 case 'b': bg = true; break;
2716 case 'e': xid_s = optarg; break;
2717 case 'i': in_fifo = optarg; break;
2718 case 'l': log_file = optarg; break;
2719 case 'o': out_fifo = optarg; break;
2720 case 'u': ui_file = optarg; break;
2721 case 'G': bye(EXIT_SUCCESS, stdout,
2722 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
2723 "cairo v%s (running v%s)\n",
2724 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
2725 gtk_get_major_version(), gtk_get_minor_version(),
2726 gtk_get_micro_version(),
2727 CAIRO_VERSION_STRING, cairo_version_string());
2728 break;
2729 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
2730 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
2731 case '?':
2732 default: bye(EXIT_FAILURE, stderr, USAGE); break;
2735 if (argv[optind] != NULL)
2736 bye(EXIT_FAILURE, stderr,
2737 "illegal parameter '%s'\n" USAGE, argv[optind]);
2738 in = fifo(in_fifo, "r");
2739 out = fifo(out_fifo, "w");
2740 if (bg) {
2741 if (in == stdin || out == stdout)
2742 bye(EXIT_FAILURE, stderr,
2743 "parameter -b requires both -i and -o\n");
2744 else if ((pid = fork()) > 0)
2745 bye(EXIT_SUCCESS, stdout, "%d\n", pid);
2746 else if (pid < 0)
2747 bye(EXIT_FAILURE, stderr,
2748 "going to background: %s\n", strerror(errno));
2750 if (ui_file == NULL)
2751 ui_file = "pipeglade.ui";
2752 builder = gtk_builder_new();
2753 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0)
2754 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
2755 log_out = open_log(log_file);
2756 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, in);
2757 main_window = gtk_builder_get_object(builder, MAIN_WIN);
2758 if (!GTK_IS_WINDOW(main_window))
2759 bye(EXIT_FAILURE, stderr,
2760 "no toplevel window named \'" MAIN_WIN "\'\n");
2761 xmlInitParser();
2762 LIBXML_TEST_VERSION;
2763 prepare_widgets(ui_file);
2764 if (xid_s == NULL) /* standalone */
2765 gtk_widget_show(GTK_WIDGET(main_window));
2766 else { /* We're being XEmbedded */
2767 xid = strtoul(xid_s, NULL, 10);
2768 snprintf(xid_s2, BUFLEN, "%lu", xid);
2769 if (!eql(xid_s, xid_s2))
2770 bye(EXIT_FAILURE, stderr,
2771 "%s is not a valid XEmbed socket id\n", xid_s);
2772 body = gtk_bin_get_child(GTK_BIN(main_window));
2773 gtk_container_remove(GTK_CONTAINER(main_window), body);
2774 plug = gtk_plug_new(xid);
2775 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
2776 bye(EXIT_FAILURE, stderr,
2777 "unable to embed into XEmbed socket %s\n", xid_s);
2778 gtk_container_add(GTK_CONTAINER(plug), body);
2779 gtk_widget_show(plug);
2781 gtk_main();
2782 if (in != stdin) {
2783 fclose(in);
2784 unlink(in_fifo);
2786 if (out != stdout) {
2787 fclose(out);
2788 unlink(out_fifo);
2790 pthread_cancel(receiver);
2791 pthread_join(receiver, NULL);
2792 xmlCleanupParser();
2793 exit(EXIT_SUCCESS);