Get rid of all global variables
[pipeglade.git] / pipeglade.c
blobcce97651ee39d46b8b5f90cd88a143e82098de6f
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 <cairo-pdf.h>
25 #include <cairo-ps.h>
26 #include <cairo-svg.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <gtk/gtk.h>
30 #include <gtk/gtkunixprint.h>
31 #include <gtk/gtkx.h>
32 #include <inttypes.h>
33 #include <libxml/xpath.h>
34 #include <locale.h>
35 #include <math.h>
36 #include <pthread.h>
37 #include <stdio.h>
38 #include <stdarg.h>
39 #include <stdbool.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/select.h>
43 #include <sys/stat.h>
44 #include <time.h>
45 #include <unistd.h>
47 #define VERSION "4.7.0"
48 #define BUFLEN 256
49 #define WHITESPACE " \t\n"
50 #define MAIN_WIN "main"
51 #define USAGE \
52 "usage: pipeglade [[-i in-fifo] " \
53 "[-o out-fifo] " \
54 "[-b] " \
55 "[-u glade-file.ui] " \
56 "[-e xid]\n" \
57 " [-l log-file] " \
58 "[-O err-file] " \
59 "[--display X-server]] | " \
60 "[-h |" \
61 "-G |" \
62 "-V]\n"
64 #define ABORT \
65 { \
66 fprintf(stderr, \
67 "In %s (%s:%d): ", \
68 __func__, __FILE__, __LINE__); \
69 abort(); \
72 #define OOM_ABORT \
73 { \
74 fprintf(stderr, \
75 "Out of memory in %s (%s:%d): ", \
76 __func__, __FILE__, __LINE__); \
77 abort(); \
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);
110 static void
111 show_lib_versions(void)
113 bye(EXIT_SUCCESS, stdout,
114 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
115 "cairo v%s (running v%s)\n",
116 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
117 gtk_get_major_version(), gtk_get_minor_version(),
118 gtk_get_micro_version(),
119 CAIRO_VERSION_STRING, cairo_version_string());
123 * XEmbed us if xid_s is given, or show a standalone window; give up
124 * on errors
126 static void
127 xembed_if(char *xid_s, GObject *main_window)
129 GtkWidget *plug, *body;
130 Window xid;
131 char xid_s2[BUFLEN];
133 if (xid_s == NULL) { /* standalone */
134 gtk_widget_show(GTK_WIDGET(main_window));
135 return;
137 /* We're being XEmbedded */
138 xid = strtoul(xid_s, NULL, 10);
139 snprintf(xid_s2, BUFLEN, "%lu", xid);
140 if (!eql(xid_s, xid_s2))
141 bye(EXIT_FAILURE, stderr,
142 "%s is not a valid XEmbed socket id\n", xid_s);
143 body = gtk_bin_get_child(GTK_BIN(main_window));
144 gtk_container_remove(GTK_CONTAINER(main_window), body);
145 plug = gtk_plug_new(xid);
146 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
147 bye(EXIT_FAILURE, stderr,
148 "unable to embed into XEmbed socket %s\n", xid_s);
149 gtk_container_add(GTK_CONTAINER(plug), body);
150 gtk_widget_show(plug);
154 * If requested, redirect stderr to file name
156 static void
157 redirect_stderr(const char *name)
159 if (name == NULL)
160 return;
161 if (freopen(name, "a", stderr) == NULL)
162 /* complaining on stdout since stderr is closed now */
163 bye(EXIT_FAILURE, stdout, "redirecting stderr to %s: %s\n",
164 name, strerror(errno));
165 if (fchmod(fileno(stderr), 0600) < 0)
166 bye(EXIT_FAILURE, stdout, "setting permissions of %s: %s\n",
167 name, strerror(errno));
168 setvbuf(stderr, NULL, _IOLBF, 0);
169 return;
173 * fork() if requested in bg; give up on errors
175 static void
176 go_bg_if(bool bg, FILE *in, FILE *out, char *err_file)
178 pid_t pid = 0;
180 if (!bg)
181 return;
182 if (in == stdin || out == stdout)
183 bye(EXIT_FAILURE, stderr,
184 "parameter -b requires both -i and -o\n");
185 pid = fork();
186 if (pid < 0)
187 bye(EXIT_FAILURE, stderr,
188 "going to background: %s\n", strerror(errno));
189 if (pid > 0)
190 bye(EXIT_SUCCESS, stdout, "%d\n", pid);
191 /* We're the child */
192 close(fileno(stdin)); /* making certain not-so-smart */
193 close(fileno(stdout)); /* system/run-shell commands happy */
194 if (err_file == NULL)
195 freopen("/dev/null", "w", stderr);
199 * Return the current locale and set it to "C". Should be free()d if
200 * done.
202 static char *
203 lc_numeric()
205 char *lc_orig;
206 char *lc = setlocale(LC_NUMERIC, NULL);
208 if ((lc_orig = malloc(strlen(lc) + 1)) == NULL)
209 OOM_ABORT;
210 strcpy(lc_orig, lc);
211 setlocale(LC_NUMERIC, "C");
212 return lc_orig;
216 * Set locale (back) to lc; free lc
218 static void
219 lc_numeric_free(char *lc)
221 setlocale(LC_NUMERIC, lc);
222 free(lc);
226 * Print a warning about a malformed command to stderr. Runs inside
227 * gtk_main().
229 static void
230 ign_cmd(GType type, const char *msg)
232 const char *name, *pad = " ";
234 if (type == G_TYPE_INVALID) {
235 name = "";
236 pad = "";
237 } else
238 name = g_type_name(type);
239 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
243 * Check if n is, or can be made, the name of a fifo, and put its
244 * struct stat into sb. Give up if n exists but is not a fifo.
246 static void
247 find_fifo(const char *n, struct stat *sb)
249 int fd;
251 if ((fd = open(n, O_RDONLY | O_NONBLOCK)) > -1) {
252 if (fstat(fd, sb) == 0 &&
253 S_ISFIFO(sb->st_mode) &&
254 fchmod(fd, 0600) == 0) {
255 fstat(fd, sb);
256 close(fd);
257 return;
259 bye(EXIT_FAILURE, stderr, "using pre-existing fifo %s: %s\n",
260 n, strerror(errno));
262 if (mkfifo(n, 0600) != 0)
263 bye(EXIT_FAILURE, stderr, "making fifo %s: %s\n",
264 n, strerror(errno));
265 find_fifo(n, sb);
268 static FILE *
269 open_fifo(const char *name, const char *fmode, FILE *fallback, int bmode)
271 FILE *s = NULL;
272 int fd;
273 struct stat sb1, sb2;
275 if (name == NULL)
276 s = fallback;
277 else {
278 find_fifo(name, &sb1);
279 /* TODO: O_RDWR on fifo is undefined in POSIX */
280 if (!((fd = open(name, O_RDWR)) > -1 &&
281 fstat(fd, &sb2) == 0 &&
282 sb1.st_mode == sb2.st_mode &&
283 sb1.st_ino == sb2.st_ino &&
284 sb1.st_dev == sb2.st_dev &&
285 (s = fdopen(fd, fmode)) != NULL))
286 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
287 name, fmode, strerror(errno));
289 setvbuf(s, NULL, bmode, 0);
290 return s;
294 * Create a log file if necessary, and open it. A name of "-"
295 * requests use of stderr.
297 static FILE *
298 open_log(const char *name)
300 FILE *s = NULL;
302 if (name == NULL)
303 return NULL;
304 if (eql(name, "-"))
305 return stderr;
306 if ((s = fopen(name, "a")) == NULL)
307 bye(EXIT_FAILURE, stderr, "opening log file %s: %s\n",
308 name, strerror(errno));
309 if (fchmod(fileno(s), 0600) < 0)
310 bye(EXIT_FAILURE, stderr, "setting permissions of %s: %s\n",
311 name, strerror(errno));
312 return s;
315 static void
316 rm_unless(FILE *forbidden, FILE *s, char *name)
318 if (s == forbidden)
319 return;
320 fclose(s);
321 unlink(name);
325 * Microseconds elapsed since start
327 static long int
328 usec_since(struct timespec *start)
330 struct timespec now;
332 clock_gettime(CLOCK_MONOTONIC, &now);
333 return (now.tv_sec - start->tv_sec) * 1e6 +
334 (now.tv_nsec - start->tv_nsec) / 1e3;
338 * Write log file
340 static void
341 log_msg(FILE *l, char *msg)
343 static char *old_msg;
344 static struct timespec start;
346 if (l == NULL) /* no logging */
347 return;
348 if (msg == NULL && old_msg == NULL)
349 fprintf(l, "##########\t##### (New Pipeglade session) #####\n");
350 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
351 fprintf(l, "%10ld\t%s\n", usec_since(&start), old_msg);
352 free(old_msg);
353 old_msg = NULL;
354 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
355 fprintf(l, "%10ld\t### (Idle) ###\n", usec_since(&start));
356 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
357 OOM_ABORT;
358 strcpy(old_msg, msg);
359 } else
360 ABORT;
361 clock_gettime(CLOCK_MONOTONIC, &start);
364 static bool
365 has_suffix(const char *s, const char *suffix)
367 int s_suf = strlen(s) - strlen(suffix);
369 if (s_suf < 0)
370 return false;
371 return eql(suffix, s + s_suf);
375 * Remove suffix from name; find the object named like this
377 static GObject *
378 obj_sans_suffix(GtkBuilder *builder, const char *suffix, const char *name)
380 char str[BUFLEN + 1] = {'\0'};
381 int str_l;
383 str_l = suffix - name;
384 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
385 return gtk_builder_get_object(builder, str);
389 * Read UI definition from ui_file; give up on errors
391 static GtkBuilder *
392 builder_from_file(char *ui_file)
394 GError *error = NULL;
395 GtkBuilder *b;
397 b = gtk_builder_new();
398 if (gtk_builder_add_from_file(b, ui_file, &error) == 0)
399 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
400 return b;
403 static const char *
404 widget_name(GtkBuildable *obj)
406 return gtk_buildable_get_name(obj);
410 * Get the main window; give up on errors
412 static GObject *
413 find_main_window(GtkBuilder *builder)
415 GObject *mw;
417 if (GTK_IS_WINDOW(mw = gtk_builder_get_object(builder, MAIN_WIN)))
418 return mw;
419 bye(EXIT_FAILURE, stderr, "no toplevel window named \'" MAIN_WIN "\'\n");
420 return NULL; /* NOT REACHED */
424 * Store a line from stream s into buf, which should have been malloc'd
425 * to bufsize. Enlarge buf and bufsize if necessary.
427 static size_t
428 read_buf(FILE *s, char **buf, size_t *bufsize)
430 bool esc = false;
431 fd_set rfds;
432 int c;
433 int ifd = fileno(s);
434 size_t i = 0;
436 FD_ZERO(&rfds);
437 FD_SET(ifd, &rfds);
438 for (;;) {
439 select(ifd + 1, &rfds, NULL, NULL, NULL);
440 c = getc(s);
441 if (c == '\n' || feof(s))
442 break;
443 if (i >= *bufsize - 1)
444 if ((*buf = realloc(*buf, *bufsize *= 2)) == NULL)
445 OOM_ABORT;
446 if (esc) {
447 esc = false;
448 switch (c) {
449 case 'n': (*buf)[i++] = '\n'; break;
450 case 'r': (*buf)[i++] = '\r'; break;
451 default: (*buf)[i++] = c; break;
453 } else if (c == '\\')
454 esc = true;
455 else
456 (*buf)[i++] = c;
458 (*buf)[i] = '\0';
459 return i;
464 * ============================================================
465 * Receiving feedback from the GUI
466 * ============================================================
469 static void
470 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
472 char *data;
473 const char *w_name = widget_name(obj);
474 fd_set wfds;
475 int ofd = fileno(o);
476 struct timeval timeout = {1, 0};
478 FD_ZERO(&wfds);
479 FD_SET(ofd, &wfds);
480 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
481 fprintf(o, "%s:%s ", w_name, tag);
482 while ((data = va_arg(ap, char *)) != NULL) {
483 size_t i = 0;
484 char c;
486 while ((c = data[i++]) != '\0')
487 if (c == '\\')
488 fprintf(o, "\\\\");
489 else if (c == '\n')
490 fprintf(o, "\\n");
491 else
492 putc(c, o);
494 putc('\n', o);
495 } else
496 fprintf(stderr,
497 "send error; discarding feedback message %s:%s\n",
498 w_name, tag);
502 * Send GUI feedback to stream o. The message format is
503 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
504 * last argument must be NULL.
506 static void
507 send_msg(FILE *o, GtkBuildable *obj, const char *tag, ...)
509 va_list ap;
511 va_start(ap, tag);
512 send_msg_to(o, obj, tag, ap);
513 va_end(ap);
517 * Send message from GUI to stream o. The message format is
518 * "<origin>:set <data ...>", which happens to be a legal command.
519 * The variadic arguments are strings; last argument must be NULL.
521 static void
522 send_msg_as_cmd(FILE *o, GtkBuildable *obj, const char *tag, ...)
524 va_list ap;
526 va_start(ap, tag);
527 send_msg_to(o, obj, "set", ap);
528 va_end(ap);
532 * Stuff to pass around
534 struct info {
535 FILE *outstr; /* UI feedback messages */
536 FILE *instr; /* command input */
537 FILE *logstr; /* logging output */
538 GtkBuilder *builder; /* to be read from .ui file */
539 GObject *obj;
540 GtkTreeModel *model;
541 char *txt;
545 * Return pointer to a newly allocated struct info
547 struct info *
548 info_new_full(FILE *stream, GObject *obj, GtkTreeModel *model, char *txt)
550 struct info *a;
552 if ((a = malloc(sizeof(struct info))) == NULL)
553 OOM_ABORT;
554 a->outstr = stream;
555 a->instr = NULL;
556 a->logstr = NULL;
557 a->builder = NULL;
558 a->obj = obj;
559 a->model = model;
560 a->txt = txt;
561 return a;
564 struct info *
565 info_txt_new(FILE *stream, char *txt)
567 return info_new_full(stream, NULL, NULL, txt);
570 struct info *
571 info_obj_new(FILE *stream, GObject *obj, GtkTreeModel *model)
573 return info_new_full(stream, obj, model, NULL);
577 * Use msg_sender() to send a message describing a particular cell
579 static void
580 send_tree_cell_msg_by(void msg_sender(FILE *, GtkBuildable *, const char *, ...),
581 const char *path_s,
582 GtkTreeIter *iter, int col, struct info *a)
584 GtkBuildable *obj = GTK_BUILDABLE(a->obj);
585 GtkTreeModel *model = a->model;
586 GType col_type;
587 GValue value = G_VALUE_INIT;
588 char str[BUFLEN], *lc = lc_numeric();
590 gtk_tree_model_get_value(model, iter, col, &value);
591 col_type = gtk_tree_model_get_column_type(model, col);
592 switch (col_type) {
593 case G_TYPE_INT:
594 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
595 msg_sender(a->outstr, obj, "gint", path_s, str, NULL);
596 break;
597 case G_TYPE_LONG:
598 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
599 msg_sender(a->outstr, obj, "glong", path_s, str, NULL);
600 break;
601 case G_TYPE_INT64:
602 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
603 msg_sender(a->outstr, obj, "gint64", path_s, str, NULL);
604 break;
605 case G_TYPE_UINT:
606 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
607 msg_sender(a->outstr, obj, "guint", path_s, str, NULL);
608 break;
609 case G_TYPE_ULONG:
610 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
611 msg_sender(a->outstr, obj, "gulong", path_s, str, NULL);
612 break;
613 case G_TYPE_UINT64:
614 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
615 msg_sender(a->outstr, obj, "guint64", path_s, str, NULL);
616 break;
617 case G_TYPE_BOOLEAN:
618 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
619 msg_sender(a->outstr, obj, "gboolean", path_s, str, NULL);
620 break;
621 case G_TYPE_FLOAT:
622 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
623 msg_sender(a->outstr, obj, "gfloat", path_s, str, NULL);
624 break;
625 case G_TYPE_DOUBLE:
626 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
627 msg_sender(a->outstr, obj, "gdouble", path_s, str, NULL);
628 break;
629 case G_TYPE_STRING:
630 snprintf(str, BUFLEN, " %d ", col);
631 msg_sender(a->outstr, obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
632 break;
633 default:
634 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
635 break;
637 g_value_unset(&value);
638 lc_numeric_free(lc);
642 * Use msg_sender() to send one message per column for a single row
644 static void
645 send_tree_row_msg_by(void msg_sender(FILE *, GtkBuildable *, const char *, ...),
646 char *path_s, GtkTreeIter *iter, struct info *args)
648 int col;
650 for (col = 0; col < gtk_tree_model_get_n_columns(args->model); col++)
651 send_tree_cell_msg_by(msg_sender, path_s, iter, col, args);
655 * send_tree_row_msg serves as an argument for
656 * gtk_tree_selection_selected_foreach()
658 static gboolean
659 send_tree_row_msg(GtkTreeModel *model,
660 GtkTreePath *path, GtkTreeIter *iter, struct info *args)
662 char *path_s = gtk_tree_path_to_string(path);
664 args->model = model;
665 send_tree_row_msg_by(send_msg, path_s, iter, args);
666 g_free(path_s);
667 return FALSE;
671 * save_tree_row_msg serves as an argument for
672 * gtk_tree_model_foreach().
673 * Send message from GUI to global stream "save".
675 static gboolean
676 save_tree_row_msg(GtkTreeModel *model,
677 GtkTreePath *path, GtkTreeIter *iter, struct info *args)
679 char *path_s = gtk_tree_path_to_string(path);
681 args->model = model;
682 send_tree_row_msg_by(send_msg_as_cmd, path_s, iter, args);
683 g_free(path_s);
684 return FALSE;
687 static void
688 cb_calendar(GtkBuildable *obj, struct info *a)
690 char str[BUFLEN];
691 unsigned int year = 0, month = 0, day = 0;
693 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
694 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
695 send_msg(a->outstr, obj, a->txt, str, NULL);
698 static void
699 cb_color_button(GtkBuildable *obj, struct info *a)
701 GdkRGBA color;
703 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
704 send_msg(a->outstr, obj, a->txt, gdk_rgba_to_string(&color), NULL);
707 static void
708 cb_editable(GtkBuildable *obj, struct info *a)
710 send_msg(a->outstr, obj, a->txt, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
714 * Callback that sends a message about a pointer device button press
715 * in a GtkEventBox
717 static bool
718 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, struct info *a)
720 char data[BUFLEN], *lc = lc_numeric();
722 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
723 e->button.button, e->button.x, e->button.y);
724 send_msg(a->outstr, obj, a->txt, data, NULL);
725 lc_numeric_free(lc);
726 return true;
730 * Callback that sends in a message the name of the key pressed when
731 * a GtkEventBox is focused
733 static bool
734 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, struct info *a)
736 send_msg(a->outstr, obj, a->txt, gdk_keyval_name(e->key.keyval), NULL);
737 return true;
741 * Callback that sends a message about pointer device motion in a
742 * GtkEventBox
744 static bool
745 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, struct info *a)
747 char data[BUFLEN], *lc = lc_numeric();
749 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
750 send_msg(a->outstr, obj, a->txt, data, NULL);
751 lc_numeric_free(lc);
752 return true;
756 * Callback that only sends "name:tag" and returns false
758 static bool
759 cb_event_simple(GtkBuildable *obj, GdkEvent *e, struct info *a)
761 (void) e;
762 send_msg(a->outstr, obj, a->txt, NULL);
763 return false;
766 static void
767 cb_file_chooser_button(GtkBuildable *obj, struct info *a)
769 send_msg(a->outstr, obj, a->txt,
770 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
773 static void
774 cb_font_button(GtkBuildable *obj, struct info *a)
776 send_msg(a->outstr, obj, a->txt,
777 gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
780 static void
781 cb_menu_item(GtkBuildable *obj, struct info *a)
783 send_msg(a->outstr, obj, a->txt,
784 gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
787 static void
788 cb_range(GtkBuildable *obj, struct info *a)
790 char str[BUFLEN], *lc = lc_numeric();
792 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
793 send_msg(a->outstr, obj, a->txt, str, NULL);
794 lc_numeric_free(lc);
798 * Callback that sends user's selection from a file dialog
800 static void
801 cb_send_file_chooser_dialog_selection(struct info *a)
803 send_msg(a->outstr, GTK_BUILDABLE(a->obj), "file",
804 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(a->obj)),
805 NULL);
806 send_msg(a->outstr, GTK_BUILDABLE(a->obj), "folder",
807 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(a->obj)),
808 NULL);
812 * Callback that sends in a message the content of the text buffer
813 * passed in user_data
815 static void
816 cb_send_text(GtkBuildable *obj, struct info *ar)
818 GtkTextIter a, b;
820 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(ar->obj), &a, &b);
821 send_msg(ar->outstr, obj, "text",
822 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar->obj), &a, &b, TRUE),
823 NULL);
827 * Callback that sends in a message the highlighted text from the text
828 * buffer which was passed in user_data
830 static void
831 cb_send_text_selection(GtkBuildable *obj, struct info *ar)
833 GtkTextIter a, b;
835 gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(ar->obj), &a, &b);
836 send_msg(ar->outstr, obj, "text",
837 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar->obj), &a, &b, TRUE),
838 NULL);
842 * Callback that only sends "name:tag" and returns true
844 static bool
845 cb_simple(GtkBuildable *obj, struct info *a)
847 send_msg(a->outstr, obj, a->txt, NULL);
848 return true;
851 static void
852 cb_spin_button(GtkBuildable *obj, struct info *a)
854 char str[BUFLEN], *lc = lc_numeric();
856 snprintf(str, BUFLEN, "%f", gtk_spin_button_get_value(GTK_SPIN_BUTTON(obj)));
857 send_msg(a->outstr, obj, a->txt, str, NULL);
858 lc_numeric_free(lc);
861 static void
862 cb_switch(GtkBuildable *obj, void *pspec, struct info *a)
864 (void) pspec;
865 send_msg(a->outstr, obj,
866 gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0",
867 NULL);
870 static void
871 cb_toggle_button(GtkBuildable *obj, struct info *a)
873 send_msg(a->outstr, obj,
874 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0",
875 NULL);
878 static void
879 cb_tree_selection(GtkBuildable *obj, struct info *a)
881 GtkTreeSelection *sel = GTK_TREE_SELECTION(obj);
882 GtkTreeView *view = gtk_tree_selection_get_tree_view(sel);
884 a->obj = G_OBJECT(view);
885 send_msg(a->outstr, GTK_BUILDABLE(view), a->txt, NULL);
886 gtk_tree_selection_selected_foreach(
887 sel, (GtkTreeSelectionForeachFunc) send_tree_row_msg, a);
892 * ============================================================
893 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
894 * helper functions
895 * ============================================================
899 * The set of supported drawing operations
901 enum cairo_fn {
902 ARC,
903 ARC_NEGATIVE,
904 CLOSE_PATH,
905 CURVE_TO,
906 FILL,
907 FILL_PRESERVE,
908 LINE_TO,
909 MOVE_TO,
910 RECTANGLE,
911 REL_CURVE_TO,
912 REL_LINE_TO,
913 REL_MOVE_TO,
914 REL_MOVE_FOR,
915 RESET_CTM,
916 SET_DASH,
917 SET_FONT_FACE,
918 SET_FONT_SIZE,
919 SET_LINE_CAP,
920 SET_LINE_JOIN,
921 SET_LINE_WIDTH,
922 SET_SOURCE_RGBA,
923 SHOW_TEXT,
924 STROKE,
925 STROKE_PRESERVE,
926 TRANSFORM,
930 * Text placement mode for rel_move_for()
932 enum ref_point {
944 enum draw_op_policy {
945 APPEND,
946 BEFORE,
947 REPLACE,
951 * One single element of a drawing
953 struct draw_op {
954 struct draw_op *next;
955 struct draw_op *prev;
956 unsigned long long int id;
957 unsigned long long int before;
958 enum draw_op_policy policy;
959 enum cairo_fn op;
960 void *op_args;
964 * Argument sets for the various drawing operations
966 struct arc_args {
967 double x;
968 double y;
969 double radius;
970 double angle1;
971 double angle2;
974 struct curve_to_args {
975 double x1;
976 double y1;
977 double x2;
978 double y2;
979 double x3;
980 double y3;
983 struct move_to_args {
984 double x;
985 double y;
988 struct rectangle_args {
989 double x;
990 double y;
991 double width;
992 double height;
995 struct rel_move_for_args {
996 enum ref_point ref;
997 int len;
998 char text[];
1001 struct set_dash_args {
1002 int num_dashes;
1003 double dashes[];
1006 struct set_font_face_args {
1007 cairo_font_slant_t slant;
1008 cairo_font_weight_t weight;
1009 char family[];
1012 struct set_font_size_args {
1013 double size;
1016 struct set_line_cap_args {
1017 cairo_line_cap_t line_cap;
1020 struct set_line_join_args {
1021 cairo_line_join_t line_join;
1024 struct set_line_width_args {
1025 double width;
1028 struct set_source_rgba_args {
1029 GdkRGBA color;
1032 struct show_text_args {
1033 int len;
1034 char text[];
1037 struct transform_args {
1038 cairo_matrix_t matrix;
1041 static void
1042 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
1044 switch (op) {
1045 case LINE_TO: {
1046 struct move_to_args *args = op_args;
1048 cairo_line_to(cr, args->x, args->y);
1049 break;
1051 case REL_LINE_TO: {
1052 struct move_to_args *args = op_args;
1054 cairo_rel_line_to(cr, args->x, args->y);
1055 break;
1057 case MOVE_TO: {
1058 struct move_to_args *args = op_args;
1060 cairo_move_to(cr, args->x, args->y);
1061 break;
1063 case REL_MOVE_TO: {
1064 struct move_to_args *args = op_args;
1066 cairo_rel_move_to(cr, args->x, args->y);
1067 break;
1069 case ARC: {
1070 struct arc_args *args = op_args;
1072 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1073 break;
1075 case ARC_NEGATIVE: {
1076 struct arc_args *args = op_args;
1078 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1079 break;
1081 case CURVE_TO: {
1082 struct curve_to_args *args = op_args;
1084 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1085 break;
1087 case REL_CURVE_TO: {
1088 struct curve_to_args *args = op_args;
1090 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1091 break;
1093 case RECTANGLE: {
1094 struct rectangle_args *args = op_args;
1096 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
1097 break;
1099 case CLOSE_PATH:
1100 cairo_close_path(cr);
1101 break;
1102 case SHOW_TEXT: {
1103 struct show_text_args *args = op_args;
1105 cairo_show_text(cr, args->text);
1106 break;
1108 case REL_MOVE_FOR: {
1109 cairo_text_extents_t e;
1110 double dx = 0.0, dy = 0.0;
1111 struct rel_move_for_args *args = op_args;
1113 cairo_text_extents(cr, args->text, &e);
1114 switch (args->ref) {
1115 case C: dx = -e.width / 2; dy = e.height / 2; break;
1116 case E: dx = -e.width; dy = e.height / 2; break;
1117 case N: dx = -e.width / 2; dy = e.height; break;
1118 case NE: dx = -e.width; dy = e.height; break;
1119 case NW: dy = e.height; break;
1120 case S: dx = -e.width / 2; break;
1121 case SE: dx = -e.width; break;
1122 case SW: break;
1123 case W: dy = e.height / 2; break;
1124 default: ABORT; break;
1126 cairo_rel_move_to(cr, dx, dy);
1127 break;
1129 case RESET_CTM:
1130 cairo_identity_matrix(cr);
1131 break;
1132 case STROKE:
1133 cairo_stroke(cr);
1134 break;
1135 case STROKE_PRESERVE:
1136 cairo_stroke_preserve(cr);
1137 break;
1138 case FILL:
1139 cairo_fill(cr);
1140 break;
1141 case FILL_PRESERVE:
1142 cairo_fill_preserve(cr);
1143 break;
1144 case SET_DASH: {
1145 struct set_dash_args *args = op_args;
1147 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1148 break;
1150 case SET_FONT_FACE: {
1151 struct set_font_face_args *args = op_args;
1153 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1154 break;
1156 case SET_FONT_SIZE: {
1157 struct set_font_size_args *args = op_args;
1159 cairo_set_font_size(cr, args->size);
1160 break;
1162 case SET_LINE_CAP: {
1163 struct set_line_cap_args *args = op_args;
1165 cairo_set_line_cap(cr, args->line_cap);
1166 break;
1168 case SET_LINE_JOIN: {
1169 struct set_line_join_args *args = op_args;
1171 cairo_set_line_join(cr, args->line_join);
1172 break;
1174 case SET_LINE_WIDTH: {
1175 struct set_line_width_args *args = op_args;
1177 cairo_set_line_width(cr, args->width);
1178 break;
1180 case SET_SOURCE_RGBA: {
1181 struct set_source_rgba_args *args = op_args;
1183 gdk_cairo_set_source_rgba(cr, &args->color);
1184 break;
1186 case TRANSFORM: {
1187 struct transform_args *args = op_args;
1189 cairo_transform(cr, &args->matrix);
1190 break;
1192 default:
1193 ABORT;
1194 break;
1199 * Callback that draws on a GtkDrawingArea
1201 static gboolean
1202 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1204 struct draw_op *op;
1206 (void) data;
1207 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1208 op != NULL;
1209 op = op->next)
1210 draw(cr, op->op, op->op_args);
1211 return FALSE;
1216 * ============================================================
1217 * Manipulating the GUI
1218 * ============================================================
1221 static void
1222 update_button(GObject *obj, const char *action, const char *data,
1223 const char *whole_msg, GType type, struct info *ar)
1225 (void) ar;
1226 if (eql(action, "set_label"))
1227 gtk_button_set_label(GTK_BUTTON(obj), data);
1228 else
1229 ign_cmd(type, whole_msg);
1232 static void
1233 update_calendar(GObject *obj, const char *action, const char *data,
1234 const char *whole_msg, GType type, struct info *ar)
1236 GtkCalendar *calendar = GTK_CALENDAR(obj);
1237 char dummy;
1238 int year = 0, month = 0, day = 0;
1240 (void) ar;
1241 if (eql(action, "select_date") &&
1242 sscanf(data, "%d-%d-%d %c", &year, &month, &day, &dummy) == 3) {
1243 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1244 gtk_calendar_select_month(calendar, --month, year);
1245 gtk_calendar_select_day(calendar, day);
1246 } else
1247 ign_cmd(type, whole_msg);
1248 } else if (eql(action, "mark_day") &&
1249 sscanf(data, "%d %c", &day, &dummy) == 1) {
1250 if (day > 0 && day <= 31)
1251 gtk_calendar_mark_day(calendar, day);
1252 else
1253 ign_cmd(type, whole_msg);
1254 } else if (eql(action, "clear_marks") && sscanf(data, " %c", &dummy) < 1)
1255 gtk_calendar_clear_marks(calendar);
1256 else
1257 ign_cmd(type, whole_msg);
1261 * Common actions for various kinds of window. Return false if
1262 * command is ignored. Runs inside gtk_main().
1264 static bool
1265 update_class_window(GObject *obj, const char *action, const char *data,
1266 const char *whole_msg, GType type, struct info *ar)
1268 GtkWindow *window = GTK_WINDOW(obj);
1269 char dummy;
1270 int x, y;
1272 (void) ar;
1273 (void) type;
1274 (void) whole_msg;
1275 if (eql(action, "set_title"))
1276 gtk_window_set_title(window, data);
1277 else if (eql(action, "fullscreen") && sscanf(data, " %c", &dummy) < 1)
1278 gtk_window_fullscreen(window);
1279 else if (eql(action, "unfullscreen") && sscanf(data, " %c", &dummy) < 1)
1280 gtk_window_unfullscreen(window);
1281 else if (eql(action, "resize") &&
1282 sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1283 gtk_window_resize(window, x, y);
1284 else if (eql(action, "resize") && sscanf(data, " %c", &dummy) < 1) {
1285 gtk_window_get_default_size(window, &x, &y);
1286 gtk_window_resize(window, x, y);
1287 } else if (eql(action, "move") &&
1288 sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1289 gtk_window_move(window, x, y);
1290 else
1291 return false;
1292 return true;
1295 static void
1296 update_color_button(GObject *obj, const char *action, const char *data,
1297 const char *whole_msg, GType type, struct info *ar)
1299 GdkRGBA color;
1301 (void) ar;
1302 if (eql(action, "set_color")) {
1303 gdk_rgba_parse(&color, data);
1304 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1305 } else
1306 ign_cmd(type, whole_msg);
1309 static void
1310 update_combo_box_text(GObject *obj, const char *action, const char *data,
1311 const char *whole_msg, GType type, struct info *ar)
1313 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1314 char data1[strlen(data) + 1];
1315 char dummy;
1316 int val;
1318 (void) ar;
1319 strcpy(data1, data);
1320 if (eql(action, "prepend_text"))
1321 gtk_combo_box_text_prepend_text(combobox, data1);
1322 else if (eql(action, "append_text"))
1323 gtk_combo_box_text_append_text(combobox, data1);
1324 else if (eql(action, "remove") && sscanf(data, "%d %c", &val, &dummy) == 1)
1325 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1326 else if (eql(action, "insert_text")) {
1327 char *position = strtok(data1, WHITESPACE);
1328 char *text = strtok(NULL, WHITESPACE);
1330 gtk_combo_box_text_insert_text(combobox,
1331 strtol(position, NULL, 10), text);
1332 } else
1333 ign_cmd(type, whole_msg);
1337 * update_drawing_area(), which runs inside gtk_main(), maintains a
1338 * list of drawing operations. It needs a few helper functions. It
1339 * is the responsibility of cb_draw() to actually execute the list.
1342 enum draw_op_stat {
1343 FAILURE,
1344 SUCCESS,
1345 NEED_REDRAW,
1349 * Fill structure *op with the drawing operation according to action
1350 * and with the appropriate set of arguments
1352 static enum draw_op_stat
1353 set_draw_op(struct draw_op *op, const char *action, const char *data)
1355 char dummy;
1356 const char *raw_args = data;
1357 enum draw_op_stat result = SUCCESS;
1358 int args_start = 0;
1360 if (sscanf(data, "=%llu %n", &op->id, &args_start) == 1) {
1361 op->policy = REPLACE;
1362 result = NEED_REDRAW;
1363 } else if (sscanf(data, "%llu<%llu %n", &op->id, &op->before, &args_start) == 2) {
1364 op->policy = BEFORE;
1365 result = NEED_REDRAW;
1366 } else if (sscanf(data, "%llu %n", &op->id, &args_start) == 1)
1367 op->policy = APPEND;
1368 else
1369 return FAILURE;
1370 raw_args += args_start;
1371 if (eql(action, "line_to")) {
1372 struct move_to_args *args;
1374 if ((args = malloc(sizeof(*args))) == NULL)
1375 OOM_ABORT;
1376 op->op = LINE_TO;
1377 op->op_args = args;
1378 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1379 return FAILURE;
1380 } else if (eql(action, "rel_line_to")) {
1381 struct move_to_args *args;
1383 if ((args = malloc(sizeof(*args))) == NULL)
1384 OOM_ABORT;
1385 op->op = REL_LINE_TO;
1386 op->op_args = args;
1387 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1388 return FAILURE;
1389 } else if (eql(action, "move_to")) {
1390 struct move_to_args *args;
1392 if ((args = malloc(sizeof(*args))) == NULL)
1393 OOM_ABORT;
1394 op->op = MOVE_TO;
1395 op->op_args = args;
1396 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1397 return FAILURE;
1398 } else if (eql(action, "rel_move_to")) {
1399 struct move_to_args *args;
1401 if ((args = malloc(sizeof(*args))) == NULL)
1402 OOM_ABORT;
1403 op->op = REL_MOVE_TO;
1404 op->op_args = args;
1405 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1406 return FAILURE;
1407 } else if (eql(action, "arc")) {
1408 struct arc_args *args;
1409 double deg1, deg2;
1411 if ((args = malloc(sizeof(*args))) == NULL)
1412 OOM_ABORT;
1413 op->op = ARC;
1414 op->op_args = args;
1415 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1416 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1417 return FAILURE;
1418 args->angle1 = deg1 * (M_PI / 180.L);
1419 args->angle2 = deg2 * (M_PI / 180.L);
1420 } else if (eql(action, "arc_negative")) {
1421 double deg1, deg2;
1422 struct arc_args *args;
1424 if ((args = malloc(sizeof(*args))) == NULL)
1425 OOM_ABORT;
1426 op->op = ARC_NEGATIVE;
1427 op->op_args = args;
1428 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1429 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1430 return FAILURE;
1431 args->angle1 = deg1 * (M_PI / 180.L);
1432 args->angle2 = deg2 * (M_PI / 180.L);
1433 } else if (eql(action, "curve_to")) {
1434 struct curve_to_args *args;
1436 if ((args = malloc(sizeof(*args))) == NULL)
1437 OOM_ABORT;
1438 op->op = CURVE_TO;
1439 op->op_args = args;
1440 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1441 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1442 return FAILURE;
1443 } else if (eql(action, "rel_curve_to")) {
1444 struct curve_to_args *args;
1446 if ((args = malloc(sizeof(*args))) == NULL)
1447 OOM_ABORT;
1448 op->op = REL_CURVE_TO;
1449 op->op_args = args;
1450 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1451 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1452 return FAILURE;
1453 } else if (eql(action, "rectangle")) {
1454 struct rectangle_args *args;
1456 if ((args = malloc(sizeof(*args))) == NULL)
1457 OOM_ABORT;
1458 op->op = RECTANGLE;
1459 op->op_args = args;
1460 if (sscanf(raw_args, "%lf %lf %lf %lf %c",
1461 &args->x, &args->y, &args->width, &args->height, &dummy) != 4)
1462 return FAILURE;
1463 } else if (eql(action, "close_path")) {
1464 op->op = CLOSE_PATH;
1465 if (sscanf(raw_args, " %c", &dummy) > 0)
1466 return FAILURE;
1467 op->op_args = NULL;
1468 } else if (eql(action, "show_text")) {
1469 struct show_text_args *args;
1470 int len;
1472 len = strlen(raw_args) + 1;
1473 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1474 OOM_ABORT;
1475 op->op = SHOW_TEXT;
1476 op->op_args = args;
1477 args->len = len; /* not used */
1478 strncpy(args->text, raw_args, len);
1479 result = NEED_REDRAW;
1480 } else if (eql(action, "rel_move_for")) {
1481 char ref_point[2 + 1];
1482 int start, len;
1483 struct rel_move_for_args *args;
1485 if (sscanf(raw_args, "%2s %n", ref_point, &start) < 1)
1486 return FAILURE;
1487 len = strlen(raw_args + start) + 1;
1488 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1489 OOM_ABORT;
1490 if (eql(ref_point, "c"))
1491 args->ref = C;
1492 else if (eql(ref_point, "e"))
1493 args->ref = E;
1494 else if (eql(ref_point, "n"))
1495 args->ref = N;
1496 else if (eql(ref_point, "ne"))
1497 args->ref = NE;
1498 else if (eql(ref_point, "nw"))
1499 args->ref = NW;
1500 else if (eql(ref_point, "s"))
1501 args->ref = S;
1502 else if (eql(ref_point, "se"))
1503 args->ref = SE;
1504 else if (eql(ref_point, "sw"))
1505 args->ref = SW;
1506 else if (eql(ref_point, "w"))
1507 args->ref = W;
1508 else
1509 return FAILURE;
1510 op->op = REL_MOVE_FOR;
1511 op->op_args = args;
1512 args->len = len; /* not used */
1513 strncpy(args->text, (raw_args + start), len);
1514 } else if (eql(action, "stroke")) {
1515 op->op = STROKE;
1516 if (sscanf(raw_args, " %c", &dummy) > 0)
1517 return FAILURE;
1518 op->op_args = NULL;
1519 result = NEED_REDRAW;
1520 } else if (eql(action, "stroke_preserve")) {
1521 op->op = STROKE_PRESERVE;
1522 if (sscanf(raw_args, " %c", &dummy) > 0)
1523 return FAILURE;
1524 op->op_args = NULL;
1525 result = NEED_REDRAW;
1526 } else if (eql(action, "fill")) {
1527 op->op = FILL;
1528 if (sscanf(raw_args, " %c", &dummy) > 0)
1529 return FAILURE;
1530 op->op_args = NULL;
1531 result = NEED_REDRAW;
1532 } else if (eql(action, "fill_preserve")) {
1533 op->op = FILL_PRESERVE;
1534 if (sscanf(raw_args, " %c", &dummy) > 0)
1535 return FAILURE;
1536 op->op_args = NULL;
1537 result = NEED_REDRAW;
1538 } else if (eql(action, "set_dash")) {
1539 char *next, *end;
1540 char data1[strlen(raw_args) + 1];
1541 int n, i;
1542 struct set_dash_args *args;
1544 strcpy(data1, raw_args);
1545 next = end = data1;
1546 n = -1;
1547 do {
1548 n++;
1549 next = end;
1550 strtod(next, &end);
1551 } while (next != end);
1552 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1553 OOM_ABORT;
1554 op->op = SET_DASH;
1555 op->op_args = args;
1556 args->num_dashes = n;
1557 for (i = 0, next = data1; i < n; i++, next = end) {
1558 args->dashes[i] = strtod(next, &end);
1560 } else if (eql(action, "set_font_face")) {
1561 char slant[7 + 1]; /* "oblique" */
1562 char weight[6 + 1]; /* "normal" */
1563 int family_start, family_len;
1564 struct set_font_face_args *args;
1566 if (sscanf(raw_args, "%7s %6s %n%*s", slant, weight, &family_start) != 2)
1567 return FAILURE;
1568 family_len = strlen(raw_args + family_start) + 1;
1569 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1570 OOM_ABORT;
1571 op->op = SET_FONT_FACE;
1572 op->op_args = args;
1573 strncpy(args->family, raw_args + family_start, family_len);
1574 if (eql(slant, "normal"))
1575 args->slant = CAIRO_FONT_SLANT_NORMAL;
1576 else if (eql(slant, "italic"))
1577 args->slant = CAIRO_FONT_SLANT_ITALIC;
1578 else if (eql(slant, "oblique"))
1579 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1580 else
1581 return FAILURE;
1582 if (eql(weight, "normal"))
1583 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1584 else if (eql(weight, "bold"))
1585 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1586 else
1587 return FAILURE;
1588 } else if (eql(action, "set_font_size")) {
1589 struct set_font_size_args *args;
1591 if ((args = malloc(sizeof(*args))) == NULL)
1592 OOM_ABORT;
1593 op->op = SET_FONT_SIZE;
1594 op->op_args = args;
1595 if (sscanf(raw_args, "%lf %c", &args->size, &dummy) != 1)
1596 return FAILURE;
1597 } else if (eql(action, "set_line_cap")) {
1598 char str[6 + 1]; /* "square" */
1599 struct set_line_cap_args *args;
1601 if ((args = malloc(sizeof(*args))) == NULL)
1602 OOM_ABORT;
1603 op->op = SET_LINE_CAP;
1604 op->op_args = args;
1605 if (sscanf(raw_args, "%6s %c", str, &dummy) != 1)
1606 return FAILURE;
1607 if (eql(str, "butt"))
1608 args->line_cap = CAIRO_LINE_CAP_BUTT;
1609 else if (eql(str, "round"))
1610 args->line_cap = CAIRO_LINE_CAP_ROUND;
1611 else if (eql(str, "square"))
1612 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1613 else
1614 return FAILURE;
1615 } else if (eql(action, "set_line_join")) {
1616 char str[5 + 1]; /* "miter" */
1617 struct set_line_join_args *args;
1619 if ((args = malloc(sizeof(*args))) == NULL)
1620 OOM_ABORT;
1621 op->op = SET_LINE_JOIN;
1622 op->op_args = args;
1623 if (sscanf(raw_args, "%5s %c", str, &dummy) != 1)
1624 return FAILURE;
1625 if (eql(str, "miter"))
1626 args->line_join = CAIRO_LINE_JOIN_MITER;
1627 else if (eql(str, "round"))
1628 args->line_join = CAIRO_LINE_JOIN_ROUND;
1629 else if (eql(str, "bevel"))
1630 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1631 else
1632 return FAILURE;
1633 } else if (eql(action, "set_line_width")) {
1634 struct set_line_width_args *args;
1636 if ((args = malloc(sizeof(*args))) == NULL)
1637 OOM_ABORT;
1638 op->op = SET_LINE_WIDTH;
1639 op->op_args = args;
1640 if (sscanf(raw_args, "%lf %c", &args->width, &dummy) != 1)
1641 return FAILURE;
1642 } else if (eql(action, "set_source_rgba")) {
1643 struct set_source_rgba_args *args;
1645 if ((args = malloc(sizeof(*args))) == NULL)
1646 OOM_ABORT;
1647 op->op = SET_SOURCE_RGBA;
1648 op->op_args = args;
1649 gdk_rgba_parse(&args->color, raw_args);
1650 } else if (eql(action, "transform")) {
1651 char dummy;
1652 double xx, yx, xy, yy, x0, y0;
1654 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1655 &xx, &yx, &xy, &yy, &x0, &y0, &dummy) == 6) {
1656 struct transform_args *args;
1658 if ((args = malloc(sizeof(*args))) == NULL)
1659 OOM_ABORT;
1660 op->op_args = args;
1661 op->op = TRANSFORM;
1662 cairo_matrix_init(&args->matrix, xx, yx, xy, yy, x0, y0);
1663 } else if (sscanf(raw_args, " %c", &dummy) < 1) {
1664 op->op = RESET_CTM;
1665 op->op_args = NULL;
1666 } else
1667 return FAILURE;
1668 } else if (eql(action, "translate")) {
1669 double tx, ty;
1670 struct transform_args *args;
1672 if ((args = malloc(sizeof(*args))) == NULL)
1673 OOM_ABORT;
1674 op->op = TRANSFORM;
1675 op->op_args = args;
1676 if (sscanf(raw_args, "%lf %lf %c", &tx, &ty, &dummy) != 2)
1677 return FAILURE;
1678 cairo_matrix_init_translate(&args->matrix, tx, ty);
1679 } else if (eql(action, "scale")) {
1680 double sx, sy;
1681 struct transform_args *args;
1683 if ((args = malloc(sizeof(*args))) == NULL)
1684 OOM_ABORT;
1685 op->op = TRANSFORM;
1686 op->op_args = args;
1687 if (sscanf(raw_args, "%lf %lf %c", &sx, &sy, &dummy) != 2)
1688 return FAILURE;
1689 cairo_matrix_init_scale(&args->matrix, sx, sy);
1690 } else if (eql(action, "rotate")) {
1691 double angle;
1692 struct transform_args *args;
1694 if ((args = malloc(sizeof(*args))) == NULL)
1695 OOM_ABORT;
1696 op->op = TRANSFORM;
1697 op->op_args = args;
1698 if (sscanf(raw_args, "%lf %c", &angle, &dummy) != 1)
1699 return FAILURE;
1700 cairo_matrix_init_rotate(&args->matrix, angle * (M_PI / 180.L));
1701 } else
1702 return FAILURE;
1703 return result;
1707 * Add another element to widget's "draw_ops" list
1709 static enum draw_op_stat
1710 ins_draw_op(GObject *widget, const char *action, const char *data)
1712 enum draw_op_stat result;
1713 struct draw_op *new_op = NULL, *draw_ops = NULL, *prev_op = NULL;
1715 if ((new_op = malloc(sizeof(*new_op))) == NULL)
1716 OOM_ABORT;
1717 new_op->op_args = NULL;
1718 new_op->next = NULL;
1719 if ((result = set_draw_op(new_op, action, data)) == FAILURE) {
1720 free(new_op->op_args);
1721 free(new_op);
1722 return FAILURE;
1724 switch (new_op->policy) {
1725 case APPEND:
1726 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1727 g_object_set_data(widget, "draw_ops", new_op);
1728 else {
1729 for (prev_op = draw_ops;
1730 prev_op->next != NULL;
1731 prev_op = prev_op->next);
1732 prev_op->next = new_op;
1734 break;
1735 case BEFORE:
1736 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1737 draw_ops != NULL && draw_ops->id != new_op->before;
1738 prev_op = draw_ops, draw_ops = draw_ops->next);
1739 if (prev_op == NULL) { /* prepend a new first element */
1740 g_object_set_data(widget, "draw_ops", new_op);
1741 new_op->next = draw_ops;
1742 } else if (draw_ops == NULL) /* append */
1743 prev_op->next = new_op;
1744 else { /* insert */
1745 new_op->next = draw_ops;
1746 prev_op->next = new_op;
1748 break;
1749 case REPLACE:
1750 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1751 draw_ops != NULL && draw_ops->id != new_op->id;
1752 prev_op = draw_ops, draw_ops = draw_ops->next);
1753 if (draw_ops == NULL && prev_op == NULL) /* start a new list */
1754 g_object_set_data(widget, "draw_ops", new_op);
1755 else if (prev_op == NULL) { /* replace the first element */
1756 g_object_set_data(widget, "draw_ops", new_op);
1757 new_op->next = draw_ops->next;
1758 free(draw_ops->op_args);
1759 free(draw_ops);
1760 } else if (draw_ops == NULL) /* append */
1761 prev_op->next = new_op;
1762 else { /* replace some other element */
1763 new_op->next = draw_ops->next;
1764 prev_op->next = new_op;
1765 free(draw_ops->op_args);
1766 free(draw_ops);
1768 break;
1769 default:
1770 ABORT;
1771 break;
1773 return result;
1777 * Remove all elements with the given id from widget's "draw_ops" list
1779 static enum draw_op_stat
1780 rem_draw_op(GObject *widget, const char *data)
1782 char dummy;
1783 struct draw_op *op, *next_op, *prev_op = NULL;
1784 unsigned long long int id;
1786 if (sscanf(data, "%llu %c", &id, &dummy) != 1)
1787 return FAILURE;
1788 op = g_object_get_data(widget, "draw_ops");
1789 while (op != NULL) {
1790 next_op = op->next;
1791 if (op->id == id) {
1792 if (prev_op == NULL) /* list head */
1793 g_object_set_data(widget, "draw_ops", op->next);
1794 else
1795 prev_op->next = op->next;
1796 free(op->op_args);
1797 free(op);
1798 } else
1799 prev_op = op;
1800 op = next_op;
1802 return NEED_REDRAW;
1805 static gboolean
1806 refresh_widget(GtkWidget *widget)
1808 gint height = gtk_widget_get_allocated_height(widget);
1809 gint width = gtk_widget_get_allocated_width(widget);
1811 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1812 return G_SOURCE_REMOVE;
1816 * Write the drawing from the GtkDrawingArea widget in an appropriate
1817 * format to file
1819 static enum draw_op_stat
1820 save_drawing(GtkWidget *widget, const char *fn)
1822 cairo_surface_t *sur;
1823 cairo_t *cr;
1824 int height = gtk_widget_get_allocated_height(widget);
1825 int width = gtk_widget_get_allocated_width(widget);
1827 if (has_suffix(fn, ".epsf") || has_suffix(fn, ".eps")) {
1828 sur = cairo_ps_surface_create(fn, width, height);
1829 cairo_ps_surface_set_eps(sur, TRUE);
1830 } else if (has_suffix(fn, ".pdf"))
1831 sur = cairo_pdf_surface_create(fn, width, height);
1832 else if (has_suffix(fn, ".ps"))
1833 sur = cairo_ps_surface_create(fn, width, height);
1834 else if (has_suffix(fn, ".svg"))
1835 sur = cairo_svg_surface_create(fn, width, height);
1836 else
1837 return FAILURE;
1838 cr = cairo_create(sur);
1839 cb_draw(widget, cr, NULL);
1840 cairo_destroy(cr);
1841 cairo_surface_destroy(sur);
1842 return SUCCESS;
1845 static void
1846 update_drawing_area(GObject *obj, const char *action, const char *data,
1847 const char *whole_msg, GType type, struct info *ar)
1849 enum draw_op_stat dost;
1851 (void) ar;
1852 if (eql(action, "remove"))
1853 dost = rem_draw_op(obj, data);
1854 else if (eql(action, "save"))
1855 dost = save_drawing(GTK_WIDGET(obj), data);
1856 else
1857 dost = ins_draw_op(obj, action, data);
1858 switch (dost) {
1859 case NEED_REDRAW:
1860 gdk_threads_add_idle_full(G_PRIORITY_LOW,
1861 (GSourceFunc) refresh_widget,
1862 GTK_WIDGET(obj), NULL);
1863 break;
1864 case FAILURE:
1865 ign_cmd(type, whole_msg);
1866 break;
1867 case SUCCESS:
1868 break;
1869 default:
1870 ABORT;
1871 break;
1875 static void
1876 update_entry(GObject *obj, const char *action, const char *data,
1877 const char *whole_msg, GType type, struct info *ar)
1879 GtkEntry *entry = GTK_ENTRY(obj);
1881 (void) ar;
1882 if (eql(action, "set_text"))
1883 gtk_entry_set_text(entry, data);
1884 else if (eql(action, "set_placeholder_text"))
1885 gtk_entry_set_placeholder_text(entry, data);
1886 else
1887 ign_cmd(type, whole_msg);
1890 static void
1891 update_expander(GObject *obj, const char *action, const char *data,
1892 const char *whole_msg, GType type, struct info *ar)
1894 GtkExpander *expander = GTK_EXPANDER(obj);
1895 char dummy;
1896 unsigned int val;
1898 (void) ar;
1899 if (eql(action, "set_expanded") &&
1900 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
1901 gtk_expander_set_expanded(expander, val);
1902 else if (eql(action, "set_label"))
1903 gtk_expander_set_label(expander, data);
1904 else
1905 ign_cmd(type, whole_msg);
1908 static void
1909 update_file_chooser_button(GObject *obj, const char *action, const char *data,
1910 const char *whole_msg, GType type, struct info *ar)
1912 (void) ar;
1913 if (eql(action, "set_filename"))
1914 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1915 else
1916 ign_cmd(type, whole_msg);
1919 static void
1920 update_file_chooser_dialog(GObject *obj, const char *action, const char *data,
1921 const char *whole_msg, GType type, struct info *ar)
1923 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1925 (void) ar;
1926 if (eql(action, "set_filename"))
1927 gtk_file_chooser_set_filename(chooser, data);
1928 else if (eql(action, "set_current_name"))
1929 gtk_file_chooser_set_current_name(chooser, data);
1930 else if (update_class_window(obj, action, data, whole_msg, type, NULL));
1931 else
1932 ign_cmd(type, whole_msg);
1935 static void
1936 update_focus(GObject *obj, const char *action, const char *data,
1937 const char *whole_msg, GType type, struct info *ar)
1939 char dummy;
1941 (void) ar;
1942 (void) action;
1943 (void) data;
1944 if (sscanf(data, " %c", &dummy) < 1 &&
1945 gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1946 gtk_widget_grab_focus(GTK_WIDGET(obj));
1947 else
1948 ign_cmd(type, whole_msg);
1951 static void
1952 update_font_button(GObject *obj, const char *action, const char *data,
1953 const char *whole_msg, GType type, struct info *ar)
1955 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1957 (void) ar;
1958 if (eql(action, "set_font_name"))
1959 gtk_font_button_set_font_name(font_button, data);
1960 else
1961 ign_cmd(type, whole_msg);
1964 static void
1965 update_frame(GObject *obj, const char *action, const char *data,
1966 const char *whole_msg, GType type, struct info *ar)
1968 (void) ar;
1969 if (eql(action, "set_label"))
1970 gtk_frame_set_label(GTK_FRAME(obj), data);
1971 else
1972 ign_cmd(type, whole_msg);
1975 static void
1976 update_image(GObject *obj, const char *action, const char *data,
1977 const char *whole_msg, GType type, struct info *ar)
1979 GtkIconSize size;
1980 GtkImage *image = GTK_IMAGE(obj);
1982 (void) ar;
1983 gtk_image_get_icon_name(image, NULL, &size);
1984 if (eql(action, "set_from_file"))
1985 gtk_image_set_from_file(image, data);
1986 else if (eql(action, "set_from_icon_name"))
1987 gtk_image_set_from_icon_name(image, data, size);
1988 else
1989 ign_cmd(type, whole_msg);
1992 static void
1993 update_label(GObject *obj, const char *action, const char *data,
1994 const char *whole_msg, GType type, struct info *ar)
1996 (void) ar;
1997 if (eql(action, "set_text"))
1998 gtk_label_set_text(GTK_LABEL(obj), data);
1999 else
2000 ign_cmd(type, whole_msg);
2003 static void
2004 update_notebook(GObject *obj, const char *action, const char *data,
2005 const char *whole_msg, GType type, struct info *ar)
2007 char dummy;
2008 int val, n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(obj));
2010 (void) ar;
2011 if (eql(action, "set_current_page") &&
2012 sscanf(data, "%d %c", &val, &dummy) == 1 &&
2013 val >= 0 && val < n_pages)
2014 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), val);
2015 else
2016 ign_cmd(type, whole_msg);
2019 static void
2020 update_nothing(GObject *obj, const char *action, const char *data,
2021 const char *whole_msg, GType type, struct info *ar)
2023 (void) action;
2024 (void) ar;
2025 (void) data;
2026 (void) obj;
2027 (void) type;
2028 (void) whole_msg;
2031 static void
2032 update_print_dialog(GObject *obj, const char *action, const char *data,
2033 const char *whole_msg, GType type, struct info *ar)
2035 GtkPageSetup *page_setup;
2036 GtkPrintJob *job;
2037 GtkPrintSettings *settings;
2038 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
2039 GtkPrinter *printer;
2040 gint response_id;
2042 (void) ar;
2043 if (eql(action, "print")) {
2044 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
2045 switch (response_id) {
2046 case GTK_RESPONSE_OK:
2047 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
2048 settings = gtk_print_unix_dialog_get_settings(dialog);
2049 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
2050 job = gtk_print_job_new(data, printer, settings, page_setup);
2051 if (gtk_print_job_set_source_file(job, data, NULL))
2052 gtk_print_job_send(job, NULL, NULL, NULL);
2053 else
2054 ign_cmd(type, whole_msg);
2055 g_clear_object(&settings);
2056 g_clear_object(&job);
2057 break;
2058 case GTK_RESPONSE_CANCEL:
2059 case GTK_RESPONSE_DELETE_EVENT:
2060 break;
2061 default:
2062 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
2063 widget_name(GTK_BUILDABLE(dialog)), response_id);
2064 break;
2066 gtk_widget_hide(GTK_WIDGET(dialog));
2067 } else
2068 ign_cmd(type, whole_msg);
2071 static void
2072 update_progress_bar(GObject *obj, const char *action, const char *data,
2073 const char *whole_msg, GType type, struct info *ar)
2075 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
2076 char dummy;
2077 double frac;
2079 (void) ar;
2080 if (eql(action, "set_text"))
2081 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
2082 else if (eql(action, "set_fraction") &&
2083 sscanf(data, "%lf %c", &frac, &dummy) == 1)
2084 gtk_progress_bar_set_fraction(progressbar, frac);
2085 else
2086 ign_cmd(type, whole_msg);
2089 static void
2090 update_scale(GObject *obj, const char *action, const char *data,
2091 const char *whole_msg, GType type, struct info *ar)
2093 GtkRange *range = GTK_RANGE(obj);
2094 char dummy;
2095 double val1, val2;
2097 (void) ar;
2098 if (eql(action, "set_value") && sscanf(data, "%lf %c", &val1, &dummy) == 1)
2099 gtk_range_set_value(range, val1);
2100 else if (eql(action, "set_fill_level") &&
2101 sscanf(data, "%lf %c", &val1, &dummy) == 1) {
2102 gtk_range_set_fill_level(range, val1);
2103 gtk_range_set_show_fill_level(range, TRUE);
2104 } else if (eql(action, "set_fill_level") &&
2105 sscanf(data, " %c", &dummy) < 1)
2106 gtk_range_set_show_fill_level(range, FALSE);
2107 else if (eql(action, "set_range") &&
2108 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2109 gtk_range_set_range(range, val1, val2);
2110 else if (eql(action, "set_increments") &&
2111 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2112 gtk_range_set_increments(range, val1, val2);
2113 else
2114 ign_cmd(type, whole_msg);
2117 static void
2118 update_scrolled_window(GObject *obj, const char *action, const char *data,
2119 const char *whole_msg, GType type, struct info *ar)
2121 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
2122 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
2123 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
2124 char dummy;
2125 double d0, d1;
2127 (void) ar;
2128 if (eql(action, "hscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
2129 gtk_adjustment_set_value(hadj, d0);
2130 else if (eql(action, "vscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
2131 gtk_adjustment_set_value(vadj, d0);
2132 else if (eql(action, "hscroll_to_range") &&
2133 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2134 gtk_adjustment_clamp_page(hadj, d0, d1);
2135 else if (eql(action, "vscroll_to_range") &&
2136 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2137 gtk_adjustment_clamp_page(vadj, d0, d1);
2138 else
2139 ign_cmd(type, whole_msg);
2142 static void
2143 update_sensitivity(GObject *obj, const char *action, const char *data,
2144 const char *whole_msg, GType type, struct info *ar)
2146 char dummy;
2147 unsigned int val;
2149 (void) action;
2150 (void) ar;
2151 (void) type;
2152 (void) whole_msg;
2153 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2154 gtk_widget_set_sensitive(GTK_WIDGET(obj), val);
2155 else
2156 ign_cmd(type, whole_msg);
2159 static void
2160 update_size_request(GObject *obj, const char *action, const char *data,
2161 const char *whole_msg, GType type, struct info *ar)
2163 char dummy;
2164 int x, y;
2166 (void) action;
2167 (void) ar;
2168 (void) type;
2169 (void) whole_msg;
2170 if (sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
2171 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
2172 else if (sscanf(data, " %c", &dummy) < 1)
2173 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
2174 else
2175 ign_cmd(type, whole_msg);
2178 static void
2179 update_socket(GObject *obj, const char *action, const char *data,
2180 const char *whole_msg, GType type, struct info *ar)
2182 GtkSocket *socket = GTK_SOCKET(obj);
2183 Window id;
2184 char str[BUFLEN], dummy;
2186 (void) data;
2187 if (eql(action, "id") && sscanf(data, " %c", &dummy) < 1) {
2188 id = gtk_socket_get_id(socket);
2189 snprintf(str, BUFLEN, "%lu", id);
2190 send_msg(ar->outstr, GTK_BUILDABLE(socket), "id", str, NULL);
2191 } else
2192 ign_cmd(type, whole_msg);
2195 static void
2196 update_spin_button(GObject *obj, const char *action, const char *data,
2197 const char *whole_msg, GType type, struct info *ar)
2199 GtkSpinButton *spinbutton = GTK_SPIN_BUTTON(obj);
2200 char dummy;
2201 double val1, val2;
2203 (void) ar;
2204 if (eql(action, "set_text") && /* TODO: rename to "set_value" */
2205 sscanf(data, "%lf %c", &val1, &dummy) == 1)
2206 gtk_spin_button_set_value(spinbutton, val1);
2207 else if (eql(action, "set_range") &&
2208 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2209 gtk_spin_button_set_range(spinbutton, val1, val2);
2210 else if (eql(action, "set_increments") &&
2211 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2212 gtk_spin_button_set_increments(spinbutton, val1, val2);
2213 else
2214 ign_cmd(type, whole_msg);
2217 static void
2218 update_spinner(GObject *obj, const char *action, const char *data,
2219 const char *whole_msg, GType type, struct info *ar)
2221 GtkSpinner *spinner = GTK_SPINNER(obj);
2222 char dummy;
2224 (void) ar;
2225 (void) data;
2226 if (eql(action, "start") && sscanf(data, " %c", &dummy) < 1)
2227 gtk_spinner_start(spinner);
2228 else if (eql(action, "stop") && sscanf(data, " %c", &dummy) < 1)
2229 gtk_spinner_stop(spinner);
2230 else
2231 ign_cmd(type, whole_msg);
2234 static void
2235 update_statusbar(GObject *obj, const char *action, const char *data,
2236 const char *whole_msg, GType type, struct info *ar)
2238 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
2239 char *ctx_msg, dummy;
2240 const char *msg;
2241 int ctx_len, t;
2243 (void) ar;
2244 /* TODO: remove "push", "pop", "remove_all"; rename "push_id" to "push", etc. */
2245 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
2246 OOM_ABORT;
2247 t = sscanf(data, "%s %n%c", ctx_msg, &ctx_len, &dummy);
2248 msg = data + ctx_len;
2249 if (eql(action, "push"))
2250 gtk_statusbar_push(statusbar,
2251 gtk_statusbar_get_context_id(statusbar, "0"),
2252 data);
2253 else if (eql(action, "push_id") && t >= 1)
2254 gtk_statusbar_push(statusbar,
2255 gtk_statusbar_get_context_id(statusbar, ctx_msg),
2256 msg);
2257 else if (eql(action, "pop") && t < 1)
2258 gtk_statusbar_pop(statusbar,
2259 gtk_statusbar_get_context_id(statusbar, "0"));
2260 else if (eql(action, "pop_id") && t == 1)
2261 gtk_statusbar_pop(statusbar,
2262 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2263 else if (eql(action, "remove_all") && t < 1)
2264 gtk_statusbar_remove_all(statusbar,
2265 gtk_statusbar_get_context_id(statusbar, "0"));
2266 else if (eql(action, "remove_all_id") && t == 1)
2267 gtk_statusbar_remove_all(statusbar,
2268 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2269 else
2270 ign_cmd(type, whole_msg);
2271 free(ctx_msg);
2274 static void
2275 update_switch(GObject *obj, const char *action, const char *data,
2276 const char *whole_msg, GType type, struct info *ar)
2278 char dummy;
2279 unsigned int val;
2281 (void) ar;
2282 if (eql(action, "set_active") &&
2283 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2284 gtk_switch_set_active(GTK_SWITCH(obj), val);
2285 else
2286 ign_cmd(type, whole_msg);
2289 static void
2290 update_text_view(GObject *obj, const char *action, const char *data,
2291 const char *whole_msg, GType type, struct info *ar)
2293 FILE *sv;
2294 GtkTextView *view = GTK_TEXT_VIEW(obj);
2295 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
2296 GtkTextIter a, b;
2297 char dummy;
2298 int val;
2300 (void) ar;
2301 if (eql(action, "set_text"))
2302 gtk_text_buffer_set_text(textbuf, data, -1);
2303 else if (eql(action, "delete") && sscanf(data, " %c", &dummy) < 1) {
2304 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2305 gtk_text_buffer_delete(textbuf, &a, &b);
2306 } else if (eql(action, "insert_at_cursor"))
2307 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
2308 else if (eql(action, "place_cursor") && eql(data, "end")) {
2309 gtk_text_buffer_get_end_iter(textbuf, &a);
2310 gtk_text_buffer_place_cursor(textbuf, &a);
2311 } else if (eql(action, "place_cursor") &&
2312 sscanf(data, "%d %c", &val, &dummy) == 1) {
2313 gtk_text_buffer_get_iter_at_offset(textbuf, &a, val);
2314 gtk_text_buffer_place_cursor(textbuf, &a);
2315 } else if (eql(action, "place_cursor_at_line") &&
2316 sscanf(data, "%d %c", &val, &dummy) == 1) {
2317 gtk_text_buffer_get_iter_at_line(textbuf, &a, val);
2318 gtk_text_buffer_place_cursor(textbuf, &a);
2319 } else if (eql(action, "scroll_to_cursor") &&
2320 sscanf(data, " %c", &dummy) < 1)
2321 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
2322 0., 0, 0., 0.);
2323 else if (eql(action, "save") && data != NULL &&
2324 (sv = fopen(data, "w")) != NULL) {
2325 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2326 send_msg(sv, GTK_BUILDABLE(view), "insert_at_cursor",
2327 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
2328 fclose(sv);
2329 } else
2330 ign_cmd(type, whole_msg);
2333 static void
2334 update_toggle_button(GObject *obj, const char *action, const char *data,
2335 const char *whole_msg, GType type, struct info *ar)
2337 char dummy;
2338 unsigned int val;
2340 (void) ar;
2341 if (eql(action, "set_label"))
2342 gtk_button_set_label(GTK_BUTTON(obj), data);
2343 else if (eql(action, "set_active") &&
2344 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2345 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), val);
2346 else
2347 ign_cmd(type, whole_msg);
2350 static void
2351 update_tooltip_text(GObject *obj, const char *action, const char *data,
2352 const char *whole_msg, GType type, struct info *ar)
2354 (void) action;
2355 (void) ar;
2356 (void) type;
2357 (void) whole_msg;
2358 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
2362 * update_tree_view(), which runs inside gtk_main(), needs a few
2363 * helper functions
2367 * Check if s is a valid string representation of a GtkTreePath
2369 static bool
2370 is_path_string(char *s)
2372 return s != NULL &&
2373 strlen(s) == strspn(s, ":0123456789") &&
2374 strstr(s, "::") == NULL &&
2375 strcspn(s, ":") > 0;
2378 static void
2379 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
2380 GtkTreeIter *parent, GtkTreeIter *sibling)
2382 if (GTK_IS_TREE_STORE(model))
2383 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
2384 iter, parent, sibling);
2385 else if (GTK_IS_LIST_STORE(model))
2386 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2387 iter, sibling);
2388 else
2389 ABORT;
2392 static void
2393 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2394 GtkTreeIter *parent, GtkTreeIter *sibling)
2396 if (GTK_IS_TREE_STORE(model))
2397 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2398 iter, parent, sibling);
2399 else if (GTK_IS_LIST_STORE(model))
2400 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2401 iter, sibling);
2402 else
2403 ABORT;
2406 static void
2407 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2408 GtkTreeIter *position)
2410 if (GTK_IS_TREE_STORE(model))
2411 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2412 else if (GTK_IS_LIST_STORE(model))
2413 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2414 else
2415 ABORT;
2418 static void
2419 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2421 if (GTK_IS_TREE_STORE(model))
2422 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2423 else if (GTK_IS_LIST_STORE(model))
2424 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2425 else
2426 ABORT;
2429 static void
2430 tree_model_clear(GtkTreeModel *model)
2432 if (GTK_IS_TREE_STORE(model))
2433 gtk_tree_store_clear(GTK_TREE_STORE(model));
2434 else if (GTK_IS_LIST_STORE(model))
2435 gtk_list_store_clear(GTK_LIST_STORE(model));
2436 else
2437 ABORT;
2440 static void
2441 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2443 va_list ap;
2445 va_start(ap, iter);
2446 if (GTK_IS_TREE_STORE(model))
2447 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2448 else if (GTK_IS_LIST_STORE(model))
2449 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2450 else
2451 ABORT;
2452 va_end(ap);
2456 * Create an empty row at path if it doesn't yet exist. Create older
2457 * siblings and parents as necessary.
2459 static void
2460 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2462 GtkTreeIter iter_1; /* iter's predecessor */
2463 GtkTreePath *path_1; /* path's predecessor */
2465 if (gtk_tree_path_get_depth(path) > 0 &&
2466 gtk_tree_model_get_iter(model, iter, path))
2467 return;
2468 path_1 = gtk_tree_path_copy(path);
2469 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2470 create_subtree(model, path_1, iter);
2471 iter_1 = *iter;
2472 tree_model_insert_after(model, iter, NULL, &iter_1);
2473 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2474 create_subtree(model, path_1, iter);
2475 if (gtk_tree_path_get_depth(path_1) == 0)
2476 /* first toplevel row */
2477 tree_model_insert_after(model, iter, NULL, NULL);
2478 else { /* first row in a lower level */
2479 iter_1 = *iter;
2480 tree_model_insert_after(model, iter, &iter_1, NULL);
2482 } /* neither prev nor up mean we're at the root of an empty tree */
2483 gtk_tree_path_free(path_1);
2486 static bool
2487 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2488 const char *path_s, int col, const char *new_text)
2490 GType col_type = gtk_tree_model_get_column_type(model, col);
2491 GtkTreePath *path;
2492 bool ok = false;
2493 char dummy;
2494 double d;
2495 long long int n;
2497 path = gtk_tree_path_new_from_string(path_s);
2498 switch (col_type) {
2499 case G_TYPE_BOOLEAN:
2500 case G_TYPE_INT:
2501 case G_TYPE_LONG:
2502 case G_TYPE_INT64:
2503 case G_TYPE_UINT:
2504 case G_TYPE_ULONG:
2505 case G_TYPE_UINT64:
2506 if (new_text != NULL &&
2507 sscanf(new_text, "%lld %c", &n, &dummy) == 1) {
2508 create_subtree(model, path, iter);
2509 tree_model_set(model, iter, col, n, -1);
2510 ok = true;
2512 break;
2513 case G_TYPE_FLOAT:
2514 case G_TYPE_DOUBLE:
2515 if (new_text != NULL &&
2516 sscanf(new_text, "%lf %c", &d, &dummy) == 1) {
2517 create_subtree(model, path, iter);
2518 tree_model_set(model, iter, col, d, -1);
2519 ok = true;
2521 break;
2522 case G_TYPE_STRING:
2523 create_subtree(model, path, iter);
2524 tree_model_set(model, iter, col, new_text, -1);
2525 ok = true;
2526 break;
2527 default:
2528 fprintf(stderr, "column %d: %s not implemented\n",
2529 col, g_type_name(col_type));
2530 ok = true;
2531 break;
2533 gtk_tree_path_free(path);
2534 return ok;
2537 static void
2538 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2540 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2541 /* is just fine and this function need not exist. */
2542 if (path == NULL)
2543 path = gtk_tree_path_new();
2544 gtk_tree_view_set_cursor(view, path, col, false);
2547 static void
2548 update_tree_view(GObject *obj, const char *action, const char *data,
2549 const char *whole_msg, GType type, struct info *ar)
2551 GtkTreeView *view = GTK_TREE_VIEW(obj);
2552 GtkTreeIter iter0, iter1;
2553 GtkTreeModel *model = gtk_tree_view_get_model(view);
2554 GtkTreePath *path = NULL;
2555 bool iter0_valid, iter1_valid;
2556 char *tokens, *arg0, *arg1, *arg2;
2557 int col = -1; /* invalid column number */
2558 struct info args;
2560 (void) ar;
2561 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2563 fprintf(stderr, "missing model/");
2564 ign_cmd(type, whole_msg);
2565 return;
2567 if ((tokens = malloc(strlen(data) + 1)) == NULL)
2568 OOM_ABORT;
2569 strcpy(tokens, data);
2570 arg0 = strtok(tokens, WHITESPACE);
2571 arg1 = strtok(NULL, WHITESPACE);
2572 arg2 = strtok(NULL, "");
2573 iter0_valid = is_path_string(arg0) &&
2574 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2575 iter1_valid = is_path_string(arg1) &&
2576 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2577 if (is_path_string(arg1))
2578 col = strtol(arg1, NULL, 10);
2579 if (eql(action, "set") &&
2580 col > -1 &&
2581 col < gtk_tree_model_get_n_columns(model) &&
2582 is_path_string(arg0)) {
2583 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2584 ign_cmd(type, whole_msg);
2585 } else if (eql(action, "scroll") && iter0_valid && iter1_valid &&
2586 arg2 == NULL) {
2587 path = gtk_tree_path_new_from_string(arg0);
2588 gtk_tree_view_scroll_to_cell (view,
2589 path,
2590 gtk_tree_view_get_column(view, col),
2591 0, 0., 0.);
2592 } else if (eql(action, "expand") && iter0_valid && arg1 == NULL) {
2593 path = gtk_tree_path_new_from_string(arg0);
2594 gtk_tree_view_expand_row(view, path, false);
2595 } else if (eql(action, "expand_all") && iter0_valid && arg1 == NULL) {
2596 path = gtk_tree_path_new_from_string(arg0);
2597 gtk_tree_view_expand_row(view, path, true);
2598 } else if (eql(action, "expand_all") && arg0 == NULL)
2599 gtk_tree_view_expand_all(view);
2600 else if (eql(action, "collapse") && iter0_valid && arg1 == NULL) {
2601 path = gtk_tree_path_new_from_string(arg0);
2602 gtk_tree_view_collapse_row(view, path);
2603 } else if (eql(action, "collapse") && arg0 == NULL)
2604 gtk_tree_view_collapse_all(view);
2605 else if (eql(action, "set_cursor") && iter0_valid && arg1 == NULL) {
2606 path = gtk_tree_path_new_from_string(arg0);
2607 tree_view_set_cursor(view, path, NULL);
2608 } else if (eql(action, "set_cursor") && arg0 == NULL) {
2609 tree_view_set_cursor(view, NULL, NULL);
2610 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2611 } else if (eql(action, "insert_row") &&
2612 eql(arg0, "end") && arg1 == NULL)
2613 tree_model_insert_before(model, &iter1, NULL, NULL);
2614 else if (eql(action, "insert_row") && iter0_valid &&
2615 eql(arg1, "as_child") && arg2 == NULL)
2616 tree_model_insert_after(model, &iter1, &iter0, NULL);
2617 else if (eql(action, "insert_row") && iter0_valid && arg1 == NULL)
2618 tree_model_insert_before(model, &iter1, NULL, &iter0);
2619 else if (eql(action, "move_row") && iter0_valid &&
2620 eql(arg1, "end") && arg2 == NULL)
2621 tree_model_move_before(model, &iter0, NULL);
2622 else if (eql(action, "move_row") && iter0_valid && iter1_valid && arg2 == NULL)
2623 tree_model_move_before(model, &iter0, &iter1);
2624 else if (eql(action, "remove_row") && iter0_valid && arg1 == NULL)
2625 tree_model_remove(model, &iter0);
2626 else if (eql(action, "clear") && arg0 == NULL) {
2627 tree_view_set_cursor(view, NULL, NULL);
2628 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2629 tree_model_clear(model);
2630 } else if (eql(action, "save") && arg0 != NULL &&
2631 (args.outstr = fopen(arg0, "w")) != NULL) {
2632 args.obj = obj;
2633 gtk_tree_model_foreach(model,
2634 (GtkTreeModelForeachFunc) save_tree_row_msg,
2635 &args);
2636 fclose(args.outstr);
2637 } else
2638 ign_cmd(type, whole_msg);
2639 free(tokens);
2640 gtk_tree_path_free(path);
2643 static void
2644 update_visibility(GObject *obj, const char *action, const char *data,
2645 const char *whole_msg, GType type, struct info *ar)
2647 char dummy;
2648 unsigned int val;
2650 (void) action;
2651 (void) ar;
2652 (void) type;
2653 (void) whole_msg;
2654 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2655 gtk_widget_set_visible(GTK_WIDGET(obj), val);
2656 else
2657 ign_cmd(type, whole_msg);
2661 * Change the style of the widget passed. Runs inside gtk_main().
2663 static void
2664 update_widget_style(GObject *obj, const char *name, const char *data,
2665 const char *whole_msg, GType type, struct info *ar)
2667 GtkStyleContext *context;
2668 GtkStyleProvider *style_provider;
2669 char *style_decl;
2670 const char *prefix = "* {", *suffix = "}";
2671 size_t sz;
2673 (void) ar;
2674 (void) name;
2675 (void) type;
2676 (void) whole_msg;
2677 style_provider = g_object_get_data(obj, "style_provider");
2678 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2679 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2680 gtk_style_context_remove_provider(context, style_provider);
2681 if ((style_decl = malloc(sz)) == NULL)
2682 OOM_ABORT;
2683 strcpy(style_decl, prefix);
2684 strcat(style_decl, data);
2685 strcat(style_decl, suffix);
2686 gtk_style_context_add_provider(context, style_provider,
2687 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2688 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2689 style_decl, -1, NULL);
2690 free(style_decl);
2693 static void
2694 update_window(GObject *obj, const char *action, const char *data,
2695 const char *whole_msg, GType type, struct info *ar)
2697 (void) ar;
2698 if (!update_class_window(obj, action, data, whole_msg, type, NULL))
2699 ign_cmd(type, whole_msg);
2703 * Simulate user activity on various widgets. Runs inside gtk_main().
2705 static void
2706 fake_ui_activity(GObject *obj, const char *action, const char *data,
2707 const char *whole_msg, GType type, struct info *ar)
2709 char dummy;
2711 (void) action;
2712 (void) data;
2713 if (!GTK_IS_WIDGET(obj) || sscanf(data, " %c", &dummy) > 0)
2714 ign_cmd(type, whole_msg);
2715 else if (GTK_IS_SPIN_BUTTON(obj)) {
2716 ar->txt = "text";
2717 cb_spin_button(GTK_BUILDABLE(obj), ar); /* TODO: rename to "value" */
2718 } else if (GTK_IS_SCALE(obj)) {
2719 ar->txt = "value";
2720 cb_range(GTK_BUILDABLE(obj), ar);
2721 } else if (GTK_IS_ENTRY(obj)) {
2722 ar->txt = "text";
2723 cb_editable(GTK_BUILDABLE(obj), ar);
2724 } else if (GTK_IS_CALENDAR(obj)) {
2725 ar->txt = "clicked";
2726 cb_calendar(GTK_BUILDABLE(obj), ar);
2727 } else if (GTK_IS_FILE_CHOOSER_BUTTON(obj)) {
2728 ar->txt = "file";
2729 cb_file_chooser_button(GTK_BUILDABLE(obj), ar);
2730 } else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2731 ign_cmd(type, whole_msg);
2735 * The final UI update. Runs inside gtk_main().
2737 static void
2738 main_quit(GObject *obj, const char *action, const char *data,
2739 const char *whole_msg, GType type, struct info *ar)
2741 char dummy;
2743 (void) action;
2744 (void) ar;
2745 (void) data;
2746 (void) obj;
2747 (void) type;
2748 (void) whole_msg;
2749 if (sscanf(data, " %c", &dummy) < 1)
2750 gtk_main_quit();
2751 else
2752 ign_cmd(type, whole_msg);
2756 * Don't update anything; just complain from inside gtk_main()
2758 static void
2759 complain(GObject *obj, const char *action, const char *data,
2760 const char *whole_msg, GType type, struct info *ar)
2762 (void) action;
2763 (void) ar;
2764 (void) data;
2765 (void) obj;
2766 ign_cmd(type, whole_msg);
2770 * Data to be passed to and from the GTK main loop
2772 struct ui_data {
2773 void (*fn)(GObject *, const char *action, const char *data,
2774 const char *msg, GType type, struct info *args);
2775 GObject *obj;
2776 char *action;
2777 char *data;
2778 char *msg;
2779 char *msg_tokens;
2780 GType type;
2781 struct info *args;
2785 * Parse command pointed to by ud, and act on ui accordingly. Runs
2786 * once per command inside gtk_main().
2788 static gboolean
2789 update_ui(struct ui_data *ud)
2791 char *lc = lc_numeric();
2793 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type, ud->args);
2794 free(ud->msg_tokens);
2795 free(ud->msg);
2796 free(ud);
2797 lc_numeric_free(lc);
2798 return G_SOURCE_REMOVE;
2802 * Keep track of loading files to avoid recursive loading of the same
2803 * file. If filename = NULL, forget the most recently remembered file.
2805 static bool
2806 remember_loading_file(char *filename)
2808 static char *filenames[BUFLEN];
2809 static size_t latest = 0;
2810 size_t i;
2812 if (filename == NULL) { /* pop */
2813 if (latest < 1)
2814 ABORT;
2815 latest--;
2816 return false;
2817 } else { /* push */
2818 for (i = 1; i <= latest; i++)
2819 if (eql(filename, filenames[i]))
2820 return false;
2821 if (latest > BUFLEN -2)
2822 return false;
2823 filenames[++latest] = filename;
2824 return true;
2829 * Read lines from stream cmd and perform appropriate actions on the
2830 * GUI. Runs inside receiver thread.
2832 static void *
2833 digest_msg(struct info *args)
2835 static int recursion = -1; /* > 0 means this is a recursive call */
2837 recursion++;
2838 for (;;) {
2839 FILE *cmd = args->instr;
2840 struct ui_data *ud = NULL;
2841 char first_char = '\0', *name;
2842 size_t msg_size = 32;
2843 int name_start = 0, name_end = 0;
2844 int action_start = 0, action_end = 0;
2845 int data_start = 0;
2847 if (feof(cmd))
2848 break;
2849 if ((ud = malloc(sizeof(*ud))) == NULL)
2850 OOM_ABORT;
2851 if ((ud->msg = malloc(msg_size)) == NULL)
2852 OOM_ABORT;
2853 ud->args = args;
2854 ud->type = G_TYPE_INVALID;
2855 pthread_testcancel();
2856 if (recursion == 0)
2857 log_msg(args->logstr, NULL);
2858 data_start = read_buf(cmd, &ud->msg, &msg_size);
2859 if (recursion == 0)
2860 log_msg(args->logstr, ud->msg);
2861 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2862 OOM_ABORT;
2863 strcpy(ud->msg_tokens, ud->msg);
2864 sscanf(ud->msg, " %c", &first_char);
2865 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2866 ud->fn = update_nothing;
2867 goto exec;
2869 sscanf(ud->msg_tokens,
2870 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2871 &name_start, &name_end, &action_start, &action_end, &data_start);
2872 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2873 name = ud->msg_tokens + name_start;
2874 ud->action = ud->msg_tokens + action_start;
2875 ud->data = ud->msg_tokens + data_start;
2876 if (eql(ud->action, "main_quit")) {
2877 ud->fn = main_quit;
2878 goto exec;
2880 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2881 remember_loading_file(ud->data)) {
2882 struct info a = *args;
2884 if ((a.instr = fopen(ud->data, "r")) != NULL) {
2885 digest_msg(&a);
2886 fclose(a.instr);
2887 ud->fn = update_nothing;
2888 } else
2889 ud->fn = complain;
2890 remember_loading_file(NULL);
2891 goto exec;
2893 if ((ud->obj = (gtk_builder_get_object(args->builder, name))) == NULL) {
2894 ud->fn = complain;
2895 goto exec;
2897 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2898 if (eql(ud->action, "force"))
2899 ud->fn = fake_ui_activity;
2900 else if (eql(ud->action, "set_sensitive"))
2901 ud->fn = update_sensitivity;
2902 else if (eql(ud->action, "set_visible"))
2903 ud->fn = update_visibility;
2904 else if (eql(ud->action, "set_size_request"))
2905 ud->fn = update_size_request;
2906 else if (eql(ud->action, "set_tooltip_text"))
2907 ud->fn = update_tooltip_text;
2908 else if (eql(ud->action, "grab_focus"))
2909 ud->fn = update_focus;
2910 else if (eql(ud->action, "style")) {
2911 ud->action = name;
2912 ud->fn = update_widget_style;
2913 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2914 ud->fn = update_drawing_area;
2915 else if (ud->type == GTK_TYPE_TREE_VIEW)
2916 ud->fn = update_tree_view;
2917 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2918 ud->fn = update_combo_box_text;
2919 else if (ud->type == GTK_TYPE_LABEL)
2920 ud->fn = update_label;
2921 else if (ud->type == GTK_TYPE_IMAGE)
2922 ud->fn = update_image;
2923 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2924 ud->fn = update_text_view;
2925 else if (ud->type == GTK_TYPE_NOTEBOOK)
2926 ud->fn = update_notebook;
2927 else if (ud->type == GTK_TYPE_EXPANDER)
2928 ud->fn = update_expander;
2929 else if (ud->type == GTK_TYPE_FRAME)
2930 ud->fn = update_frame;
2931 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2932 ud->fn = update_scrolled_window;
2933 else if (ud->type == GTK_TYPE_BUTTON)
2934 ud->fn = update_button;
2935 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2936 ud->fn = update_file_chooser_dialog;
2937 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2938 ud->fn = update_file_chooser_button;
2939 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2940 ud->fn = update_color_button;
2941 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2942 ud->fn = update_font_button;
2943 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2944 ud->fn = update_print_dialog;
2945 else if (ud->type == GTK_TYPE_SWITCH)
2946 ud->fn = update_switch;
2947 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2948 ud->type == GTK_TYPE_RADIO_BUTTON ||
2949 ud->type == GTK_TYPE_CHECK_BUTTON)
2950 ud->fn = update_toggle_button;
2951 else if (ud->type == GTK_TYPE_ENTRY)
2952 ud->fn = update_entry;
2953 else if (ud->type == GTK_TYPE_SPIN_BUTTON)
2954 ud->fn = update_spin_button;
2955 else if (ud->type == GTK_TYPE_SCALE)
2956 ud->fn = update_scale;
2957 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2958 ud->fn = update_progress_bar;
2959 else if (ud->type == GTK_TYPE_SPINNER)
2960 ud->fn = update_spinner;
2961 else if (ud->type == GTK_TYPE_STATUSBAR)
2962 ud->fn = update_statusbar;
2963 else if (ud->type == GTK_TYPE_CALENDAR)
2964 ud->fn = update_calendar;
2965 else if (ud->type == GTK_TYPE_SOCKET)
2966 ud->fn = update_socket;
2967 else if (ud->type == GTK_TYPE_WINDOW ||
2968 ud->type == GTK_TYPE_DIALOG)
2969 ud->fn = update_window;
2970 else
2971 ud->fn = complain;
2972 exec:
2973 pthread_testcancel();
2974 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2976 recursion--;
2977 return NULL;
2982 * ============================================================
2983 * Initialization
2984 * ============================================================
2988 * Attach to renderer key "col_number". Associate "col_number" with
2989 * the corresponding column number in the underlying model.
2990 * Due to what looks like a gap in the GTK API, renderer id and column
2991 * number are taken directly from the XML .ui file.
2993 static bool
2994 tree_view_column_get_renderer_column(GtkBuilder *builder, const char *ui_file,
2995 GtkTreeViewColumn *t_col, int n,
2996 GtkCellRenderer **renderer)
2998 bool r = false;
2999 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
3000 char *xpath_base2 = "\"]/child[";
3001 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
3002 " or @class=\"GtkCellRendererToggle\"]/";
3003 char *xpath_renderer_id = "/@id";
3004 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
3005 " or @name=\"active\"]";
3006 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
3007 int i;
3008 size_t xpath_len;
3009 size_t xpath_n_len = 3; /* Big Enough (TM) */
3010 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
3011 xmlDocPtr doc;
3012 xmlNodePtr cur;
3013 xmlNodeSetPtr nodes;
3014 xmlXPathContextPtr xpath_ctx;
3015 xmlXPathObjectPtr xpath_obj;
3017 if ((doc = xmlParseFile(ui_file)) == NULL)
3018 return false;
3019 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
3020 xmlFreeDoc(doc);
3021 return false;
3023 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
3024 strlen(xpath_base2) + xpath_n_len +
3025 strlen(xpath_base3)) +
3026 sizeof('|') +
3027 strlen(xpath_text_col) + strlen(xpath_renderer_id) +
3028 sizeof('\0');
3029 if ((xpath = malloc(xpath_len)) == NULL)
3030 OOM_ABORT;
3031 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
3032 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
3033 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
3034 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
3035 xmlXPathFreeContext(xpath_ctx);
3036 free(xpath);
3037 xmlFreeDoc(doc);
3038 return false;
3040 if ((nodes = xpath_obj->nodesetval) != NULL) {
3041 for (i = 0; i < nodes->nodeNr; ++i) {
3042 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
3043 cur = nodes->nodeTab[i];
3044 m_col_s = xmlNodeGetContent(cur);
3045 } else {
3046 cur = nodes->nodeTab[i];
3047 renderer_name = xmlNodeGetContent(cur);
3051 if (renderer_name) {
3052 *renderer = GTK_CELL_RENDERER(
3053 gtk_builder_get_object(builder, (char *) renderer_name));
3054 if (m_col_s) {
3055 g_object_set_data(G_OBJECT(*renderer), "col_number",
3056 GINT_TO_POINTER(strtol((char *) m_col_s,
3057 NULL, 10)));
3058 xmlFree(m_col_s);
3059 r = true;
3061 xmlFree(renderer_name);
3063 xmlXPathFreeObject(xpath_obj);
3064 xmlXPathFreeContext(xpath_ctx);
3065 free(xpath);
3066 xmlFreeDoc(doc);
3067 return r;
3071 * Callbacks that forward a modification of a tree view cell to the
3072 * underlying model
3074 static void
3075 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
3076 const gchar *new_text, struct info *args)
3078 GtkTreeIter iter;
3079 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3080 "col_number"));
3082 gtk_tree_model_get_iter_from_string(args->model, &iter, path_s);
3083 set_tree_view_cell(args->model, &iter, path_s, col,
3084 new_text);
3085 send_tree_cell_msg_by(send_msg, path_s, &iter, col, args);
3088 static void
3089 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, struct info *args)
3091 GtkTreeIter iter;
3092 bool toggle_state;
3093 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3094 "col_number"));
3096 gtk_tree_model_get_iter_from_string(args->model, &iter, path_s);
3097 gtk_tree_model_get(args->model, &iter, col, &toggle_state, -1);
3098 set_tree_view_cell(args->model, &iter, path_s, col,
3099 toggle_state? "0" : "1");
3102 static void
3103 connect_widget_signals(gpointer *obj, struct info *ar)
3105 GObject *obj2;
3106 GType type = G_TYPE_INVALID;
3107 char *suffix = NULL;
3108 const char *name = NULL;
3109 FILE *o = ar->outstr;
3111 type = G_TYPE_FROM_INSTANCE(obj);
3112 if (GTK_IS_BUILDABLE(obj))
3113 name = widget_name(GTK_BUILDABLE(obj));
3114 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
3115 GList *cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(obj));
3116 GtkTreeViewColumn *tv_col = GTK_TREE_VIEW_COLUMN(obj);
3117 GObject *view = G_OBJECT(
3118 gtk_tree_view_column_get_tree_view(tv_col));
3119 unsigned int i, n_cells = g_list_length(cells);
3121 g_list_free(cells);
3122 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3123 for (i = 1; i <= n_cells; i++) {
3124 GtkCellRenderer *renderer;
3125 gboolean editable = FALSE;
3127 if (!tree_view_column_get_renderer_column(ar->builder, ar->txt, tv_col,
3128 i, &renderer))
3129 continue;
3130 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
3131 g_object_get(renderer, "editable", &editable, NULL);
3132 if (editable) {
3133 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
3135 g_signal_connect(renderer, "edited",
3136 G_CALLBACK(cb_tree_model_edit),
3137 info_obj_new(o, view, model));
3139 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
3140 g_object_get(renderer, "activatable", &editable, NULL);
3141 if (editable) {
3142 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
3144 g_signal_connect(renderer, "toggled",
3145 G_CALLBACK(cb_tree_model_toggle),
3146 info_obj_new(o, NULL, model));
3150 } else if (type == GTK_TYPE_BUTTON)
3151 /* Button associated with a GtkTextView. */
3152 if ((suffix = strstr(name, "_send_text")) != NULL &&
3153 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(ar->builder, suffix, name)))
3154 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
3155 info_obj_new(o, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2))), NULL));
3156 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
3157 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(ar->builder, suffix, name)))
3158 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
3159 info_obj_new(o, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2))), NULL));
3160 else {
3161 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3162 /* Buttons associated with (and part of) a GtkDialog.
3163 * (We shun response ids which could be returned from
3164 * gtk_dialog_run() because that would require the
3165 * user to define those response ids in Glade,
3166 * numerically */
3167 if ((suffix = strstr(name, "_cancel")) != NULL &&
3168 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, name)))
3169 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
3170 g_signal_connect_swapped(obj, "clicked",
3171 G_CALLBACK(gtk_main_quit), NULL);
3172 else
3173 g_signal_connect_swapped(obj, "clicked",
3174 G_CALLBACK(gtk_widget_hide), obj2);
3175 else if ((suffix = strstr(name, "_ok")) != NULL &&
3176 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, name))) {
3177 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
3178 g_signal_connect_swapped(obj, "clicked",
3179 G_CALLBACK(cb_send_file_chooser_dialog_selection),
3180 info_obj_new(o, obj2, NULL));
3181 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
3182 g_signal_connect_swapped(obj, "clicked",
3183 G_CALLBACK(gtk_main_quit), NULL);
3184 else
3185 g_signal_connect_swapped(obj, "clicked",
3186 G_CALLBACK(gtk_widget_hide), obj2);
3187 } else if ((suffix = strstr(name, "_apply")) != NULL &&
3188 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, name)))
3189 g_signal_connect_swapped(obj, "clicked",
3190 G_CALLBACK(cb_send_file_chooser_dialog_selection),
3191 info_obj_new(o, obj2, NULL));
3193 else if (GTK_IS_MENU_ITEM(obj))
3194 if ((suffix = strstr(name, "_invoke")) != NULL &&
3195 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, name)))
3196 g_signal_connect_swapped(obj, "activate",
3197 G_CALLBACK(gtk_widget_show), obj2);
3198 else
3199 g_signal_connect(obj, "activate",
3200 G_CALLBACK(cb_menu_item), info_txt_new(o, "active"));
3201 else if (GTK_IS_WINDOW(obj)) {
3202 g_signal_connect(obj, "delete-event",
3203 G_CALLBACK(cb_event_simple), info_txt_new(o, "closed"));
3204 if (eql(name, MAIN_WIN))
3205 g_signal_connect_swapped(obj, "delete-event",
3206 G_CALLBACK(gtk_main_quit), NULL);
3207 else
3208 g_signal_connect(obj, "delete-event",
3209 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3210 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
3211 g_signal_connect(obj, "file-set",
3212 G_CALLBACK(cb_file_chooser_button), info_txt_new(o, "file"));
3213 else if (type == GTK_TYPE_COLOR_BUTTON)
3214 g_signal_connect(obj, "color-set",
3215 G_CALLBACK(cb_color_button), info_txt_new(o, "color"));
3216 else if (type == GTK_TYPE_FONT_BUTTON)
3217 g_signal_connect(obj, "font-set",
3218 G_CALLBACK(cb_font_button), info_txt_new(o, "font"));
3219 else if (type == GTK_TYPE_SWITCH)
3220 g_signal_connect(obj, "notify::active",
3221 G_CALLBACK(cb_switch), info_txt_new(o, NULL));
3222 else if (type == GTK_TYPE_TOGGLE_BUTTON ||
3223 type == GTK_TYPE_RADIO_BUTTON ||
3224 type == GTK_TYPE_CHECK_BUTTON)
3225 g_signal_connect(obj, "toggled",
3226 G_CALLBACK(cb_toggle_button), info_txt_new(o, NULL));
3227 else if (type == GTK_TYPE_ENTRY)
3228 g_signal_connect(obj, "changed",
3229 G_CALLBACK(cb_editable), info_txt_new(o, "text"));
3230 else if (type == GTK_TYPE_SPIN_BUTTON)
3231 g_signal_connect(obj, "value_changed",
3232 G_CALLBACK(cb_spin_button), info_txt_new(o, "text")); /* TODO: rename to "value" */
3233 else if (type == GTK_TYPE_SCALE)
3234 g_signal_connect(obj, "value-changed",
3235 G_CALLBACK(cb_range), info_txt_new(o, "value"));
3236 else if (type == GTK_TYPE_CALENDAR) {
3237 g_signal_connect(obj, "day-selected-double-click",
3238 G_CALLBACK(cb_calendar), info_txt_new(o, "doubleclicked"));
3239 g_signal_connect(obj, "day-selected",
3240 G_CALLBACK(cb_calendar), info_txt_new(o, "clicked"));
3241 } else if (type == GTK_TYPE_TREE_SELECTION)
3242 g_signal_connect(obj, "changed",
3243 G_CALLBACK(cb_tree_selection), info_txt_new(o, "clicked"));
3244 else if (type == GTK_TYPE_SOCKET) {
3245 g_signal_connect(obj, "plug-added",
3246 G_CALLBACK(cb_simple), info_txt_new(o, "plug-added"));
3247 g_signal_connect(obj, "plug-removed",
3248 G_CALLBACK(cb_simple), info_txt_new(o, "plug-removed"));
3249 /* TODO: rename to plug_added, plug_removed */
3250 } else if (type == GTK_TYPE_DRAWING_AREA)
3251 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
3252 else if (type == GTK_TYPE_EVENT_BOX) {
3253 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
3254 g_signal_connect(obj, "button-press-event",
3255 G_CALLBACK(cb_event_box_button),
3256 info_txt_new(o, "button_press"));
3257 g_signal_connect(obj, "button-release-event",
3258 G_CALLBACK(cb_event_box_button),
3259 info_txt_new(o, "button_release"));
3260 g_signal_connect(obj, "motion-notify-event",
3261 G_CALLBACK(cb_event_box_motion),
3262 info_txt_new(o, "motion"));
3263 g_signal_connect(obj, "key-press-event",
3264 G_CALLBACK(cb_event_box_key),
3265 info_txt_new(o, "key_press"));
3270 * We keep a style provider with each widget
3272 static void
3273 add_widget_style_provider(gpointer *obj, void *data)
3275 GtkCssProvider *style_provider;
3276 GtkStyleContext *context;
3278 (void) data;
3279 if (!GTK_IS_WIDGET(obj))
3280 return;
3281 style_provider = gtk_css_provider_new();
3282 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
3283 gtk_style_context_add_provider(context,
3284 GTK_STYLE_PROVIDER(style_provider),
3285 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3286 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
3289 static void
3290 prepare_widgets(GtkBuilder *builder, char *ui_file, FILE *out)
3292 GSList *objects = NULL;
3293 struct info args = {.builder = builder, .outstr = out, .txt = ui_file};
3295 objects = gtk_builder_get_objects(builder);
3296 g_slist_foreach(objects, (GFunc) connect_widget_signals, &args);
3297 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
3298 g_slist_free(objects);
3302 main(int argc, char *argv[])
3304 GObject *main_window = NULL;
3305 bool bg = false;
3306 char *in_fifo = NULL, *out_fifo = NULL;
3307 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
3308 char *xid = NULL;
3309 char opt;
3310 pthread_t receiver;
3311 struct info ar;
3313 /* Disable runtime GLIB deprecation warnings: */
3314 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3315 gtk_init(&argc, &argv);
3316 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
3317 switch (opt) {
3318 case 'b': bg = true; break;
3319 case 'e': xid = optarg; break;
3320 case 'G': show_lib_versions(); break;
3321 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
3322 case 'i': in_fifo = optarg; break;
3323 case 'l': log_file = optarg; break;
3324 case 'o': out_fifo = optarg; break;
3325 case 'O': err_file = optarg; break;
3326 case 'u': ui_file = optarg; break;
3327 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
3328 case '?':
3329 default: bye(EXIT_FAILURE, stderr, USAGE); break;
3332 if (argv[optind] != NULL)
3333 bye(EXIT_FAILURE, stderr,
3334 "illegal parameter '%s'\n" USAGE, argv[optind]);
3335 redirect_stderr(err_file);
3336 ar.instr = open_fifo(in_fifo, "r", stdin, _IONBF);
3337 ar.outstr = open_fifo(out_fifo, "w", stdout, _IOLBF);
3338 go_bg_if(bg, ar.instr, ar.outstr, err_file);
3339 ar.builder = builder_from_file(ui_file);
3340 ar.logstr = open_log(log_file);
3341 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, &ar);
3342 main_window = find_main_window(ar.builder);
3343 xmlInitParser();
3344 LIBXML_TEST_VERSION;
3345 prepare_widgets(ar.builder, ui_file, ar.outstr);
3346 xembed_if(xid, main_window);
3347 gtk_main();
3348 rm_unless(stdin, ar.instr, in_fifo);
3349 rm_unless(stdout, ar.outstr, out_fifo);
3350 pthread_cancel(receiver);
3351 pthread_join(receiver, NULL);
3352 xmlCleanupParser();
3353 exit(EXIT_SUCCESS);