Change an error message for consistency of terms
[pipeglade.git] / pipeglade.c
blob116dbe22b571da9478a362c3ea4ddda9b0b4b171
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 do { \
66 fprintf(stderr, \
67 "In %s (%s:%d): ", \
68 __func__, __FILE__, __LINE__); \
69 abort(); \
70 } while (0)
72 #define OOM_ABORT \
73 do { \
74 fprintf(stderr, \
75 "Out of memory in %s (%s:%d): ", \
76 __func__, __FILE__, __LINE__); \
77 abort(); \
78 } while (0)
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;
316 * Delete fifo fn if streams s and forbidden are distinct
318 static void
319 rm_unless(FILE *forbidden, FILE *s, char *fn)
321 if (s == forbidden)
322 return;
323 fclose(s);
324 remove(fn);
328 * Microseconds elapsed since start
330 static long int
331 usec_since(struct timespec *start)
333 struct timespec now;
335 clock_gettime(CLOCK_MONOTONIC, &now);
336 return (now.tv_sec - start->tv_sec) * 1e6 +
337 (now.tv_nsec - start->tv_nsec) / 1e3;
341 * Write string s to stream o, escaping newlines and backslashes
343 static void
344 fputs_escaped(const char *s, FILE *o)
346 size_t i = 0;
347 char c;
349 while ((c = s[i++]) != '\0')
350 switch (c) {
351 case '\\': fputs("\\\\", o); break;
352 case '\n': fputs("\\n", o); break;
353 default: putc(c, o); break;
358 * Write log file
360 static void
361 log_msg(FILE *l, char *msg)
363 static char *old_msg;
364 static struct timespec start;
366 if (l == NULL) /* no logging */
367 return;
368 if (msg == NULL && old_msg == NULL)
369 fprintf(l, "##########\t##### (New Pipeglade session) #####\n");
370 else if (msg == NULL && old_msg != NULL) {
371 /* command done; start idle */
372 fprintf(l, "%10ld\t", usec_since(&start));
373 fputs_escaped(old_msg, l);
374 putc('\n', l);
375 free(old_msg);
376 old_msg = NULL;
377 } else if (msg != NULL && old_msg == NULL) {
378 /* idle done; start command */
379 fprintf(l, "%10ld\t### (Idle) ###\n", usec_since(&start));
380 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
381 OOM_ABORT;
382 strcpy(old_msg, msg);
383 } else
384 ABORT;
385 clock_gettime(CLOCK_MONOTONIC, &start);
388 static bool
389 has_suffix(const char *s, const char *suffix)
391 int s_suf = strlen(s) - strlen(suffix);
393 if (s_suf < 0)
394 return false;
395 return eql(suffix, s + s_suf);
399 * Remove suffix from name; find the object named like this
401 static GObject *
402 obj_sans_suffix(GtkBuilder *builder, const char *suffix, const char *name)
404 char str[BUFLEN + 1] = {'\0'};
405 int str_l;
407 str_l = suffix - name;
408 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
409 return gtk_builder_get_object(builder, str);
413 * Read UI definition from ui_file; give up on errors
415 static GtkBuilder *
416 builder_from_file(char *ui_file)
418 GError *error = NULL;
419 GtkBuilder *b;
421 b = gtk_builder_new();
422 if (gtk_builder_add_from_file(b, ui_file, &error) == 0)
423 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
424 return b;
428 * Return the id attribute of widget
430 static const char *
431 widget_id(GtkBuildable *widget)
433 return gtk_buildable_get_name(widget);
437 * Get the main window; give up on errors
439 static GObject *
440 find_main_window(GtkBuilder *builder)
442 GObject *mw;
444 if (GTK_IS_WINDOW(mw = gtk_builder_get_object(builder, MAIN_WIN)))
445 return mw;
446 bye(EXIT_FAILURE, stderr, "no toplevel window with id \'" MAIN_WIN "\'\n");
447 return NULL; /* NOT REACHED */
451 * Store a line from stream s into buf, which should have been malloc'd
452 * to bufsize. Enlarge buf and bufsize if necessary.
454 static size_t
455 read_buf(FILE *s, char **buf, size_t *bufsize)
457 bool esc = false;
458 fd_set rfds;
459 int c;
460 int ifd = fileno(s);
461 size_t i = 0;
463 FD_ZERO(&rfds);
464 FD_SET(ifd, &rfds);
465 for (;;) {
466 select(ifd + 1, &rfds, NULL, NULL, NULL);
467 c = getc(s);
468 if (c == '\n' || feof(s))
469 break;
470 if (i >= *bufsize - 1)
471 if ((*buf = realloc(*buf, *bufsize *= 2)) == NULL)
472 OOM_ABORT;
473 if (esc) {
474 esc = false;
475 switch (c) {
476 case 'n': (*buf)[i++] = '\n'; break;
477 case 'r': (*buf)[i++] = '\r'; break;
478 default: (*buf)[i++] = c; break;
480 } else if (c == '\\')
481 esc = true;
482 else
483 (*buf)[i++] = c;
485 (*buf)[i] = '\0';
486 return i;
491 * ============================================================
492 * Receiving feedback from the GUI
493 * ============================================================
496 static void
497 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
499 char *data;
500 const char *w_id = widget_id(obj);
501 fd_set wfds;
502 int ofd = fileno(o);
503 struct timeval timeout = {1, 0};
505 FD_ZERO(&wfds);
506 FD_SET(ofd, &wfds);
507 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
508 fprintf(o, "%s:%s ", w_id, tag);
509 while ((data = va_arg(ap, char *)) != NULL)
510 fputs_escaped(data, o);
511 putc('\n', o);
512 } else
513 fprintf(stderr,
514 "send error; discarding feedback message %s:%s\n",
515 w_id, tag);
519 * Send GUI feedback to stream o. The message format is
520 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
521 * last argument must be NULL.
523 static void
524 send_msg(FILE *o, GtkBuildable *obj, const char *tag, ...)
526 va_list ap;
528 va_start(ap, tag);
529 send_msg_to(o, obj, tag, ap);
530 va_end(ap);
534 * Send message from GUI to stream o. The message format is
535 * "<origin>:set <data ...>", which happens to be a legal command.
536 * The variadic arguments are strings; last argument must be NULL.
538 static void
539 send_msg_as_cmd(FILE *o, GtkBuildable *obj, const char *tag, ...)
541 va_list ap;
543 va_start(ap, tag);
544 send_msg_to(o, obj, "set", ap);
545 va_end(ap);
549 * Stuff to pass around
551 struct info {
552 FILE *fout; /* UI feedback messages */
553 FILE *fin; /* command input */
554 FILE *flog; /* logging output */
555 GtkBuilder *builder; /* to be read from .ui file */
556 GObject *obj;
557 GtkTreeModel *model;
558 char *txt;
562 * Data to be passed to and from the GTK main loop
564 struct ui_data {
565 void (*fn)(struct ui_data *);
566 GObject *obj;
567 char *action;
568 char *data;
569 char *msg;
570 char *msg_tokens;
571 GType type;
572 struct info *args;
576 * Return pointer to a newly allocated struct info
578 struct info *
579 info_new_full(FILE *stream, GObject *obj, GtkTreeModel *model, char *txt)
581 struct info *ar;
583 if ((ar = malloc(sizeof(struct info))) == NULL)
584 OOM_ABORT;
585 ar->fout = stream;
586 ar->fin = NULL;
587 ar->flog = NULL;
588 ar->builder = NULL;
589 ar->obj = obj;
590 ar->model = model;
591 ar->txt = txt;
592 return ar;
595 struct info *
596 info_txt_new(FILE *stream, char *txt)
598 return info_new_full(stream, NULL, NULL, txt);
601 struct info *
602 info_obj_new(FILE *stream, GObject *obj, GtkTreeModel *model)
604 return info_new_full(stream, obj, model, NULL);
608 * Use msg_sender() to send a message describing a particular cell
610 static void
611 send_tree_cell_msg_by(void msg_sender(FILE *, GtkBuildable *, const char *, ...),
612 const char *path_s,
613 GtkTreeIter *iter, int col, struct info *ar)
615 GtkBuildable *obj = GTK_BUILDABLE(ar->obj);
616 GtkTreeModel *model = ar->model;
617 GType col_type;
618 GValue value = G_VALUE_INIT;
619 char str[BUFLEN], *lc = lc_numeric();
621 gtk_tree_model_get_value(model, iter, col, &value);
622 col_type = gtk_tree_model_get_column_type(model, col);
623 switch (col_type) {
624 case G_TYPE_INT:
625 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
626 msg_sender(ar->fout, obj, "gint", path_s, str, NULL);
627 break;
628 case G_TYPE_LONG:
629 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
630 msg_sender(ar->fout, obj, "glong", path_s, str, NULL);
631 break;
632 case G_TYPE_INT64:
633 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
634 msg_sender(ar->fout, obj, "gint64", path_s, str, NULL);
635 break;
636 case G_TYPE_UINT:
637 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
638 msg_sender(ar->fout, obj, "guint", path_s, str, NULL);
639 break;
640 case G_TYPE_ULONG:
641 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
642 msg_sender(ar->fout, obj, "gulong", path_s, str, NULL);
643 break;
644 case G_TYPE_UINT64:
645 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
646 msg_sender(ar->fout, obj, "guint64", path_s, str, NULL);
647 break;
648 case G_TYPE_BOOLEAN:
649 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
650 msg_sender(ar->fout, obj, "gboolean", path_s, str, NULL);
651 break;
652 case G_TYPE_FLOAT:
653 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
654 msg_sender(ar->fout, obj, "gfloat", path_s, str, NULL);
655 break;
656 case G_TYPE_DOUBLE:
657 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
658 msg_sender(ar->fout, obj, "gdouble", path_s, str, NULL);
659 break;
660 case G_TYPE_STRING:
661 snprintf(str, BUFLEN, " %d ", col);
662 msg_sender(ar->fout, obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
663 break;
664 default:
665 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
666 break;
668 g_value_unset(&value);
669 lc_numeric_free(lc);
673 * Use msg_sender() to send one message per column for a single row
675 static void
676 send_tree_row_msg_by(void msg_sender(FILE *, GtkBuildable *, const char *, ...),
677 char *path_s, GtkTreeIter *iter, struct info *ar)
679 int col;
681 for (col = 0; col < gtk_tree_model_get_n_columns(ar->model); col++)
682 send_tree_cell_msg_by(msg_sender, path_s, iter, col, ar);
686 * send_tree_row_msg serves as an argument for
687 * gtk_tree_selection_selected_foreach()
689 static gboolean
690 send_tree_row_msg(GtkTreeModel *model,
691 GtkTreePath *path, GtkTreeIter *iter, struct info *ar)
693 char *path_s = gtk_tree_path_to_string(path);
695 ar->model = model;
696 send_tree_row_msg_by(send_msg, path_s, iter, ar);
697 g_free(path_s);
698 return FALSE;
702 * save_tree_row_msg serves as an argument for
703 * gtk_tree_model_foreach().
704 * Send message from GUI to global stream "save".
706 static gboolean
707 save_tree_row_msg(GtkTreeModel *model,
708 GtkTreePath *path, GtkTreeIter *iter, struct info *ar)
710 char *path_s = gtk_tree_path_to_string(path);
712 ar->model = model;
713 send_tree_row_msg_by(send_msg_as_cmd, path_s, iter, ar);
714 g_free(path_s);
715 return FALSE;
718 static void
719 cb_calendar(GtkBuildable *obj, struct info *ar)
721 char str[BUFLEN];
722 unsigned int year = 0, month = 0, day = 0;
724 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
725 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
726 send_msg(ar->fout, obj, ar->txt, str, NULL);
729 static void
730 cb_color_button(GtkBuildable *obj, struct info *ar)
732 GdkRGBA color;
734 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
735 send_msg(ar->fout, obj, ar->txt, gdk_rgba_to_string(&color), NULL);
738 static void
739 cb_editable(GtkBuildable *obj, struct info *ar)
741 send_msg(ar->fout, obj, ar->txt, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
745 * Callback that sends a message about a pointer device button press
746 * in a GtkEventBox
748 static bool
749 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, struct info *ar)
751 char data[BUFLEN], *lc = lc_numeric();
753 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
754 e->button.button, e->button.x, e->button.y);
755 send_msg(ar->fout, obj, ar->txt, data, NULL);
756 lc_numeric_free(lc);
757 return true;
761 * Callback that sends in a message the name of the key pressed when
762 * a GtkEventBox is focused
764 static bool
765 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, struct info *ar)
767 send_msg(ar->fout, obj, ar->txt, gdk_keyval_name(e->key.keyval), NULL);
768 return true;
772 * Callback that sends a message about pointer device motion in a
773 * GtkEventBox
775 static bool
776 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, struct info *ar)
778 char data[BUFLEN], *lc = lc_numeric();
780 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
781 send_msg(ar->fout, obj, ar->txt, data, NULL);
782 lc_numeric_free(lc);
783 return true;
787 * Callback that only sends "name:tag" and returns false
789 static bool
790 cb_event_simple(GtkBuildable *obj, GdkEvent *e, struct info *ar)
792 (void) e;
793 send_msg(ar->fout, obj, ar->txt, NULL);
794 return false;
797 static void
798 cb_file_chooser_button(GtkBuildable *obj, struct info *ar)
800 send_msg(ar->fout, obj, ar->txt,
801 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
804 static void
805 cb_font_button(GtkBuildable *obj, struct info *ar)
807 send_msg(ar->fout, obj, ar->txt,
808 gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
811 static void
812 cb_menu_item(GtkBuildable *obj, struct info *ar)
814 send_msg(ar->fout, obj, ar->txt,
815 gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
818 static void
819 cb_range(GtkBuildable *obj, struct info *ar)
821 char str[BUFLEN], *lc = lc_numeric();
823 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
824 send_msg(ar->fout, obj, ar->txt, str, NULL);
825 lc_numeric_free(lc);
829 * Callback that sends user's selection from a file dialog
831 static void
832 cb_send_file_chooser_dialog_selection(struct info *ar)
834 send_msg(ar->fout, GTK_BUILDABLE(ar->obj), "file",
835 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(ar->obj)),
836 NULL);
837 send_msg(ar->fout, GTK_BUILDABLE(ar->obj), "folder",
838 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(ar->obj)),
839 NULL);
843 * Callback that sends in a message the content of the text buffer
844 * passed in user_data
846 static void
847 cb_send_text(GtkBuildable *obj, struct info *ar)
849 GtkTextIter a, b;
851 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(ar->obj), &a, &b);
852 send_msg(ar->fout, obj, "text",
853 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar->obj), &a, &b, TRUE),
854 NULL);
858 * Callback that sends in a message the highlighted text from the text
859 * buffer which was passed in user_data
861 static void
862 cb_send_text_selection(GtkBuildable *obj, struct info *ar)
864 GtkTextIter a, b;
866 gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(ar->obj), &a, &b);
867 send_msg(ar->fout, obj, "text",
868 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar->obj), &a, &b, TRUE),
869 NULL);
873 * Callback that only sends "name:tag" and returns true
875 static bool
876 cb_simple(GtkBuildable *obj, struct info *ar)
878 send_msg(ar->fout, obj, ar->txt, NULL);
879 return true;
882 static void
883 cb_spin_button(GtkBuildable *obj, struct info *ar)
885 char str[BUFLEN], *lc = lc_numeric();
887 snprintf(str, BUFLEN, "%f", gtk_spin_button_get_value(GTK_SPIN_BUTTON(obj)));
888 send_msg(ar->fout, obj, ar->txt, str, NULL);
889 lc_numeric_free(lc);
892 static void
893 cb_switch(GtkBuildable *obj, void *pspec, struct info *ar)
895 (void) pspec;
896 send_msg(ar->fout, obj,
897 gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0",
898 NULL);
901 static void
902 cb_toggle_button(GtkBuildable *obj, struct info *ar)
904 send_msg(ar->fout, obj,
905 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0",
906 NULL);
909 static void
910 cb_tree_selection(GtkBuildable *obj, struct info *ar)
912 GtkTreeSelection *sel = GTK_TREE_SELECTION(obj);
913 GtkTreeView *view = gtk_tree_selection_get_tree_view(sel);
915 ar->obj = G_OBJECT(view);
916 send_msg(ar->fout, GTK_BUILDABLE(view), ar->txt, NULL);
917 gtk_tree_selection_selected_foreach(
918 sel, (GtkTreeSelectionForeachFunc) send_tree_row_msg, ar);
923 * ============================================================
924 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
925 * helper functions
926 * ============================================================
930 * The set of supported drawing operations
932 enum cairo_fn {
933 ARC,
934 ARC_NEGATIVE,
935 CLOSE_PATH,
936 CURVE_TO,
937 FILL,
938 FILL_PRESERVE,
939 LINE_TO,
940 MOVE_TO,
941 RECTANGLE,
942 REL_CURVE_TO,
943 REL_LINE_TO,
944 REL_MOVE_TO,
945 REL_MOVE_FOR,
946 RESET_CTM,
947 SET_DASH,
948 SET_FONT_FACE,
949 SET_FONT_SIZE,
950 SET_LINE_CAP,
951 SET_LINE_JOIN,
952 SET_LINE_WIDTH,
953 SET_SOURCE_RGBA,
954 SHOW_TEXT,
955 STROKE,
956 STROKE_PRESERVE,
957 TRANSFORM,
961 * Text placement mode for rel_move_for()
963 enum ref_point {
975 enum draw_op_policy {
976 APPEND,
977 BEFORE,
978 REPLACE,
982 * One single element of a drawing
984 struct draw_op {
985 struct draw_op *next;
986 struct draw_op *prev;
987 unsigned long long int id;
988 unsigned long long int before;
989 enum draw_op_policy policy;
990 enum cairo_fn op;
991 void *op_args;
995 * Argument sets for the various drawing operations
997 struct arc_args {
998 double x;
999 double y;
1000 double radius;
1001 double angle1;
1002 double angle2;
1005 struct curve_to_args {
1006 double x1;
1007 double y1;
1008 double x2;
1009 double y2;
1010 double x3;
1011 double y3;
1014 struct move_to_args {
1015 double x;
1016 double y;
1019 struct rectangle_args {
1020 double x;
1021 double y;
1022 double width;
1023 double height;
1026 struct rel_move_for_args {
1027 enum ref_point ref;
1028 int len;
1029 char text[];
1032 struct set_dash_args {
1033 int num_dashes;
1034 double dashes[];
1037 struct set_font_face_args {
1038 cairo_font_slant_t slant;
1039 cairo_font_weight_t weight;
1040 char family[];
1043 struct set_font_size_args {
1044 double size;
1047 struct set_line_cap_args {
1048 cairo_line_cap_t line_cap;
1051 struct set_line_join_args {
1052 cairo_line_join_t line_join;
1055 struct set_line_width_args {
1056 double width;
1059 struct set_source_rgba_args {
1060 GdkRGBA color;
1063 struct show_text_args {
1064 int len;
1065 char text[];
1068 struct transform_args {
1069 cairo_matrix_t matrix;
1072 static void
1073 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
1075 switch (op) {
1076 case LINE_TO: {
1077 struct move_to_args *args = op_args;
1079 cairo_line_to(cr, args->x, args->y);
1080 break;
1082 case REL_LINE_TO: {
1083 struct move_to_args *args = op_args;
1085 cairo_rel_line_to(cr, args->x, args->y);
1086 break;
1088 case MOVE_TO: {
1089 struct move_to_args *args = op_args;
1091 cairo_move_to(cr, args->x, args->y);
1092 break;
1094 case REL_MOVE_TO: {
1095 struct move_to_args *args = op_args;
1097 cairo_rel_move_to(cr, args->x, args->y);
1098 break;
1100 case ARC: {
1101 struct arc_args *args = op_args;
1103 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1104 break;
1106 case ARC_NEGATIVE: {
1107 struct arc_args *args = op_args;
1109 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1110 break;
1112 case CURVE_TO: {
1113 struct curve_to_args *args = op_args;
1115 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1116 break;
1118 case REL_CURVE_TO: {
1119 struct curve_to_args *args = op_args;
1121 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1122 break;
1124 case RECTANGLE: {
1125 struct rectangle_args *args = op_args;
1127 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
1128 break;
1130 case CLOSE_PATH:
1131 cairo_close_path(cr);
1132 break;
1133 case SHOW_TEXT: {
1134 struct show_text_args *args = op_args;
1136 cairo_show_text(cr, args->text);
1137 break;
1139 case REL_MOVE_FOR: {
1140 cairo_text_extents_t e;
1141 double dx = 0.0, dy = 0.0;
1142 struct rel_move_for_args *args = op_args;
1144 cairo_text_extents(cr, args->text, &e);
1145 switch (args->ref) {
1146 case C: dx = -e.width / 2; dy = e.height / 2; break;
1147 case E: dx = -e.width; dy = e.height / 2; break;
1148 case N: dx = -e.width / 2; dy = e.height; break;
1149 case NE: dx = -e.width; dy = e.height; break;
1150 case NW: dy = e.height; break;
1151 case S: dx = -e.width / 2; break;
1152 case SE: dx = -e.width; break;
1153 case SW: break;
1154 case W: dy = e.height / 2; break;
1155 default: ABORT; break;
1157 cairo_rel_move_to(cr, dx, dy);
1158 break;
1160 case RESET_CTM:
1161 cairo_identity_matrix(cr);
1162 break;
1163 case STROKE:
1164 cairo_stroke(cr);
1165 break;
1166 case STROKE_PRESERVE:
1167 cairo_stroke_preserve(cr);
1168 break;
1169 case FILL:
1170 cairo_fill(cr);
1171 break;
1172 case FILL_PRESERVE:
1173 cairo_fill_preserve(cr);
1174 break;
1175 case SET_DASH: {
1176 struct set_dash_args *args = op_args;
1178 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1179 break;
1181 case SET_FONT_FACE: {
1182 struct set_font_face_args *args = op_args;
1184 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1185 break;
1187 case SET_FONT_SIZE: {
1188 struct set_font_size_args *args = op_args;
1190 cairo_set_font_size(cr, args->size);
1191 break;
1193 case SET_LINE_CAP: {
1194 struct set_line_cap_args *args = op_args;
1196 cairo_set_line_cap(cr, args->line_cap);
1197 break;
1199 case SET_LINE_JOIN: {
1200 struct set_line_join_args *args = op_args;
1202 cairo_set_line_join(cr, args->line_join);
1203 break;
1205 case SET_LINE_WIDTH: {
1206 struct set_line_width_args *args = op_args;
1208 cairo_set_line_width(cr, args->width);
1209 break;
1211 case SET_SOURCE_RGBA: {
1212 struct set_source_rgba_args *args = op_args;
1214 gdk_cairo_set_source_rgba(cr, &args->color);
1215 break;
1217 case TRANSFORM: {
1218 struct transform_args *args = op_args;
1220 cairo_transform(cr, &args->matrix);
1221 break;
1223 default:
1224 ABORT;
1225 break;
1230 * Callback that draws on a GtkDrawingArea
1232 static gboolean
1233 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1235 struct draw_op *op;
1237 (void) data;
1238 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1239 op != NULL;
1240 op = op->next)
1241 draw(cr, op->op, op->op_args);
1242 return FALSE;
1247 * ============================================================
1248 * Manipulating the GUI
1249 * ============================================================
1252 static void
1253 update_button(struct ui_data *ud)
1255 if (eql(ud->action, "set_label"))
1256 gtk_button_set_label(GTK_BUTTON(ud->obj), ud->data);
1257 else
1258 ign_cmd(ud->type, ud->msg);
1261 static void
1262 update_calendar(struct ui_data *ud)
1264 GtkCalendar *calendar = GTK_CALENDAR(ud->obj);
1265 char dummy;
1266 int year = 0, month = 0, day = 0;
1268 if (eql(ud->action, "select_date") &&
1269 sscanf(ud->data, "%d-%d-%d %c", &year, &month, &day, &dummy) == 3) {
1270 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1271 gtk_calendar_select_month(calendar, --month, year);
1272 gtk_calendar_select_day(calendar, day);
1273 } else
1274 ign_cmd(ud->type, ud->msg);
1275 } else if (eql(ud->action, "mark_day") &&
1276 sscanf(ud->data, "%d %c", &day, &dummy) == 1) {
1277 if (day > 0 && day <= 31)
1278 gtk_calendar_mark_day(calendar, day);
1279 else
1280 ign_cmd(ud->type, ud->msg);
1281 } else if (eql(ud->action, "clear_marks") && sscanf(ud->data, " %c", &dummy) < 1)
1282 gtk_calendar_clear_marks(calendar);
1283 else
1284 ign_cmd(ud->type, ud->msg);
1288 * Common actions for various kinds of window. Return false if
1289 * command is ignored. Runs inside gtk_main().
1291 static bool
1292 update_class_window(struct ui_data *ud)
1294 GtkWindow *window = GTK_WINDOW(ud->obj);
1295 char dummy;
1296 int x, y;
1298 if (eql(ud->action, "set_title"))
1299 gtk_window_set_title(window, ud->data);
1300 else if (eql(ud->action, "fullscreen") && sscanf(ud->data, " %c", &dummy) < 1)
1301 gtk_window_fullscreen(window);
1302 else if (eql(ud->action, "unfullscreen") && sscanf(ud->data, " %c", &dummy) < 1)
1303 gtk_window_unfullscreen(window);
1304 else if (eql(ud->action, "resize") &&
1305 sscanf(ud->data, "%d %d %c", &x, &y, &dummy) == 2)
1306 gtk_window_resize(window, x, y);
1307 else if (eql(ud->action, "resize") && sscanf(ud->data, " %c", &dummy) < 1) {
1308 gtk_window_get_default_size(window, &x, &y);
1309 gtk_window_resize(window, x, y);
1310 } else if (eql(ud->action, "move") &&
1311 sscanf(ud->data, "%d %d %c", &x, &y, &dummy) == 2)
1312 gtk_window_move(window, x, y);
1313 else
1314 return false;
1315 return true;
1318 static void
1319 update_color_button(struct ui_data *ud)
1321 GdkRGBA color;
1323 if (eql(ud->action, "set_color")) {
1324 gdk_rgba_parse(&color, ud->data);
1325 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(ud->obj), &color);
1326 } else
1327 ign_cmd(ud->type, ud->msg);
1330 static void
1331 update_combo_box_text(struct ui_data *ud)
1333 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(ud->obj);
1334 char data1[strlen(ud->data) + 1];
1335 char dummy;
1336 int val;
1338 strcpy(data1, ud->data);
1339 if (eql(ud->action, "prepend_text"))
1340 gtk_combo_box_text_prepend_text(combobox, data1);
1341 else if (eql(ud->action, "append_text"))
1342 gtk_combo_box_text_append_text(combobox, data1);
1343 else if (eql(ud->action, "remove") && sscanf(ud->data, "%d %c", &val, &dummy) == 1)
1344 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1345 else if (eql(ud->action, "insert_text")) {
1346 char *position = strtok(data1, WHITESPACE);
1347 char *text = strtok(NULL, WHITESPACE);
1349 gtk_combo_box_text_insert_text(combobox,
1350 strtol(position, NULL, 10), text);
1351 } else
1352 ign_cmd(ud->type, ud->msg);
1356 * update_drawing_area(), which runs inside gtk_main(), maintains a
1357 * list of drawing operations. It needs a few helper functions. It
1358 * is the responsibility of cb_draw() to actually execute the list.
1361 enum draw_op_stat {
1362 FAILURE,
1363 SUCCESS,
1364 NEED_REDRAW,
1368 * Fill structure *op with the drawing operation according to action
1369 * and with the appropriate set of arguments
1371 static enum draw_op_stat
1372 set_draw_op(struct draw_op *op, const char *action, const char *data)
1374 char dummy;
1375 const char *raw_args = data;
1376 enum draw_op_stat result = SUCCESS;
1377 int args_start = 0;
1379 if (sscanf(data, "=%llu %n", &op->id, &args_start) == 1) {
1380 op->policy = REPLACE;
1381 result = NEED_REDRAW;
1382 } else if (sscanf(data, "%llu<%llu %n", &op->id, &op->before, &args_start) == 2) {
1383 op->policy = BEFORE;
1384 result = NEED_REDRAW;
1385 } else if (sscanf(data, "%llu %n", &op->id, &args_start) == 1)
1386 op->policy = APPEND;
1387 else
1388 return FAILURE;
1389 raw_args += args_start;
1390 if (eql(action, "line_to")) {
1391 struct move_to_args *args;
1393 if ((args = malloc(sizeof(*args))) == NULL)
1394 OOM_ABORT;
1395 op->op = LINE_TO;
1396 op->op_args = args;
1397 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1398 return FAILURE;
1399 } else if (eql(action, "rel_line_to")) {
1400 struct move_to_args *args;
1402 if ((args = malloc(sizeof(*args))) == NULL)
1403 OOM_ABORT;
1404 op->op = REL_LINE_TO;
1405 op->op_args = args;
1406 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1407 return FAILURE;
1408 } else if (eql(action, "move_to")) {
1409 struct move_to_args *args;
1411 if ((args = malloc(sizeof(*args))) == NULL)
1412 OOM_ABORT;
1413 op->op = MOVE_TO;
1414 op->op_args = args;
1415 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1416 return FAILURE;
1417 } else if (eql(action, "rel_move_to")) {
1418 struct move_to_args *args;
1420 if ((args = malloc(sizeof(*args))) == NULL)
1421 OOM_ABORT;
1422 op->op = REL_MOVE_TO;
1423 op->op_args = args;
1424 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1425 return FAILURE;
1426 } else if (eql(action, "arc")) {
1427 struct arc_args *args;
1428 double deg1, deg2;
1430 if ((args = malloc(sizeof(*args))) == NULL)
1431 OOM_ABORT;
1432 op->op = ARC;
1433 op->op_args = args;
1434 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1435 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1436 return FAILURE;
1437 args->angle1 = deg1 * (M_PI / 180.L);
1438 args->angle2 = deg2 * (M_PI / 180.L);
1439 } else if (eql(action, "arc_negative")) {
1440 double deg1, deg2;
1441 struct arc_args *args;
1443 if ((args = malloc(sizeof(*args))) == NULL)
1444 OOM_ABORT;
1445 op->op = ARC_NEGATIVE;
1446 op->op_args = args;
1447 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1448 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1449 return FAILURE;
1450 args->angle1 = deg1 * (M_PI / 180.L);
1451 args->angle2 = deg2 * (M_PI / 180.L);
1452 } else if (eql(action, "curve_to")) {
1453 struct curve_to_args *args;
1455 if ((args = malloc(sizeof(*args))) == NULL)
1456 OOM_ABORT;
1457 op->op = CURVE_TO;
1458 op->op_args = args;
1459 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1460 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1461 return FAILURE;
1462 } else if (eql(action, "rel_curve_to")) {
1463 struct curve_to_args *args;
1465 if ((args = malloc(sizeof(*args))) == NULL)
1466 OOM_ABORT;
1467 op->op = REL_CURVE_TO;
1468 op->op_args = args;
1469 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1470 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1471 return FAILURE;
1472 } else if (eql(action, "rectangle")) {
1473 struct rectangle_args *args;
1475 if ((args = malloc(sizeof(*args))) == NULL)
1476 OOM_ABORT;
1477 op->op = RECTANGLE;
1478 op->op_args = args;
1479 if (sscanf(raw_args, "%lf %lf %lf %lf %c",
1480 &args->x, &args->y, &args->width, &args->height, &dummy) != 4)
1481 return FAILURE;
1482 } else if (eql(action, "close_path")) {
1483 op->op = CLOSE_PATH;
1484 if (sscanf(raw_args, " %c", &dummy) > 0)
1485 return FAILURE;
1486 op->op_args = NULL;
1487 } else if (eql(action, "show_text")) {
1488 struct show_text_args *args;
1489 int len;
1491 len = strlen(raw_args) + 1;
1492 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1493 OOM_ABORT;
1494 op->op = SHOW_TEXT;
1495 op->op_args = args;
1496 args->len = len; /* not used */
1497 strncpy(args->text, raw_args, len);
1498 result = NEED_REDRAW;
1499 } else if (eql(action, "rel_move_for")) {
1500 char ref_point[2 + 1];
1501 int start, len;
1502 struct rel_move_for_args *args;
1504 if (sscanf(raw_args, "%2s %n", ref_point, &start) < 1)
1505 return FAILURE;
1506 len = strlen(raw_args + start) + 1;
1507 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1508 OOM_ABORT;
1509 if (eql(ref_point, "c"))
1510 args->ref = C;
1511 else if (eql(ref_point, "e"))
1512 args->ref = E;
1513 else if (eql(ref_point, "n"))
1514 args->ref = N;
1515 else if (eql(ref_point, "ne"))
1516 args->ref = NE;
1517 else if (eql(ref_point, "nw"))
1518 args->ref = NW;
1519 else if (eql(ref_point, "s"))
1520 args->ref = S;
1521 else if (eql(ref_point, "se"))
1522 args->ref = SE;
1523 else if (eql(ref_point, "sw"))
1524 args->ref = SW;
1525 else if (eql(ref_point, "w"))
1526 args->ref = W;
1527 else
1528 return FAILURE;
1529 op->op = REL_MOVE_FOR;
1530 op->op_args = args;
1531 args->len = len; /* not used */
1532 strncpy(args->text, (raw_args + start), len);
1533 } else if (eql(action, "stroke")) {
1534 op->op = STROKE;
1535 if (sscanf(raw_args, " %c", &dummy) > 0)
1536 return FAILURE;
1537 op->op_args = NULL;
1538 result = NEED_REDRAW;
1539 } else if (eql(action, "stroke_preserve")) {
1540 op->op = STROKE_PRESERVE;
1541 if (sscanf(raw_args, " %c", &dummy) > 0)
1542 return FAILURE;
1543 op->op_args = NULL;
1544 result = NEED_REDRAW;
1545 } else if (eql(action, "fill")) {
1546 op->op = FILL;
1547 if (sscanf(raw_args, " %c", &dummy) > 0)
1548 return FAILURE;
1549 op->op_args = NULL;
1550 result = NEED_REDRAW;
1551 } else if (eql(action, "fill_preserve")) {
1552 op->op = FILL_PRESERVE;
1553 if (sscanf(raw_args, " %c", &dummy) > 0)
1554 return FAILURE;
1555 op->op_args = NULL;
1556 result = NEED_REDRAW;
1557 } else if (eql(action, "set_dash")) {
1558 char *next, *end;
1559 char data1[strlen(raw_args) + 1];
1560 int n, i;
1561 struct set_dash_args *args;
1563 strcpy(data1, raw_args);
1564 next = end = data1;
1565 n = -1;
1566 do {
1567 n++;
1568 next = end;
1569 strtod(next, &end);
1570 } while (next != end);
1571 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1572 OOM_ABORT;
1573 op->op = SET_DASH;
1574 op->op_args = args;
1575 args->num_dashes = n;
1576 for (i = 0, next = data1; i < n; i++, next = end) {
1577 args->dashes[i] = strtod(next, &end);
1579 } else if (eql(action, "set_font_face")) {
1580 char slant[7 + 1]; /* "oblique" */
1581 char weight[6 + 1]; /* "normal" */
1582 int family_start, family_len;
1583 struct set_font_face_args *args;
1585 if (sscanf(raw_args, "%7s %6s %n%*s", slant, weight, &family_start) != 2)
1586 return FAILURE;
1587 family_len = strlen(raw_args + family_start) + 1;
1588 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1589 OOM_ABORT;
1590 op->op = SET_FONT_FACE;
1591 op->op_args = args;
1592 strncpy(args->family, raw_args + family_start, family_len);
1593 if (eql(slant, "normal"))
1594 args->slant = CAIRO_FONT_SLANT_NORMAL;
1595 else if (eql(slant, "italic"))
1596 args->slant = CAIRO_FONT_SLANT_ITALIC;
1597 else if (eql(slant, "oblique"))
1598 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1599 else
1600 return FAILURE;
1601 if (eql(weight, "normal"))
1602 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1603 else if (eql(weight, "bold"))
1604 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1605 else
1606 return FAILURE;
1607 } else if (eql(action, "set_font_size")) {
1608 struct set_font_size_args *args;
1610 if ((args = malloc(sizeof(*args))) == NULL)
1611 OOM_ABORT;
1612 op->op = SET_FONT_SIZE;
1613 op->op_args = args;
1614 if (sscanf(raw_args, "%lf %c", &args->size, &dummy) != 1)
1615 return FAILURE;
1616 } else if (eql(action, "set_line_cap")) {
1617 char str[6 + 1]; /* "square" */
1618 struct set_line_cap_args *args;
1620 if ((args = malloc(sizeof(*args))) == NULL)
1621 OOM_ABORT;
1622 op->op = SET_LINE_CAP;
1623 op->op_args = args;
1624 if (sscanf(raw_args, "%6s %c", str, &dummy) != 1)
1625 return FAILURE;
1626 if (eql(str, "butt"))
1627 args->line_cap = CAIRO_LINE_CAP_BUTT;
1628 else if (eql(str, "round"))
1629 args->line_cap = CAIRO_LINE_CAP_ROUND;
1630 else if (eql(str, "square"))
1631 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1632 else
1633 return FAILURE;
1634 } else if (eql(action, "set_line_join")) {
1635 char str[5 + 1]; /* "miter" */
1636 struct set_line_join_args *args;
1638 if ((args = malloc(sizeof(*args))) == NULL)
1639 OOM_ABORT;
1640 op->op = SET_LINE_JOIN;
1641 op->op_args = args;
1642 if (sscanf(raw_args, "%5s %c", str, &dummy) != 1)
1643 return FAILURE;
1644 if (eql(str, "miter"))
1645 args->line_join = CAIRO_LINE_JOIN_MITER;
1646 else if (eql(str, "round"))
1647 args->line_join = CAIRO_LINE_JOIN_ROUND;
1648 else if (eql(str, "bevel"))
1649 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1650 else
1651 return FAILURE;
1652 } else if (eql(action, "set_line_width")) {
1653 struct set_line_width_args *args;
1655 if ((args = malloc(sizeof(*args))) == NULL)
1656 OOM_ABORT;
1657 op->op = SET_LINE_WIDTH;
1658 op->op_args = args;
1659 if (sscanf(raw_args, "%lf %c", &args->width, &dummy) != 1)
1660 return FAILURE;
1661 } else if (eql(action, "set_source_rgba")) {
1662 struct set_source_rgba_args *args;
1664 if ((args = malloc(sizeof(*args))) == NULL)
1665 OOM_ABORT;
1666 op->op = SET_SOURCE_RGBA;
1667 op->op_args = args;
1668 gdk_rgba_parse(&args->color, raw_args);
1669 } else if (eql(action, "transform")) {
1670 char dummy;
1671 double xx, yx, xy, yy, x0, y0;
1673 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1674 &xx, &yx, &xy, &yy, &x0, &y0, &dummy) == 6) {
1675 struct transform_args *args;
1677 if ((args = malloc(sizeof(*args))) == NULL)
1678 OOM_ABORT;
1679 op->op_args = args;
1680 op->op = TRANSFORM;
1681 cairo_matrix_init(&args->matrix, xx, yx, xy, yy, x0, y0);
1682 } else if (sscanf(raw_args, " %c", &dummy) < 1) {
1683 op->op = RESET_CTM;
1684 op->op_args = NULL;
1685 } else
1686 return FAILURE;
1687 } else if (eql(action, "translate")) {
1688 double tx, ty;
1689 struct transform_args *args;
1691 if ((args = malloc(sizeof(*args))) == NULL)
1692 OOM_ABORT;
1693 op->op = TRANSFORM;
1694 op->op_args = args;
1695 if (sscanf(raw_args, "%lf %lf %c", &tx, &ty, &dummy) != 2)
1696 return FAILURE;
1697 cairo_matrix_init_translate(&args->matrix, tx, ty);
1698 } else if (eql(action, "scale")) {
1699 double sx, sy;
1700 struct transform_args *args;
1702 if ((args = malloc(sizeof(*args))) == NULL)
1703 OOM_ABORT;
1704 op->op = TRANSFORM;
1705 op->op_args = args;
1706 if (sscanf(raw_args, "%lf %lf %c", &sx, &sy, &dummy) != 2)
1707 return FAILURE;
1708 cairo_matrix_init_scale(&args->matrix, sx, sy);
1709 } else if (eql(action, "rotate")) {
1710 double angle;
1711 struct transform_args *args;
1713 if ((args = malloc(sizeof(*args))) == NULL)
1714 OOM_ABORT;
1715 op->op = TRANSFORM;
1716 op->op_args = args;
1717 if (sscanf(raw_args, "%lf %c", &angle, &dummy) != 1)
1718 return FAILURE;
1719 cairo_matrix_init_rotate(&args->matrix, angle * (M_PI / 180.L));
1720 } else
1721 return FAILURE;
1722 return result;
1726 * Add another element to widget's "draw_ops" list
1728 static enum draw_op_stat
1729 ins_draw_op(GObject *widget, const char *action, const char *data)
1731 enum draw_op_stat result;
1732 struct draw_op *new_op = NULL, *draw_ops = NULL, *prev_op = NULL;
1734 if ((new_op = malloc(sizeof(*new_op))) == NULL)
1735 OOM_ABORT;
1736 new_op->op_args = NULL;
1737 new_op->next = NULL;
1738 if ((result = set_draw_op(new_op, action, data)) == FAILURE) {
1739 free(new_op->op_args);
1740 free(new_op);
1741 return FAILURE;
1743 switch (new_op->policy) {
1744 case APPEND:
1745 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1746 g_object_set_data(widget, "draw_ops", new_op);
1747 else {
1748 for (prev_op = draw_ops;
1749 prev_op->next != NULL;
1750 prev_op = prev_op->next);
1751 prev_op->next = new_op;
1753 break;
1754 case BEFORE:
1755 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1756 draw_ops != NULL && draw_ops->id != new_op->before;
1757 prev_op = draw_ops, draw_ops = draw_ops->next);
1758 if (prev_op == NULL) { /* prepend a new first element */
1759 g_object_set_data(widget, "draw_ops", new_op);
1760 new_op->next = draw_ops;
1761 } else if (draw_ops == NULL) /* append */
1762 prev_op->next = new_op;
1763 else { /* insert */
1764 new_op->next = draw_ops;
1765 prev_op->next = new_op;
1767 break;
1768 case REPLACE:
1769 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1770 draw_ops != NULL && draw_ops->id != new_op->id;
1771 prev_op = draw_ops, draw_ops = draw_ops->next);
1772 if (draw_ops == NULL && prev_op == NULL) /* start a new list */
1773 g_object_set_data(widget, "draw_ops", new_op);
1774 else if (prev_op == NULL) { /* replace the first element */
1775 g_object_set_data(widget, "draw_ops", new_op);
1776 new_op->next = draw_ops->next;
1777 free(draw_ops->op_args);
1778 free(draw_ops);
1779 } else if (draw_ops == NULL) /* append */
1780 prev_op->next = new_op;
1781 else { /* replace some other element */
1782 new_op->next = draw_ops->next;
1783 prev_op->next = new_op;
1784 free(draw_ops->op_args);
1785 free(draw_ops);
1787 break;
1788 default:
1789 ABORT;
1790 break;
1792 return result;
1796 * Remove all elements with the given id from widget's "draw_ops" list
1798 static enum draw_op_stat
1799 rem_draw_op(GObject *widget, const char *data)
1801 char dummy;
1802 struct draw_op *op, *next_op, *prev_op = NULL;
1803 unsigned long long int id;
1805 if (sscanf(data, "%llu %c", &id, &dummy) != 1)
1806 return FAILURE;
1807 op = g_object_get_data(widget, "draw_ops");
1808 while (op != NULL) {
1809 next_op = op->next;
1810 if (op->id == id) {
1811 if (prev_op == NULL) /* list head */
1812 g_object_set_data(widget, "draw_ops", op->next);
1813 else
1814 prev_op->next = op->next;
1815 free(op->op_args);
1816 free(op);
1817 } else
1818 prev_op = op;
1819 op = next_op;
1821 return NEED_REDRAW;
1824 static gboolean
1825 refresh_widget(GtkWidget *widget)
1827 gint height = gtk_widget_get_allocated_height(widget);
1828 gint width = gtk_widget_get_allocated_width(widget);
1830 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1831 return G_SOURCE_REMOVE;
1834 static void
1835 update_drawing_area(struct ui_data *ud)
1837 enum draw_op_stat dost;
1839 if (eql(ud->action, "remove"))
1840 dost = rem_draw_op(ud->obj, ud->data);
1841 else
1842 dost = ins_draw_op(ud->obj, ud->action, ud->data);
1843 switch (dost) {
1844 case NEED_REDRAW:
1845 gdk_threads_add_idle_full(G_PRIORITY_LOW,
1846 (GSourceFunc) refresh_widget,
1847 GTK_WIDGET(ud->obj), NULL);
1848 break;
1849 case FAILURE:
1850 ign_cmd(ud->type, ud->msg);
1851 break;
1852 case SUCCESS:
1853 break;
1854 default:
1855 ABORT;
1856 break;
1860 static void
1861 update_entry(struct ui_data *ud)
1863 GtkEntry *entry = GTK_ENTRY(ud->obj);
1865 if (eql(ud->action, "set_text"))
1866 gtk_entry_set_text(entry, ud->data);
1867 else if (eql(ud->action, "set_placeholder_text"))
1868 gtk_entry_set_placeholder_text(entry, ud->data);
1869 else
1870 ign_cmd(ud->type, ud->msg);
1873 static void
1874 update_expander(struct ui_data *ud)
1876 GtkExpander *expander = GTK_EXPANDER(ud->obj);
1877 char dummy;
1878 unsigned int val;
1880 if (eql(ud->action, "set_expanded") &&
1881 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
1882 gtk_expander_set_expanded(expander, val);
1883 else if (eql(ud->action, "set_label"))
1884 gtk_expander_set_label(expander, ud->data);
1885 else
1886 ign_cmd(ud->type, ud->msg);
1889 static void
1890 update_file_chooser_button(struct ui_data *ud)
1892 if (eql(ud->action, "set_filename"))
1893 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(ud->obj), ud->data);
1894 else
1895 ign_cmd(ud->type, ud->msg);
1898 static void
1899 update_file_chooser_dialog(struct ui_data *ud)
1901 GtkFileChooser *chooser = GTK_FILE_CHOOSER(ud->obj);
1903 if (eql(ud->action, "set_filename"))
1904 gtk_file_chooser_set_filename(chooser, ud->data);
1905 else if (eql(ud->action, "set_current_name"))
1906 gtk_file_chooser_set_current_name(chooser, ud->data);
1907 else if (update_class_window(ud));
1908 else
1909 ign_cmd(ud->type, ud->msg);
1912 static void
1913 update_focus(struct ui_data *ud){
1914 char dummy;
1916 if (GTK_IS_WIDGET(ud->obj) &&
1917 sscanf(ud->data, " %c", &dummy) < 1 &&
1918 gtk_widget_get_can_focus(GTK_WIDGET(ud->obj)))
1919 gtk_widget_grab_focus(GTK_WIDGET(ud->obj));
1920 else
1921 ign_cmd(ud->type, ud->msg);
1924 static void
1925 update_font_button(struct ui_data *ud){
1926 GtkFontButton *font_button = GTK_FONT_BUTTON(ud->obj);
1928 if (eql(ud->action, "set_font_name"))
1929 gtk_font_button_set_font_name(font_button, ud->data);
1930 else
1931 ign_cmd(ud->type, ud->msg);
1934 static void
1935 update_frame(struct ui_data *ud)
1937 if (eql(ud->action, "set_label"))
1938 gtk_frame_set_label(GTK_FRAME(ud->obj), ud->data);
1939 else
1940 ign_cmd(ud->type, ud->msg);
1943 static void
1944 update_image(struct ui_data *ud)
1946 GtkIconSize size;
1947 GtkImage *image = GTK_IMAGE(ud->obj);
1949 gtk_image_get_icon_name(image, NULL, &size);
1950 if (eql(ud->action, "set_from_file"))
1951 gtk_image_set_from_file(image, ud->data);
1952 else if (eql(ud->action, "set_from_icon_name"))
1953 gtk_image_set_from_icon_name(image, ud->data, size);
1954 else
1955 ign_cmd(ud->type, ud->msg);
1958 static void
1959 update_label(struct ui_data *ud)
1961 if (eql(ud->action, "set_text"))
1962 gtk_label_set_text(GTK_LABEL(ud->obj), ud->data);
1963 else
1964 ign_cmd(ud->type, ud->msg);
1967 struct handler_id {
1968 unsigned int id; /* returned by g_signal_connect() and friends */
1969 bool blocked; /* we avoid multiple blocking/unblocking */
1970 struct handler_id *next;
1973 static void
1974 update_blocked(struct ui_data *ud)
1976 char dummy;
1977 struct handler_id *hid;
1978 unsigned long int val;
1980 if (sscanf(ud->data, "%lu %c", &val, &dummy) == 1 && val < 2) {
1981 for (hid = g_object_get_data(ud->obj, "signal-id");
1982 hid != NULL; hid = hid->next) {
1983 if (val == 0 && hid->blocked == true) {
1984 g_signal_handler_unblock(ud->obj, hid->id);
1985 hid->blocked = false;
1986 } else if (val == 1 && hid->blocked == false) {
1987 g_signal_handler_block(ud->obj, hid->id);
1988 hid->blocked = true;
1991 } else
1992 ign_cmd(ud->type, ud->msg);
1995 static void
1996 update_notebook(struct ui_data *ud)
1998 char dummy;
1999 int val, n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(ud->obj));
2001 if (eql(ud->action, "set_current_page") &&
2002 sscanf(ud->data, "%d %c", &val, &dummy) == 1 &&
2003 val >= 0 && val < n_pages)
2004 gtk_notebook_set_current_page(GTK_NOTEBOOK(ud->obj), val);
2005 else
2006 ign_cmd(ud->type, ud->msg);
2009 static void
2010 update_nothing(struct ui_data *ud)
2012 (void) ud;
2015 static void
2016 update_print_dialog(struct ui_data *ud)
2018 GtkPageSetup *page_setup;
2019 GtkPrintJob *job;
2020 GtkPrintSettings *settings;
2021 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(ud->obj);
2022 GtkPrinter *printer;
2023 gint response_id;
2025 if (eql(ud->action, "print")) {
2026 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
2027 switch (response_id) {
2028 case GTK_RESPONSE_OK:
2029 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
2030 settings = gtk_print_unix_dialog_get_settings(dialog);
2031 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
2032 job = gtk_print_job_new(ud->data, printer, settings, page_setup);
2033 if (gtk_print_job_set_source_file(job, ud->data, NULL))
2034 gtk_print_job_send(job, NULL, NULL, NULL);
2035 else
2036 ign_cmd(ud->type, ud->msg);
2037 g_clear_object(&settings);
2038 g_clear_object(&job);
2039 break;
2040 case GTK_RESPONSE_CANCEL:
2041 case GTK_RESPONSE_DELETE_EVENT:
2042 break;
2043 default:
2044 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
2045 widget_id(GTK_BUILDABLE(dialog)), response_id);
2046 break;
2048 gtk_widget_hide(GTK_WIDGET(dialog));
2049 } else
2050 ign_cmd(ud->type, ud->msg);
2053 static void
2054 update_progress_bar(struct ui_data *ud)
2056 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(ud->obj);
2057 char dummy;
2058 double frac;
2060 if (eql(ud->action, "set_text"))
2061 gtk_progress_bar_set_text(progressbar, *(ud->data) == '\0' ? NULL : ud->data);
2062 else if (eql(ud->action, "set_fraction") &&
2063 sscanf(ud->data, "%lf %c", &frac, &dummy) == 1)
2064 gtk_progress_bar_set_fraction(progressbar, frac);
2065 else
2066 ign_cmd(ud->type, ud->msg);
2069 static void
2070 update_scale(struct ui_data *ud)
2072 GtkRange *range = GTK_RANGE(ud->obj);
2073 char dummy;
2074 double val1, val2;
2076 if (eql(ud->action, "set_value") && sscanf(ud->data, "%lf %c", &val1, &dummy) == 1)
2077 gtk_range_set_value(range, val1);
2078 else if (eql(ud->action, "set_fill_level") &&
2079 sscanf(ud->data, "%lf %c", &val1, &dummy) == 1) {
2080 gtk_range_set_fill_level(range, val1);
2081 gtk_range_set_show_fill_level(range, TRUE);
2082 } else if (eql(ud->action, "set_fill_level") &&
2083 sscanf(ud->data, " %c", &dummy) < 1)
2084 gtk_range_set_show_fill_level(range, FALSE);
2085 else if (eql(ud->action, "set_range") &&
2086 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2087 gtk_range_set_range(range, val1, val2);
2088 else if (eql(ud->action, "set_increments") &&
2089 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2090 gtk_range_set_increments(range, val1, val2);
2091 else
2092 ign_cmd(ud->type, ud->msg);
2095 static void
2096 update_scrolled_window(struct ui_data *ud)
2098 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(ud->obj);
2099 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
2100 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
2101 char dummy;
2102 double d0, d1;
2104 if (eql(ud->action, "hscroll") && sscanf(ud->data, "%lf %c", &d0, &dummy) == 1)
2105 gtk_adjustment_set_value(hadj, d0);
2106 else if (eql(ud->action, "vscroll") && sscanf(ud->data, "%lf %c", &d0, &dummy) == 1)
2107 gtk_adjustment_set_value(vadj, d0);
2108 else if (eql(ud->action, "hscroll_to_range") &&
2109 sscanf(ud->data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2110 gtk_adjustment_clamp_page(hadj, d0, d1);
2111 else if (eql(ud->action, "vscroll_to_range") &&
2112 sscanf(ud->data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2113 gtk_adjustment_clamp_page(vadj, d0, d1);
2114 else
2115 ign_cmd(ud->type, ud->msg);
2118 static void
2119 update_sensitivity(struct ui_data *ud)
2121 char dummy;
2122 unsigned int val;
2124 if (GTK_IS_WIDGET(ud->obj) &&
2125 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2126 gtk_widget_set_sensitive(GTK_WIDGET(ud->obj), val);
2127 else
2128 ign_cmd(ud->type, ud->msg);
2131 static void
2132 update_size_request(struct ui_data *ud)
2134 char dummy;
2135 int x, y;
2137 if (GTK_IS_WIDGET(ud->obj) &&
2138 sscanf(ud->data, "%d %d %c", &x, &y, &dummy) == 2)
2139 gtk_widget_set_size_request(GTK_WIDGET(ud->obj), x, y);
2140 else if (GTK_IS_WIDGET(ud->obj) &&
2141 sscanf(ud->data, " %c", &dummy) < 1)
2142 gtk_widget_set_size_request(GTK_WIDGET(ud->obj), -1, -1);
2143 else
2144 ign_cmd(ud->type, ud->msg);
2147 static void
2148 update_socket(struct ui_data *ud)
2150 GtkSocket *socket = GTK_SOCKET(ud->obj);
2151 Window id;
2152 char str[BUFLEN], dummy;
2154 if (eql(ud->action, "id") && sscanf(ud->data, " %c", &dummy) < 1) {
2155 id = gtk_socket_get_id(socket);
2156 snprintf(str, BUFLEN, "%lu", id);
2157 send_msg(ud->args->fout, GTK_BUILDABLE(socket), "id", str, NULL);
2158 } else
2159 ign_cmd(ud->type, ud->msg);
2162 static void
2163 update_spin_button(struct ui_data *ud)
2165 GtkSpinButton *spinbutton = GTK_SPIN_BUTTON(ud->obj);
2166 char dummy;
2167 double val1, val2;
2169 if (eql(ud->action, "set_text") && /* TODO: rename to "set_value" */
2170 sscanf(ud->data, "%lf %c", &val1, &dummy) == 1)
2171 gtk_spin_button_set_value(spinbutton, val1);
2172 else if (eql(ud->action, "set_range") &&
2173 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2174 gtk_spin_button_set_range(spinbutton, val1, val2);
2175 else if (eql(ud->action, "set_increments") &&
2176 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2177 gtk_spin_button_set_increments(spinbutton, val1, val2);
2178 else
2179 ign_cmd(ud->type, ud->msg);
2182 static void
2183 update_spinner(struct ui_data *ud)
2185 GtkSpinner *spinner = GTK_SPINNER(ud->obj);
2186 char dummy;
2188 if (eql(ud->action, "start") && sscanf(ud->data, " %c", &dummy) < 1)
2189 gtk_spinner_start(spinner);
2190 else if (eql(ud->action, "stop") && sscanf(ud->data, " %c", &dummy) < 1)
2191 gtk_spinner_stop(spinner);
2192 else
2193 ign_cmd(ud->type, ud->msg);
2196 static void
2197 update_statusbar(struct ui_data *ud)
2199 GtkStatusbar *statusbar = GTK_STATUSBAR(ud->obj);
2200 char *ctx_msg, dummy;
2201 const char *status_msg;
2202 int ctx_len, t;
2204 /* TODO: remove "push", "pop", "remove_all"; rename "push_id" to "push", etc. */
2205 if ((ctx_msg = malloc(strlen(ud->data) + 1)) == NULL)
2206 OOM_ABORT;
2207 t = sscanf(ud->data, "%s %n%c", ctx_msg, &ctx_len, &dummy);
2208 status_msg = ud->data + ctx_len;
2209 if (eql(ud->action, "push"))
2210 gtk_statusbar_push(statusbar,
2211 gtk_statusbar_get_context_id(statusbar, "0"),
2212 ud->data);
2213 else if (eql(ud->action, "push_id") && t >= 1)
2214 gtk_statusbar_push(statusbar,
2215 gtk_statusbar_get_context_id(statusbar, ctx_msg),
2216 status_msg);
2217 else if (eql(ud->action, "pop") && t < 1)
2218 gtk_statusbar_pop(statusbar,
2219 gtk_statusbar_get_context_id(statusbar, "0"));
2220 else if (eql(ud->action, "pop_id") && t == 1)
2221 gtk_statusbar_pop(statusbar,
2222 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2223 else if (eql(ud->action, "remove_all") && t < 1)
2224 gtk_statusbar_remove_all(statusbar,
2225 gtk_statusbar_get_context_id(statusbar, "0"));
2226 else if (eql(ud->action, "remove_all_id") && t == 1)
2227 gtk_statusbar_remove_all(statusbar,
2228 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2229 else
2230 ign_cmd(ud->type, ud->msg);
2231 free(ctx_msg);
2234 static void
2235 update_switch(struct ui_data *ud)
2237 char dummy;
2238 unsigned int val;
2240 if (eql(ud->action, "set_active") &&
2241 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2242 gtk_switch_set_active(GTK_SWITCH(ud->obj), val);
2243 else
2244 ign_cmd(ud->type, ud->msg);
2247 static void
2248 update_text_view(struct ui_data *ud)
2250 FILE *sv;
2251 GtkTextView *view = GTK_TEXT_VIEW(ud->obj);
2252 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
2253 GtkTextIter a, b;
2254 char dummy;
2255 int val;
2257 if (eql(ud->action, "set_text"))
2258 gtk_text_buffer_set_text(textbuf, ud->data, -1);
2259 else if (eql(ud->action, "delete") && sscanf(ud->data, " %c", &dummy) < 1) {
2260 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2261 gtk_text_buffer_delete(textbuf, &a, &b);
2262 } else if (eql(ud->action, "insert_at_cursor"))
2263 gtk_text_buffer_insert_at_cursor(textbuf, ud->data, -1);
2264 else if (eql(ud->action, "place_cursor") && eql(ud->data, "end")) {
2265 gtk_text_buffer_get_end_iter(textbuf, &a);
2266 gtk_text_buffer_place_cursor(textbuf, &a);
2267 } else if (eql(ud->action, "place_cursor") &&
2268 sscanf(ud->data, "%d %c", &val, &dummy) == 1) {
2269 gtk_text_buffer_get_iter_at_offset(textbuf, &a, val);
2270 gtk_text_buffer_place_cursor(textbuf, &a);
2271 } else if (eql(ud->action, "place_cursor_at_line") &&
2272 sscanf(ud->data, "%d %c", &val, &dummy) == 1) {
2273 gtk_text_buffer_get_iter_at_line(textbuf, &a, val);
2274 gtk_text_buffer_place_cursor(textbuf, &a);
2275 } else if (eql(ud->action, "scroll_to_cursor") &&
2276 sscanf(ud->data, " %c", &dummy) < 1)
2277 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
2278 0., 0, 0., 0.);
2279 else if (eql(ud->action, "save") && ud->data != NULL &&
2280 (sv = fopen(ud->data, "w")) != NULL) {
2281 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2282 send_msg(sv, GTK_BUILDABLE(view), "insert_at_cursor",
2283 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
2284 fclose(sv);
2285 } else
2286 ign_cmd(ud->type, ud->msg);
2289 static void
2290 update_toggle_button(struct ui_data *ud)
2292 char dummy;
2293 unsigned int val;
2295 if (eql(ud->action, "set_label"))
2296 gtk_button_set_label(GTK_BUTTON(ud->obj), ud->data);
2297 else if (eql(ud->action, "set_active") &&
2298 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2299 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ud->obj), val);
2300 else
2301 ign_cmd(ud->type, ud->msg);
2304 static void
2305 update_tooltip_text(struct ui_data *ud)
2307 if (GTK_IS_WIDGET(ud->obj))
2308 gtk_widget_set_tooltip_text(GTK_WIDGET(ud->obj), ud->data);
2309 else
2310 ign_cmd(ud->type, ud->msg);
2314 * update_tree_view(), which runs inside gtk_main(), needs a few
2315 * helper functions
2319 * Check if s is a valid string representation of a GtkTreePath
2321 static bool
2322 is_path_string(char *s)
2324 return s != NULL &&
2325 strlen(s) == strspn(s, ":0123456789") &&
2326 strstr(s, "::") == NULL &&
2327 strcspn(s, ":") > 0;
2330 static void
2331 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
2332 GtkTreeIter *parent, GtkTreeIter *sibling)
2334 if (GTK_IS_TREE_STORE(model))
2335 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
2336 iter, parent, sibling);
2337 else if (GTK_IS_LIST_STORE(model))
2338 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2339 iter, sibling);
2340 else
2341 ABORT;
2344 static void
2345 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2346 GtkTreeIter *parent, GtkTreeIter *sibling)
2348 if (GTK_IS_TREE_STORE(model))
2349 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2350 iter, parent, sibling);
2351 else if (GTK_IS_LIST_STORE(model))
2352 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2353 iter, sibling);
2354 else
2355 ABORT;
2358 static void
2359 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2360 GtkTreeIter *position)
2362 if (GTK_IS_TREE_STORE(model))
2363 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2364 else if (GTK_IS_LIST_STORE(model))
2365 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2366 else
2367 ABORT;
2370 static void
2371 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2373 if (GTK_IS_TREE_STORE(model))
2374 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2375 else if (GTK_IS_LIST_STORE(model))
2376 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2377 else
2378 ABORT;
2381 static void
2382 tree_model_clear(GtkTreeModel *model)
2384 if (GTK_IS_TREE_STORE(model))
2385 gtk_tree_store_clear(GTK_TREE_STORE(model));
2386 else if (GTK_IS_LIST_STORE(model))
2387 gtk_list_store_clear(GTK_LIST_STORE(model));
2388 else
2389 ABORT;
2392 static void
2393 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2395 va_list ap;
2397 va_start(ap, iter);
2398 if (GTK_IS_TREE_STORE(model))
2399 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2400 else if (GTK_IS_LIST_STORE(model))
2401 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2402 else
2403 ABORT;
2404 va_end(ap);
2408 * Create an empty row at path if it doesn't yet exist. Create older
2409 * siblings and parents as necessary.
2411 static void
2412 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2414 GtkTreeIter iter_1; /* iter's predecessor */
2415 GtkTreePath *path_1; /* path's predecessor */
2417 if (gtk_tree_path_get_depth(path) > 0 &&
2418 gtk_tree_model_get_iter(model, iter, path))
2419 return;
2420 path_1 = gtk_tree_path_copy(path);
2421 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2422 create_subtree(model, path_1, iter);
2423 iter_1 = *iter;
2424 tree_model_insert_after(model, iter, NULL, &iter_1);
2425 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2426 create_subtree(model, path_1, iter);
2427 if (gtk_tree_path_get_depth(path_1) == 0)
2428 /* first toplevel row */
2429 tree_model_insert_after(model, iter, NULL, NULL);
2430 else { /* first row in a lower level */
2431 iter_1 = *iter;
2432 tree_model_insert_after(model, iter, &iter_1, NULL);
2434 } /* neither prev nor up mean we're at the root of an empty tree */
2435 gtk_tree_path_free(path_1);
2438 static bool
2439 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2440 const char *path_s, int col, const char *new_text)
2442 GType col_type = gtk_tree_model_get_column_type(model, col);
2443 GtkTreePath *path;
2444 bool ok = false;
2445 char dummy;
2446 double d;
2447 long long int n;
2449 path = gtk_tree_path_new_from_string(path_s);
2450 switch (col_type) {
2451 case G_TYPE_BOOLEAN:
2452 case G_TYPE_INT:
2453 case G_TYPE_LONG:
2454 case G_TYPE_INT64:
2455 case G_TYPE_UINT:
2456 case G_TYPE_ULONG:
2457 case G_TYPE_UINT64:
2458 if (new_text != NULL &&
2459 sscanf(new_text, "%lld %c", &n, &dummy) == 1) {
2460 create_subtree(model, path, iter);
2461 tree_model_set(model, iter, col, n, -1);
2462 ok = true;
2464 break;
2465 case G_TYPE_FLOAT:
2466 case G_TYPE_DOUBLE:
2467 if (new_text != NULL &&
2468 sscanf(new_text, "%lf %c", &d, &dummy) == 1) {
2469 create_subtree(model, path, iter);
2470 tree_model_set(model, iter, col, d, -1);
2471 ok = true;
2473 break;
2474 case G_TYPE_STRING:
2475 create_subtree(model, path, iter);
2476 tree_model_set(model, iter, col, new_text, -1);
2477 ok = true;
2478 break;
2479 default:
2480 fprintf(stderr, "column %d: %s not implemented\n",
2481 col, g_type_name(col_type));
2482 ok = true;
2483 break;
2485 gtk_tree_path_free(path);
2486 return ok;
2489 static void
2490 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2492 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2493 /* is just fine and this function need not exist. */
2494 if (path == NULL)
2495 path = gtk_tree_path_new();
2496 gtk_tree_view_set_cursor(view, path, col, false);
2499 static void
2500 update_tree_view(struct ui_data *ud)
2502 GtkTreeView *view = GTK_TREE_VIEW(ud->obj);
2503 GtkTreeIter iter0, iter1;
2504 GtkTreeModel *model = gtk_tree_view_get_model(view);
2505 GtkTreePath *path = NULL;
2506 bool iter0_valid, iter1_valid;
2507 char *tokens, *arg0, *arg1, *arg2;
2508 int col = -1; /* invalid column number */
2509 struct info ar;
2511 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2513 fprintf(stderr, "missing model/");
2514 ign_cmd(ud->type, ud->msg);
2515 return;
2517 if ((tokens = malloc(strlen(ud->data) + 1)) == NULL)
2518 OOM_ABORT;
2519 strcpy(tokens, ud->data);
2520 arg0 = strtok(tokens, WHITESPACE);
2521 arg1 = strtok(NULL, WHITESPACE);
2522 arg2 = strtok(NULL, "");
2523 iter0_valid = is_path_string(arg0) &&
2524 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2525 iter1_valid = is_path_string(arg1) &&
2526 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2527 if (is_path_string(arg1))
2528 col = strtol(arg1, NULL, 10);
2529 if (eql(ud->action, "set") &&
2530 col > -1 &&
2531 col < gtk_tree_model_get_n_columns(model) &&
2532 is_path_string(arg0)) {
2533 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2534 ign_cmd(ud->type, ud->msg);
2535 } else if (eql(ud->action, "scroll") && iter0_valid && iter1_valid &&
2536 arg2 == NULL) {
2537 path = gtk_tree_path_new_from_string(arg0);
2538 gtk_tree_view_scroll_to_cell (view,
2539 path,
2540 gtk_tree_view_get_column(view, col),
2541 0, 0., 0.);
2542 } else if (eql(ud->action, "expand") && iter0_valid && arg1 == NULL) {
2543 path = gtk_tree_path_new_from_string(arg0);
2544 gtk_tree_view_expand_row(view, path, false);
2545 } else if (eql(ud->action, "expand_all") && iter0_valid && arg1 == NULL) {
2546 path = gtk_tree_path_new_from_string(arg0);
2547 gtk_tree_view_expand_row(view, path, true);
2548 } else if (eql(ud->action, "expand_all") && arg0 == NULL)
2549 gtk_tree_view_expand_all(view);
2550 else if (eql(ud->action, "collapse") && iter0_valid && arg1 == NULL) {
2551 path = gtk_tree_path_new_from_string(arg0);
2552 gtk_tree_view_collapse_row(view, path);
2553 } else if (eql(ud->action, "collapse") && arg0 == NULL)
2554 gtk_tree_view_collapse_all(view);
2555 else if (eql(ud->action, "set_cursor") && iter0_valid && arg1 == NULL) {
2556 path = gtk_tree_path_new_from_string(arg0);
2557 tree_view_set_cursor(view, path, NULL);
2558 } else if (eql(ud->action, "set_cursor") && arg0 == NULL) {
2559 tree_view_set_cursor(view, NULL, NULL);
2560 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2561 } else if (eql(ud->action, "insert_row") &&
2562 eql(arg0, "end") && arg1 == NULL)
2563 tree_model_insert_before(model, &iter1, NULL, NULL);
2564 else if (eql(ud->action, "insert_row") && iter0_valid &&
2565 eql(arg1, "as_child") && arg2 == NULL)
2566 tree_model_insert_after(model, &iter1, &iter0, NULL);
2567 else if (eql(ud->action, "insert_row") && iter0_valid && arg1 == NULL)
2568 tree_model_insert_before(model, &iter1, NULL, &iter0);
2569 else if (eql(ud->action, "move_row") && iter0_valid &&
2570 eql(arg1, "end") && arg2 == NULL)
2571 tree_model_move_before(model, &iter0, NULL);
2572 else if (eql(ud->action, "move_row") && iter0_valid && iter1_valid && arg2 == NULL)
2573 tree_model_move_before(model, &iter0, &iter1);
2574 else if (eql(ud->action, "remove_row") && iter0_valid && arg1 == NULL)
2575 tree_model_remove(model, &iter0);
2576 else if (eql(ud->action, "clear") && arg0 == NULL) {
2577 tree_view_set_cursor(view, NULL, NULL);
2578 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2579 tree_model_clear(model);
2580 } else if (eql(ud->action, "save") && arg0 != NULL &&
2581 (ar.fout = fopen(arg0, "w")) != NULL) {
2582 ar.obj = ud->obj;
2583 gtk_tree_model_foreach(model,
2584 (GtkTreeModelForeachFunc) save_tree_row_msg,
2585 &ar);
2586 fclose(ar.fout);
2587 } else
2588 ign_cmd(ud->type, ud->msg);
2589 free(tokens);
2590 gtk_tree_path_free(path);
2593 static void
2594 update_visibility(struct ui_data *ud)
2596 char dummy;
2597 unsigned int val;
2599 if (GTK_IS_WIDGET(ud->obj) &&
2600 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2601 gtk_widget_set_visible(GTK_WIDGET(ud->obj), val);
2602 else
2603 ign_cmd(ud->type, ud->msg);
2607 * Change the style of the widget passed. Runs inside gtk_main().
2609 static void
2610 update_widget_style(struct ui_data *ud)
2612 GtkStyleContext *context;
2613 GtkStyleProvider *style_provider;
2614 char *style_decl;
2615 const char *prefix = "* {", *suffix = "}";
2616 size_t sz;
2618 if (!GTK_IS_WIDGET(ud->obj)) {
2619 ign_cmd(ud->type, ud->msg);
2620 return;
2622 style_provider = g_object_get_data(ud->obj, "style_provider");
2623 sz = strlen(prefix) + strlen(suffix) + strlen(ud->data) + 1;
2624 context = gtk_widget_get_style_context(GTK_WIDGET(ud->obj));
2625 gtk_style_context_remove_provider(context, style_provider);
2626 if ((style_decl = malloc(sz)) == NULL)
2627 OOM_ABORT;
2628 strcpy(style_decl, prefix);
2629 strcat(style_decl, ud->data);
2630 strcat(style_decl, suffix);
2631 gtk_style_context_add_provider(context, style_provider,
2632 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2633 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2634 style_decl, -1, NULL);
2635 free(style_decl);
2638 static void
2639 update_window(struct ui_data *ud)
2641 if (!update_class_window(ud))
2642 ign_cmd(ud->type, ud->msg);
2646 * Simulate user activity on various widgets. Runs inside gtk_main().
2648 static void
2649 fake_ui_activity(struct ui_data *ud)
2651 char dummy;
2653 if (!GTK_IS_WIDGET(ud->obj) || sscanf(ud->data, " %c", &dummy) > 0)
2654 ign_cmd(ud->type, ud->msg);
2655 else if (GTK_IS_SPIN_BUTTON(ud->obj)) {
2656 ud->args->txt = "text";
2657 cb_spin_button(GTK_BUILDABLE(ud->obj), ud->args); /* TODO: rename to "value" */
2658 } else if (GTK_IS_SCALE(ud->obj)) {
2659 ud->args->txt = "value";
2660 cb_range(GTK_BUILDABLE(ud->obj), ud->args);
2661 } else if (GTK_IS_ENTRY(ud->obj)) {
2662 ud->args->txt = "text";
2663 cb_editable(GTK_BUILDABLE(ud->obj), ud->args);
2664 } else if (GTK_IS_CALENDAR(ud->obj)) {
2665 ud->args->txt = "clicked";
2666 cb_calendar(GTK_BUILDABLE(ud->obj), ud->args);
2667 } else if (GTK_IS_FILE_CHOOSER_BUTTON(ud->obj)) {
2668 ud->args->txt = "file";
2669 cb_file_chooser_button(GTK_BUILDABLE(ud->obj), ud->args);
2670 } else if (!gtk_widget_activate(GTK_WIDGET(ud->obj)))
2671 ign_cmd(ud->type, ud->msg);
2675 * The final UI update. Runs inside gtk_main().
2677 static void
2678 main_quit(struct ui_data *ud)
2680 char dummy;
2682 if (sscanf(ud->data, " %c", &dummy) < 1)
2683 gtk_main_quit();
2684 else
2685 ign_cmd(ud->type, ud->msg);
2689 * Write snapshot of widget in an appropriate format to file
2691 static void
2692 take_snapshot(struct ui_data *ud)
2694 cairo_surface_t *sur = NULL;
2695 cairo_t *cr = NULL;
2696 int height;
2697 int width;
2699 if (!GTK_IS_WIDGET(ud->obj)) {
2700 ign_cmd(ud->type, ud->msg);
2701 return;
2703 height = gtk_widget_get_allocated_height(GTK_WIDGET(ud->obj));
2704 width = gtk_widget_get_allocated_width(GTK_WIDGET(ud->obj));
2705 if (has_suffix(ud->data, ".epsf") || has_suffix(ud->data, ".eps")) {
2706 sur = cairo_ps_surface_create(ud->data, width, height);
2707 cairo_ps_surface_set_eps(sur, TRUE);
2708 } else if (has_suffix(ud->data, ".pdf"))
2709 sur = cairo_pdf_surface_create(ud->data, width, height);
2710 else if (has_suffix(ud->data, ".ps"))
2711 sur = cairo_ps_surface_create(ud->data, width, height);
2712 else if (has_suffix(ud->data, ".svg"))
2713 sur = cairo_svg_surface_create(ud->data, width, height);
2714 else
2715 ign_cmd(ud->type, ud->msg);
2716 cr = cairo_create(sur);
2717 gtk_widget_draw(GTK_WIDGET(ud->obj), cr);
2718 cairo_destroy(cr);
2719 cairo_surface_destroy(sur);
2723 * Don't update anything; just complain from inside gtk_main()
2725 static void
2726 complain(struct ui_data *ud)
2728 ign_cmd(ud->type, ud->msg);
2732 * Parse command pointed to by ud, and act on ui accordingly. Runs
2733 * once per command inside gtk_main().
2735 static gboolean
2736 update_ui(struct ui_data *ud)
2738 char *lc = lc_numeric();
2740 (ud->fn)(ud);
2741 free(ud->msg_tokens);
2742 free(ud->msg);
2743 free(ud);
2744 lc_numeric_free(lc);
2745 return G_SOURCE_REMOVE;
2749 * Keep track of loading files to avoid recursive loading of the same
2750 * file. If filename = NULL, forget the most recently remembered file.
2752 static bool
2753 remember_loading_file(char *filename)
2755 static char *filenames[BUFLEN];
2756 static size_t latest = 0;
2757 size_t i;
2759 if (filename == NULL) { /* pop */
2760 if (latest < 1)
2761 ABORT;
2762 latest--;
2763 return false;
2764 } else { /* push */
2765 for (i = 1; i <= latest; i++)
2766 if (eql(filename, filenames[i]))
2767 return false;
2768 if (latest > BUFLEN -2)
2769 return false;
2770 filenames[++latest] = filename;
2771 return true;
2776 * Read lines from stream cmd and perform appropriate actions on the
2777 * GUI. Runs inside receiver thread.
2779 static void *
2780 digest_msg(struct info *ar)
2782 static int recursion = -1; /* > 0 means this is a recursive call */
2784 recursion++;
2785 for (;;) {
2786 FILE *cmd = ar->fin;
2787 struct ui_data *ud = NULL;
2788 char first_char = '\0';
2789 char *id; /* widget id */
2790 size_t msg_size = 32;
2791 int id_start = 0, id_end = 0;
2792 int action_start = 0, action_end = 0;
2793 int data_start = 0;
2795 if (feof(cmd))
2796 break;
2797 if ((ud = malloc(sizeof(*ud))) == NULL)
2798 OOM_ABORT;
2799 if ((ud->msg = malloc(msg_size)) == NULL)
2800 OOM_ABORT;
2801 ud->args = ar;
2802 ud->type = G_TYPE_INVALID;
2803 pthread_testcancel();
2804 if (recursion == 0)
2805 log_msg(ar->flog, NULL);
2806 data_start = read_buf(cmd, &ud->msg, &msg_size);
2807 if (recursion == 0)
2808 log_msg(ar->flog, ud->msg);
2809 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2810 OOM_ABORT;
2811 sscanf(ud->msg, " %c", &first_char);
2812 if (data_start == 0 || /* empty line */
2813 first_char == '#') { /* comment */
2814 ud->fn = update_nothing;
2815 goto exec;
2817 strcpy(ud->msg_tokens, ud->msg);
2818 sscanf(ud->msg_tokens,
2819 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2820 &id_start, &id_end, &action_start, &action_end, &data_start);
2821 ud->msg_tokens[id_end] = ud->msg_tokens[action_end] = '\0';
2822 id = ud->msg_tokens + id_start;
2823 ud->action = ud->msg_tokens + action_start;
2824 ud->data = ud->msg_tokens + data_start;
2825 if (eql(ud->action, "main_quit")) {
2826 ud->fn = main_quit;
2827 goto exec;
2829 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2830 remember_loading_file(ud->data)) {
2831 struct info a = *ar;
2833 if ((a.fin = fopen(ud->data, "r")) != NULL) {
2834 digest_msg(&a);
2835 fclose(a.fin);
2836 ud->fn = update_nothing;
2837 } else
2838 ud->fn = complain;
2839 remember_loading_file(NULL);
2840 goto exec;
2842 if ((ud->obj = (gtk_builder_get_object(ar->builder, id))) == NULL) {
2843 ud->fn = complain;
2844 goto exec;
2846 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2847 if (eql(ud->action, "force"))
2848 ud->fn = fake_ui_activity;
2849 else if (eql(ud->action, "snapshot"))
2850 ud->fn = take_snapshot;
2851 else if (eql(ud->action, "block"))
2852 ud->fn = update_blocked;
2853 else if (eql(ud->action, "set_sensitive"))
2854 ud->fn = update_sensitivity;
2855 else if (eql(ud->action, "set_visible"))
2856 ud->fn = update_visibility;
2857 else if (eql(ud->action, "set_size_request"))
2858 ud->fn = update_size_request;
2859 else if (eql(ud->action, "set_tooltip_text"))
2860 ud->fn = update_tooltip_text;
2861 else if (eql(ud->action, "grab_focus"))
2862 ud->fn = update_focus;
2863 else if (eql(ud->action, "style")) {
2864 ud->action = id;
2865 ud->fn = update_widget_style;
2866 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2867 ud->fn = update_drawing_area;
2868 else if (ud->type == GTK_TYPE_TREE_VIEW)
2869 ud->fn = update_tree_view;
2870 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2871 ud->fn = update_combo_box_text;
2872 else if (ud->type == GTK_TYPE_LABEL)
2873 ud->fn = update_label;
2874 else if (ud->type == GTK_TYPE_IMAGE)
2875 ud->fn = update_image;
2876 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2877 ud->fn = update_text_view;
2878 else if (ud->type == GTK_TYPE_NOTEBOOK)
2879 ud->fn = update_notebook;
2880 else if (ud->type == GTK_TYPE_EXPANDER)
2881 ud->fn = update_expander;
2882 else if (ud->type == GTK_TYPE_FRAME)
2883 ud->fn = update_frame;
2884 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2885 ud->fn = update_scrolled_window;
2886 else if (ud->type == GTK_TYPE_BUTTON)
2887 ud->fn = update_button;
2888 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2889 ud->fn = update_file_chooser_dialog;
2890 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2891 ud->fn = update_file_chooser_button;
2892 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2893 ud->fn = update_color_button;
2894 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2895 ud->fn = update_font_button;
2896 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2897 ud->fn = update_print_dialog;
2898 else if (ud->type == GTK_TYPE_SWITCH)
2899 ud->fn = update_switch;
2900 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2901 ud->type == GTK_TYPE_RADIO_BUTTON ||
2902 ud->type == GTK_TYPE_CHECK_BUTTON)
2903 ud->fn = update_toggle_button;
2904 else if (ud->type == GTK_TYPE_ENTRY)
2905 ud->fn = update_entry;
2906 else if (ud->type == GTK_TYPE_SPIN_BUTTON)
2907 ud->fn = update_spin_button;
2908 else if (ud->type == GTK_TYPE_SCALE)
2909 ud->fn = update_scale;
2910 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2911 ud->fn = update_progress_bar;
2912 else if (ud->type == GTK_TYPE_SPINNER)
2913 ud->fn = update_spinner;
2914 else if (ud->type == GTK_TYPE_STATUSBAR)
2915 ud->fn = update_statusbar;
2916 else if (ud->type == GTK_TYPE_CALENDAR)
2917 ud->fn = update_calendar;
2918 else if (ud->type == GTK_TYPE_SOCKET)
2919 ud->fn = update_socket;
2920 else if (ud->type == GTK_TYPE_WINDOW ||
2921 ud->type == GTK_TYPE_DIALOG)
2922 ud->fn = update_window;
2923 else
2924 ud->fn = complain;
2925 exec:
2926 pthread_testcancel();
2927 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2929 recursion--;
2930 return NULL;
2935 * ============================================================
2936 * Initialization
2937 * ============================================================
2941 * Return the first string xpath obtains from ui_file.
2942 * xmlFree(string) must be called when done
2944 static xmlChar *
2945 xpath1(xmlChar *xpath, const char *ui_file)
2947 xmlChar *r = NULL;
2948 xmlDocPtr doc = NULL;
2949 xmlNodeSetPtr nodes = NULL;
2950 xmlXPathContextPtr ctx = NULL;
2951 xmlXPathObjectPtr xpath_obj = NULL;
2953 if ((doc = xmlParseFile(ui_file)) == NULL)
2954 goto ret0;
2955 if ((ctx = xmlXPathNewContext(doc)) == NULL)
2956 goto ret1;
2957 if ((xpath_obj = xmlXPathEvalExpression(xpath, ctx)) == NULL)
2958 goto ret2;
2959 if ((nodes = xpath_obj->nodesetval) != NULL && nodes->nodeNr > 0)
2960 r = xmlNodeGetContent(nodes->nodeTab[0]);
2961 xmlXPathFreeObject(xpath_obj);
2962 ret2:
2963 xmlXPathFreeContext(ctx);
2964 ret1:
2965 xmlFreeDoc(doc);
2966 ret0:
2967 return r;
2971 * Attach key "col_number" to renderer. Associate "col_number" with
2972 * the corresponding column number in the underlying model.
2973 * Due to what looks like a gap in the GTK API, renderer id and column
2974 * number are taken directly from the XML .ui file.
2976 static bool
2977 tree_view_column_get_renderer_column(GtkBuilder *builder, const char *ui_file,
2978 GtkTreeViewColumn *t_col, int n,
2979 GtkCellRenderer **rnd)
2981 bool r = false;
2982 char *xp_bas1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2983 char *xp_bas2 = "\"]/child[";
2984 char *xp_bas3 = "]/object[@class=\"GtkCellRendererText\""
2985 " or @class=\"GtkCellRendererToggle\"]/";
2986 char *xp_rnd_id = "@id";
2987 char *xp_text_col = "../attributes/attribute[@name=\"text\""
2988 " or @name=\"active\"]/text()";
2989 const char *tree_col_id = widget_id(GTK_BUILDABLE(t_col));
2990 size_t xp_rnd_nam_len, xp_mod_col_len;
2991 size_t xp_n_len = 3; /* Big Enough (TM) */
2992 xmlChar *xp_rnd_nam = NULL, *xp_mod_col = NULL;
2993 xmlChar *rnd_nam = NULL, *mod_col = NULL;
2995 /* find name of nth cell renderer under the GtkTreeViewColumn */
2996 /* tree_col_id */
2997 xp_rnd_nam_len = strlen(xp_bas1) + strlen(tree_col_id) +
2998 strlen(xp_bas2) + xp_n_len + strlen(xp_bas3) +
2999 strlen(xp_rnd_id) + sizeof('\0');
3000 if ((xp_rnd_nam = malloc(xp_rnd_nam_len)) == NULL)
3001 OOM_ABORT;
3002 snprintf((char *) xp_rnd_nam, xp_rnd_nam_len, "%s%s%s%d%s%s",
3003 xp_bas1, tree_col_id, xp_bas2, n,
3004 xp_bas3, xp_rnd_id);
3005 rnd_nam = xpath1(xp_rnd_nam, ui_file);
3006 /* find the model column that is attached to the nth cell */
3007 /* renderer under GtkTreeViewColumn tree_col_id */
3008 xp_mod_col_len = strlen(xp_bas1) + strlen(tree_col_id) +
3009 strlen(xp_bas2) + xp_n_len + strlen(xp_bas3) +
3010 strlen(xp_text_col) + sizeof('\0');
3011 if ((xp_mod_col = malloc(xp_mod_col_len)) == NULL)
3012 OOM_ABORT;
3013 snprintf((char *) xp_mod_col, xp_mod_col_len, "%s%s%s%d%s%s",
3014 xp_bas1, tree_col_id, xp_bas2, n, xp_bas3, xp_text_col);
3015 mod_col = xpath1(xp_mod_col, ui_file);
3016 if (rnd_nam) {
3017 *rnd = GTK_CELL_RENDERER(
3018 gtk_builder_get_object(builder, (char *) rnd_nam));
3019 if (mod_col) {
3020 g_object_set_data(G_OBJECT(*rnd), "col_number",
3021 GINT_TO_POINTER(strtol((char *) mod_col,
3022 NULL, 10)));
3023 r = true;
3026 free(xp_rnd_nam);
3027 free(xp_mod_col);
3028 xmlFree(rnd_nam);
3029 xmlFree(mod_col);
3030 return r;
3034 * Callbacks that forward a modification of a tree view cell to the
3035 * underlying model
3037 static void
3038 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
3039 const gchar *new_text, struct info *ar)
3041 GtkTreeIter iter;
3042 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3043 "col_number"));
3045 gtk_tree_model_get_iter_from_string(ar->model, &iter, path_s);
3046 set_tree_view_cell(ar->model, &iter, path_s, col,
3047 new_text);
3048 send_tree_cell_msg_by(send_msg, path_s, &iter, col, ar);
3051 static void
3052 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, struct info *ar)
3054 GtkTreeIter iter;
3055 bool toggle_state;
3056 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3057 "col_number"));
3059 gtk_tree_model_get_iter_from_string(ar->model, &iter, path_s);
3060 gtk_tree_model_get(ar->model, &iter, col, &toggle_state, -1);
3061 set_tree_view_cell(ar->model, &iter, path_s, col,
3062 toggle_state? "0" : "1");
3066 * Add new element containing id to the list of callback-handler ids
3067 * stored in obj's field named "signal_id"
3069 static void
3070 push_handler_id(gpointer *obj, unsigned int id)
3072 struct handler_id *prev_hid, *hid;
3074 prev_hid = g_object_get_data(G_OBJECT(obj), "signal-id");
3075 if ((hid = malloc(sizeof(struct handler_id))) == NULL)
3076 OOM_ABORT;
3077 hid->next = prev_hid;
3078 hid->id = id;
3079 hid->blocked = false;
3080 g_object_set_data(G_OBJECT(obj), "signal-id", hid);
3084 * Connect function cb to obj's widget signal sig, remembering the
3085 * handler id in a list in obj's field named "signal-id"
3087 static void
3088 sig_conn(gpointer *obj, char *sig, GCallback cb, struct info *ar)
3090 unsigned int handler_id = g_signal_connect(obj, sig, cb, ar);
3092 push_handler_id(obj, handler_id);
3095 static void
3096 sig_conn_swapped(gpointer *obj, char *sig, GCallback cb, void *data)
3098 unsigned int handler_id = g_signal_connect_swapped(obj, sig, cb, data);
3100 push_handler_id(obj, handler_id);
3103 static void
3104 connect_widget_signals(gpointer *obj, struct info *ar)
3106 GObject *obj2;
3107 GType type = G_TYPE_INVALID;
3108 char *suffix = NULL;
3109 const char *w_id = NULL;
3110 FILE *o = ar->fout;
3112 type = G_TYPE_FROM_INSTANCE(obj);
3113 if (GTK_IS_BUILDABLE(obj))
3114 w_id = widget_id(GTK_BUILDABLE(obj));
3115 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
3116 GList *cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(obj));
3117 GtkTreeViewColumn *tv_col = GTK_TREE_VIEW_COLUMN(obj);
3118 GObject *view = G_OBJECT(
3119 gtk_tree_view_column_get_tree_view(tv_col));
3120 unsigned int i, n_cells = g_list_length(cells);
3122 g_list_free(cells);
3123 sig_conn(obj, "clicked", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3124 for (i = 1; i <= n_cells; i++) {
3125 GtkCellRenderer *renderer;
3126 gboolean editable = FALSE;
3128 if (!tree_view_column_get_renderer_column(ar->builder, ar->txt, tv_col,
3129 i, &renderer))
3130 continue;
3131 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
3132 g_object_get(renderer, "editable", &editable, NULL);
3133 if (editable) {
3134 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
3136 g_signal_connect(renderer, "edited",
3137 G_CALLBACK(cb_tree_model_edit),
3138 info_obj_new(o, view, model));
3140 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
3141 g_object_get(renderer, "activatable", &editable, NULL);
3142 if (editable) {
3143 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
3145 g_signal_connect(renderer, "toggled",
3146 G_CALLBACK(cb_tree_model_toggle),
3147 info_obj_new(o, NULL, model));
3151 } else if (type == GTK_TYPE_BUTTON)
3152 /* Button associated with a GtkTextView. */
3153 if ((suffix = strstr(w_id, "_send_text")) != NULL &&
3154 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3155 sig_conn(obj, "clicked", G_CALLBACK(cb_send_text),
3156 info_obj_new(o, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2))), NULL));
3157 else if ((suffix = strstr(w_id, "_send_selection")) != NULL &&
3158 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3159 sig_conn(obj, "clicked", G_CALLBACK(cb_send_text_selection),
3160 info_obj_new(o, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2))), NULL));
3161 else {
3162 sig_conn(obj, "clicked", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3163 /* Buttons associated with (and part of) a GtkDialog.
3164 * (We shun response ids which could be returned from
3165 * gtk_dialog_run() because that would require the
3166 * user to define those response ids in Glade,
3167 * numerically */
3168 if ((suffix = strstr(w_id, "_cancel")) != NULL &&
3169 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3170 if (eql(widget_id(GTK_BUILDABLE(obj2)), MAIN_WIN))
3171 sig_conn_swapped(obj, "clicked",
3172 G_CALLBACK(gtk_main_quit), NULL);
3173 else
3174 sig_conn_swapped(obj, "clicked",
3175 G_CALLBACK(gtk_widget_hide), obj2);
3176 else if ((suffix = strstr(w_id, "_ok")) != NULL &&
3177 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id))) {
3178 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
3179 sig_conn_swapped(obj, "clicked",
3180 G_CALLBACK(cb_send_file_chooser_dialog_selection),
3181 info_obj_new(o, obj2, NULL));
3182 if (eql(widget_id(GTK_BUILDABLE(obj2)), MAIN_WIN))
3183 sig_conn_swapped(obj, "clicked",
3184 G_CALLBACK(gtk_main_quit), NULL);
3185 else
3186 sig_conn_swapped(obj, "clicked",
3187 G_CALLBACK(gtk_widget_hide), obj2);
3188 } else if ((suffix = strstr(w_id, "_apply")) != NULL &&
3189 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3190 sig_conn_swapped(obj, "clicked",
3191 G_CALLBACK(cb_send_file_chooser_dialog_selection),
3192 info_obj_new(o, obj2, NULL));
3194 else if (GTK_IS_MENU_ITEM(obj))
3195 if ((suffix = strstr(w_id, "_invoke")) != NULL &&
3196 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3197 sig_conn_swapped(obj, "activate",
3198 G_CALLBACK(gtk_widget_show), obj2);
3199 else
3200 sig_conn(obj, "activate",
3201 G_CALLBACK(cb_menu_item), info_txt_new(o, "active"));
3202 else if (GTK_IS_WINDOW(obj)) {
3203 sig_conn(obj, "delete-event",
3204 G_CALLBACK(cb_event_simple), info_txt_new(o, "closed"));
3205 if (eql(w_id, MAIN_WIN))
3206 sig_conn_swapped(obj, "delete-event",
3207 G_CALLBACK(gtk_main_quit), NULL);
3208 else
3209 sig_conn(obj, "delete-event",
3210 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3211 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
3212 sig_conn(obj, "file-set",
3213 G_CALLBACK(cb_file_chooser_button), info_txt_new(o, "file"));
3214 else if (type == GTK_TYPE_COLOR_BUTTON)
3215 sig_conn(obj, "color-set",
3216 G_CALLBACK(cb_color_button), info_txt_new(o, "color"));
3217 else if (type == GTK_TYPE_FONT_BUTTON)
3218 sig_conn(obj, "font-set",
3219 G_CALLBACK(cb_font_button), info_txt_new(o, "font"));
3220 else if (type == GTK_TYPE_SWITCH)
3221 sig_conn(obj, "notify::active",
3222 G_CALLBACK(cb_switch), info_txt_new(o, NULL));
3223 else if (type == GTK_TYPE_TOGGLE_BUTTON ||
3224 type == GTK_TYPE_RADIO_BUTTON ||
3225 type == GTK_TYPE_CHECK_BUTTON)
3226 sig_conn(obj, "toggled",
3227 G_CALLBACK(cb_toggle_button), info_txt_new(o, NULL));
3228 else if (type == GTK_TYPE_ENTRY)
3229 sig_conn(obj, "changed",
3230 G_CALLBACK(cb_editable), info_txt_new(o, "text"));
3231 else if (type == GTK_TYPE_SPIN_BUTTON)
3232 sig_conn(obj, "value_changed",
3233 G_CALLBACK(cb_spin_button), info_txt_new(o, "text")); /* TODO: rename to "value" */
3234 else if (type == GTK_TYPE_SCALE)
3235 sig_conn(obj, "value-changed",
3236 G_CALLBACK(cb_range), info_txt_new(o, "value"));
3237 else if (type == GTK_TYPE_CALENDAR) {
3238 sig_conn(obj, "day-selected-double-click",
3239 G_CALLBACK(cb_calendar), info_txt_new(o, "doubleclicked"));
3240 sig_conn(obj, "day-selected",
3241 G_CALLBACK(cb_calendar), info_txt_new(o, "clicked"));
3242 } else if (type == GTK_TYPE_TREE_SELECTION)
3243 sig_conn(obj, "changed",
3244 G_CALLBACK(cb_tree_selection), info_txt_new(o, "clicked"));
3245 else if (type == GTK_TYPE_SOCKET) {
3246 sig_conn(obj, "plug-added",
3247 G_CALLBACK(cb_simple), info_txt_new(o, "plug-added"));
3248 sig_conn(obj, "plug-removed",
3249 G_CALLBACK(cb_simple), info_txt_new(o, "plug-removed"));
3250 /* TODO: rename to plug_added, plug_removed */
3251 } else if (type == GTK_TYPE_DRAWING_AREA)
3252 sig_conn(obj, "draw", G_CALLBACK(cb_draw), NULL);
3253 else if (type == GTK_TYPE_EVENT_BOX) {
3254 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
3255 sig_conn(obj, "button-press-event",
3256 G_CALLBACK(cb_event_box_button),
3257 info_txt_new(o, "button_press"));
3258 sig_conn(obj, "button-release-event",
3259 G_CALLBACK(cb_event_box_button),
3260 info_txt_new(o, "button_release"));
3261 sig_conn(obj, "motion-notify-event",
3262 G_CALLBACK(cb_event_box_motion),
3263 info_txt_new(o, "motion"));
3264 sig_conn(obj, "key-press-event",
3265 G_CALLBACK(cb_event_box_key),
3266 info_txt_new(o, "key_press"));
3271 * We keep a style provider with each widget
3273 static void
3274 add_widget_style_provider(gpointer *obj, void *data)
3276 GtkCssProvider *style_provider;
3277 GtkStyleContext *context;
3279 (void) data;
3280 if (!GTK_IS_WIDGET(obj))
3281 return;
3282 style_provider = gtk_css_provider_new();
3283 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
3284 gtk_style_context_add_provider(context,
3285 GTK_STYLE_PROVIDER(style_provider),
3286 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3287 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
3290 static void
3291 prepare_widgets(GtkBuilder *builder, char *ui_file, FILE *out)
3293 GSList *objects = NULL;
3294 struct info ar = {.builder = builder, .fout = out, .txt = ui_file};
3296 objects = gtk_builder_get_objects(builder);
3297 g_slist_foreach(objects, (GFunc) connect_widget_signals, &ar);
3298 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
3299 g_slist_free(objects);
3303 main(int argc, char *argv[])
3305 GObject *main_window = NULL;
3306 bool bg = false;
3307 char *in_fifo = NULL, *out_fifo = NULL;
3308 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
3309 char *xid = NULL;
3310 char opt;
3311 pthread_t receiver;
3312 struct info ar;
3314 /* Disable runtime GLIB deprecation warnings: */
3315 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3316 gtk_init(&argc, &argv);
3317 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
3318 switch (opt) {
3319 case 'b': bg = true; break;
3320 case 'e': xid = optarg; break;
3321 case 'G': show_lib_versions(); break;
3322 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
3323 case 'i': in_fifo = optarg; break;
3324 case 'l': log_file = optarg; break;
3325 case 'o': out_fifo = optarg; break;
3326 case 'O': err_file = optarg; break;
3327 case 'u': ui_file = optarg; break;
3328 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
3329 case '?':
3330 default: bye(EXIT_FAILURE, stderr, USAGE); break;
3333 if (argv[optind] != NULL)
3334 bye(EXIT_FAILURE, stderr,
3335 "illegal parameter '%s'\n" USAGE, argv[optind]);
3336 redirect_stderr(err_file);
3337 ar.fin = open_fifo(in_fifo, "r", stdin, _IONBF);
3338 ar.fout = open_fifo(out_fifo, "w", stdout, _IOLBF);
3339 go_bg_if(bg, ar.fin, ar.fout, err_file);
3340 ar.builder = builder_from_file(ui_file);
3341 ar.flog = open_log(log_file);
3342 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, &ar);
3343 main_window = find_main_window(ar.builder);
3344 xmlInitParser();
3345 LIBXML_TEST_VERSION;
3346 prepare_widgets(ar.builder, ui_file, ar.fout);
3347 xembed_if(xid, main_window);
3348 gtk_main();
3349 pthread_cancel(receiver);
3350 pthread_join(receiver, NULL);
3351 xmlCleanupParser();
3352 rm_unless(stdin, ar.fin, in_fifo);
3353 rm_unless(stdout, ar.fout, out_fifo);
3354 exit(EXIT_SUCCESS);