1 /*
2 * conterm.c
3 *
4 * Copyright 2012 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gdk/gdkkeysyms.h>
28
29 #include "common.h"
30
31 #define NFD 5
32 #define DS_COPY (DS_BASICS | DS_EXTRA_1)
33
34 #ifdef G_OS_UNIX
35 #include <vte/vte.h>
36 #include <gp_vtecompat.h>
37 /* instead of detecting N kinds of *nix */
38 #if defined(HAVE_UTIL_H)
39 #include <util.h>
40 #elif defined(HAVE_LIBUTIL_H)
41 #include <libutil.h>
42 #elif defined(HAVE_PTY_H)
43 #include <pty.h>
44 #endif
45 int grantpt(int fd);
46 int unlockpt(int fd);
47
48 static GtkWidget *program_window;
49 static VteTerminal *program_terminal;
50 static GtkWidget *terminal_parent;
51 static GtkWidget *terminal_window;
52 static GtkCheckMenuItem *terminal_show;
53
on_terminal_show(G_GNUC_UNUSED const MenuItem * menu_item)54 void on_terminal_show(G_GNUC_UNUSED const MenuItem *menu_item)
55 {
56 GtkWidget *terminal = GTK_WIDGET(program_terminal);
57
58 if (gtk_check_menu_item_get_active(terminal_show))
59 {
60 gtk_container_remove(GTK_CONTAINER(program_window), terminal);
61 gtk_widget_set_size_request(terminal, pref_terminal_width, pref_terminal_height);
62 gtk_container_add(GTK_CONTAINER(terminal_window), terminal);
63 gtk_widget_show(terminal_parent);
64 gtk_window_move(GTK_WINDOW(terminal_parent), pref_terminal_window_x,
65 pref_terminal_window_y);
66 }
67 else
68 {
69 gtk_window_get_position(GTK_WINDOW(terminal_parent), &pref_terminal_window_x,
70 &pref_terminal_window_y);
71 gtk_widget_get_size_request(terminal, &pref_terminal_width, &pref_terminal_height);
72 gtk_widget_hide(terminal_parent);
73 gtk_container_remove(GTK_CONTAINER(terminal_window), terminal);
74 gtk_widget_set_size_request(terminal, -1, -1);
75 gtk_container_add(GTK_CONTAINER(program_window), terminal);
76 }
77 }
78
terminal_clear(void)79 void terminal_clear(void)
80 {
81 vte_terminal_reset(program_terminal, TRUE, TRUE);
82 }
83
terminal_standalone(gboolean alone)84 void terminal_standalone(gboolean alone)
85 {
86 gtk_check_menu_item_set_active(terminal_show, alone);
87 }
88
on_terminal_parent_delete(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED GdkEvent * event,G_GNUC_UNUSED gpointer gdata)89 static gboolean on_terminal_parent_delete(G_GNUC_UNUSED GtkWidget *widget,
90 G_GNUC_UNUSED GdkEvent *event, G_GNUC_UNUSED gpointer gdata)
91 {
92 terminal_standalone(FALSE);
93 return TRUE;
94 }
95
on_terminal_copy(G_GNUC_UNUSED const MenuItem * menu_item)96 static void on_terminal_copy(G_GNUC_UNUSED const MenuItem *menu_item)
97 {
98 vte_terminal_copy_clipboard(program_terminal);
99 }
100
on_terminal_paste(G_GNUC_UNUSED const MenuItem * menu_item)101 static void on_terminal_paste(G_GNUC_UNUSED const MenuItem *menu_item)
102 {
103 vte_terminal_paste_clipboard(program_terminal);
104 }
105
on_terminal_feed(G_GNUC_UNUSED const MenuItem * menu_item)106 static void on_terminal_feed(G_GNUC_UNUSED const MenuItem *menu_item)
107 {
108 gdouble value = 4;
109
110 if (dialogs_show_input_numeric(_("Feed Terminal"), _("Enter char # (0..255):"), &value,
111 0, 255, 1))
112 {
113 char text = (char) value;
114 vte_terminal_feed_child(program_terminal, &text, 1);
115 }
116 }
117
on_terminal_select_all(G_GNUC_UNUSED const MenuItem * menu_item)118 static void on_terminal_select_all(G_GNUC_UNUSED const MenuItem *menu_item)
119 {
120 vte_terminal_select_all(program_terminal);
121 }
122
on_terminal_clear(G_GNUC_UNUSED const MenuItem * menu_item)123 static void on_terminal_clear(G_GNUC_UNUSED const MenuItem *menu_item)
124 {
125 vte_terminal_reset(program_terminal, TRUE, TRUE);
126 }
127
128 /* show terminal on program startup */
129 gboolean terminal_auto_show;
130 /* hide terminal on program exit */
131 gboolean terminal_auto_hide;
132 /* show terminal on program exit with non-zero exit code */
133 gboolean terminal_show_on_error;
134
135 static MenuItem terminal_menu_items[] =
136 {
137 { "terminal_copy", on_terminal_copy, DS_COPY, NULL, NULL },
138 { "terminal_paste", on_terminal_paste, 0, NULL, NULL },
139 { "terminal_feed", on_terminal_feed, 0, NULL, NULL },
140 { "terminal_select_all", on_terminal_select_all, 0, NULL, NULL },
141 { "terminal_clear", on_terminal_clear, 0, NULL, NULL },
142 { "terminal_show_hide", on_menu_display_booleans, 0, NULL, GINT_TO_POINTER(3) },
143 { "terminal_auto_show", on_menu_update_boolean, 0, NULL, &terminal_auto_show },
144 { "terminal_auto_hide", on_menu_update_boolean, 0, NULL, &terminal_auto_hide },
145 { "terminal_show_on_error", on_menu_update_boolean, 0, NULL, &terminal_show_on_error },
146 { NULL, NULL, 0, NULL, NULL }
147 };
148
terminal_menu_extra_state(void)149 static guint terminal_menu_extra_state(void)
150 {
151 return vte_terminal_get_has_selection(program_terminal) << DS_INDEX_1;
152 }
153
154 static MenuInfo terminal_menu_info = { terminal_menu_items, terminal_menu_extra_state, 0 };
155
on_vte_realize(VteTerminal * vte,G_GNUC_UNUSED gpointer gdata)156 void on_vte_realize(VteTerminal *vte, G_GNUC_UNUSED gpointer gdata)
157 {
158 vte_terminal_set_emulation(vte, pref_vte_emulation);
159 vte_terminal_set_font_from_string(vte, pref_vte_font);
160 vte_terminal_set_scrollback_lines(vte, pref_vte_scrollback);
161 vte_terminal_set_scroll_on_output(vte, TRUE);
162 vte_terminal_set_color_foreground(vte, &pref_vte_colour_fore);
163 vte_terminal_set_color_background(vte, &pref_vte_colour_back);
164 #if VTE_CHECK_VERSION(0, 17, 1)
165 vte_terminal_set_cursor_blink_mode(vte,
166 pref_vte_blinken ? VTE_CURSOR_BLINK_ON : VTE_CURSOR_BLINK_OFF);
167 #else
168 vte_terminal_set_cursor_blinks(vte, pref_vte_blinken);
169 #endif
170 }
171
172 static VteTerminal *debug_console = NULL; /* NULL -> GtkTextView "context" */
173
console_output(int fd,const char * text,gint length)174 static void console_output(int fd, const char *text, gint length)
175 {
176 static const char fd_colors[NFD] = { '6', '7', '1', '7', '5' };
177 static char setaf[5] = { '\033', '[', '3', '?', 'm' };
178 static int last_fd = -1;
179 gint i;
180
181 if (last_fd == 3 && fd != 0)
182 vte_terminal_feed(debug_console, "\r\n", 2);
183
184 if (fd != last_fd)
185 {
186 setaf[3] = fd_colors[fd];
187 vte_terminal_feed(debug_console, setaf, sizeof(setaf));
188 last_fd = fd;
189 }
190
191 if (length == -1)
192 length = strlen(text);
193
194 for (i = 0; i < length; i++)
195 {
196 if (text[i] == '\n')
197 {
198 vte_terminal_feed(debug_console, text, i);
199 vte_terminal_feed(debug_console, "\r", 2);
200 length -= i;
201 text += i;
202 i = 0;
203 }
204 }
205
206 vte_terminal_feed(debug_console, text, length);
207 }
208
console_output_nl(int fd,const char * text,gint length)209 static void console_output_nl(int fd, const char *text, gint length)
210 {
211 dc_output(fd, text, length);
212 vte_terminal_feed(debug_console, "\r\n", 2);
213 }
214 #endif /* G_OS_UNIX */
215
216 static GtkTextView *debug_context;
217 static GtkTextBuffer *context;
218 static GtkTextTag *fd_tags[NFD];
219 #define DC_LIMIT 32768 /* approx */
220 #define DC_DELTA 6144
221 static guint dc_chars = 0;
222
context_output(int fd,const char * text,gint length)223 void context_output(int fd, const char *text, gint length)
224 {
225 static int last_fd = -1;
226 GtkTextIter end;
227 gchar *utf8;
228
229 gtk_text_buffer_get_end_iter(context, &end);
230
231 if (last_fd == 3 && fd != 0)
232 gtk_text_buffer_insert(context, &end, "\n", 1);
233
234 if (fd != last_fd)
235 last_fd = fd;
236
237 if (length == -1)
238 length = strlen(text);
239
240 dc_chars += length;
241 utf8 = g_locale_to_utf8(text, length, NULL, NULL, NULL);
242
243 if (utf8)
244 {
245 gtk_text_buffer_insert_with_tags(context, &end, utf8, -1, fd_tags[fd], NULL);
246 g_free(utf8);
247 }
248 else
249 gtk_text_buffer_insert_with_tags(context, &end, text, length, fd_tags[fd], NULL);
250
251 if (dc_chars > DC_LIMIT + (DC_DELTA / 2))
252 {
253 GtkTextIter start, delta;
254
255 gtk_text_buffer_get_start_iter(context, &start);
256 gtk_text_buffer_get_iter_at_offset(context, &delta, DC_DELTA);
257 gtk_text_buffer_delete(context, &start, &delta);
258 gtk_text_buffer_get_end_iter(context, &end);
259 dc_chars = gtk_text_buffer_get_char_count(context);
260 }
261
262 gtk_text_buffer_place_cursor(context, &end);
263 gtk_text_view_scroll_mark_onscreen(debug_context, gtk_text_buffer_get_insert(context));
264 }
265
context_output_nl(int fd,const char * text,gint length)266 void context_output_nl(int fd, const char *text, gint length)
267 {
268 dc_output(fd, text, length);
269 dc_output(fd, "\n", 1);
270 }
271
on_console_button_3_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,GtkMenu * menu)272 static gboolean on_console_button_3_press(G_GNUC_UNUSED GtkWidget *widget,
273 GdkEventButton *event, GtkMenu *menu)
274 {
275 if (event->button == 3)
276 {
277 gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);
278 return TRUE;
279 }
280
281 return FALSE;
282 }
283
284 void (*dc_output)(int fd, const char *text, gint length);
285 void (*dc_output_nl)(int fd, const char *text, gint length);
286
dc_error(const char * format,...)287 void dc_error(const char *format, ...)
288 {
289 char *string;
290 va_list args;
291
292 va_start(args, format);
293 string = g_strdup_vprintf(format, args);
294 va_end(args);
295
296 dc_output_nl(4, string, -1);
297 g_free(string);
298 plugin_blink();
299 }
300
dc_clear(void)301 void dc_clear(void)
302 {
303 #ifdef G_OS_UNIX
304 if (debug_console)
305 vte_terminal_reset(debug_console, TRUE, TRUE);
306 else
307 #endif
308 {
309 gtk_text_buffer_set_text(context, "", -1);
310 dc_chars = 0;
311 }
312 }
313
dc_update(void)314 gboolean dc_update(void)
315 {
316 if (thread_state == THREAD_AT_ASSEMBLER)
317 debug_send_format(T, "04-data-disassemble -s $pc -e $pc+1 0");
318
319 return TRUE;
320 }
321
on_console_copy(G_GNUC_UNUSED const MenuItem * menu_item)322 static void on_console_copy(G_GNUC_UNUSED const MenuItem *menu_item)
323 {
324 #ifdef G_OS_UNIX
325 if (debug_console)
326 vte_terminal_copy_clipboard(debug_console);
327 else
328 #endif
329 {
330 g_signal_emit_by_name(debug_context, "copy-clipboard");
331 }
332 }
333
on_console_select_all(G_GNUC_UNUSED const MenuItem * menu_item)334 static void on_console_select_all(G_GNUC_UNUSED const MenuItem *menu_item)
335 {
336 #ifdef G_OS_UNIX
337 if (debug_console)
338 vte_terminal_select_all(program_terminal);
339 else
340 #endif
341 {
342 g_signal_emit_by_name(debug_context, "select-all");
343 }
344 }
345
on_console_clear(G_GNUC_UNUSED const MenuItem * menu_item)346 static void on_console_clear(G_GNUC_UNUSED const MenuItem *menu_item)
347 {
348 dc_clear();
349 }
350
on_console_key_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventKey * event,G_GNUC_UNUSED gpointer gdata)351 static gboolean on_console_key_press(G_GNUC_UNUSED GtkWidget *widget,
352 GdkEventKey *event, G_GNUC_UNUSED gpointer gdata)
353 {
354 gboolean insert = event->keyval == GDK_Insert || event->keyval == GDK_KP_Insert;
355
356 if ((insert || (event->keyval >= 0x21 && event->keyval <= 0x7F &&
357 event->state <= GDK_SHIFT_MASK)) && (debug_state() & DS_ACTIVE))
358 {
359 char command[2] = { event->keyval, '\0' };
360 view_command_line(insert ? NULL : command, NULL, NULL, TRUE);
361 return TRUE;
362 }
363
364 return FALSE;
365 }
366
367 static MenuItem console_menu_items[] =
368 {
369 { "console_copy", on_console_copy, DS_COPY, NULL, NULL },
370 { "console_select_all", on_console_select_all, 0, NULL, NULL },
371 { "console_clear", on_console_clear, 0, NULL, NULL },
372 { NULL, NULL, 0, NULL, NULL }
373 };
374
console_menu_extra_state(void)375 static guint console_menu_extra_state(void)
376 {
377 #ifdef G_OS_UNIX
378 if (debug_console)
379 return vte_terminal_get_has_selection(debug_console) << DS_INDEX_1;
380 #endif
381 return gtk_text_buffer_get_has_selection(context) << DS_INDEX_1;
382 }
383
384 static MenuInfo console_menu_info = { console_menu_items, console_menu_extra_state, 0 };
385
conterm_load_config(void)386 void conterm_load_config(void)
387 {
388 gchar *configfile = g_build_filename(geany_data->app->configdir, "geany.conf", NULL);
389 GKeyFile *config = g_key_file_new();
390 gchar *tmp_string;
391
392 g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, NULL);
393 pref_vte_blinken = utils_get_setting_boolean(config, "VTE", "cursor_blinks", FALSE);
394 pref_vte_emulation = utils_get_setting_string(config, "VTE", "emulation", "xterm");
395 pref_vte_font = utils_get_setting_string(config, "VTE", "font", "Monospace 10");
396 pref_vte_scrollback = utils_get_setting_integer(config, "VTE", "scrollback_lines", 500);
397 tmp_string = utils_get_setting_string(config, "VTE", "colour_fore", "#ffffff");
398 #if !GTK_CHECK_VERSION(3, 14, 0)
399 gdk_color_parse(tmp_string, &pref_vte_colour_fore);
400 #else
401 gdk_rgba_parse(&pref_vte_colour_fore, tmp_string);
402 #endif
403 g_free(tmp_string);
404 tmp_string = utils_get_setting_string(config, "VTE", "colour_back", "#000000");
405 #if !GTK_CHECK_VERSION(3, 14, 0)
406 gdk_color_parse(tmp_string, &pref_vte_colour_back);
407 #else
408 gdk_rgba_parse(&pref_vte_colour_back, tmp_string);
409 #endif
410 g_free(tmp_string);
411 g_key_file_free(config);
412 g_free(configfile);
413 }
414
context_apply_config(GtkWidget * console)415 static void context_apply_config(GtkWidget *console)
416 {
417 #if !GTK_CHECK_VERSION(3, 0, 0)
418 gtk_widget_modify_base(console, GTK_STATE_NORMAL, &pref_vte_colour_back);
419 gtk_widget_modify_cursor(console, &pref_vte_colour_fore, &pref_vte_colour_back);
420 #else
421 GString *css_string;
422 GtkStyleContext *context;
423 GtkCssProvider *provider;
424 gchar *css_code, *color, *background_color;
425
426 color = gdk_rgba_to_string (&pref_vte_colour_fore);
427 background_color = gdk_rgba_to_string (&pref_vte_colour_back);
428
429 gtk_widget_set_name(console, "scope-console");
430 context = gtk_widget_get_style_context(console);
431 provider = gtk_css_provider_new();
432 gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
433 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
434
435 css_string = g_string_new(NULL);
436 g_string_printf(css_string, "#scope-console { color: %s; background-color: %s; }",
437 color, background_color);
438 css_code = g_string_free(css_string, FALSE);
439
440 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider), css_code, -1, NULL);
441
442 g_free(css_code);
443 g_object_unref(provider);
444 #endif
445 ui_widget_modify_font_from_string(console, pref_vte_font);
446 }
447
conterm_apply_config(void)448 void conterm_apply_config(void)
449 {
450 #ifdef G_OS_UNIX
451 on_vte_realize(program_terminal, NULL);
452
453 if (debug_console)
454 on_vte_realize(debug_console, NULL);
455 else
456 #endif
457 {
458 context_apply_config(GTK_WIDGET(debug_context));
459 }
460 }
461
462 #ifdef G_OS_UNIX
463 static int pty_slave = -1;
464 char *slave_pty_name = NULL;
465 #endif
466
conterm_init(void)467 void conterm_init(void)
468 {
469 GtkWidget *console;
470 #ifdef G_OS_UNIX
471 gchar *error = NULL;
472 int pty_master;
473 char *pty_name;
474 #endif
475
476 conterm_load_config();
477 #ifdef G_OS_UNIX
478 program_window = get_widget("program_window");
479 console = vte_terminal_new();
480 gtk_widget_show(console);
481 program_terminal = VTE_TERMINAL(console);
482 g_object_ref(program_terminal);
483 gtk_container_add(GTK_CONTAINER(program_window), console);
484 g_signal_connect_after(program_terminal, "realize", G_CALLBACK(on_vte_realize), NULL);
485 terminal_parent = get_widget("terminal_parent");
486 g_signal_connect(terminal_parent, "delete-event", G_CALLBACK(on_terminal_parent_delete),
487 NULL);
488 terminal_window = get_widget("terminal_window");
489 terminal_show = GTK_CHECK_MENU_ITEM(get_widget("terminal_show"));
490
491 if (pref_terminal_padding)
492 {
493 gint vte_border_x, vte_border_y;
494
495 #if GTK_CHECK_VERSION(3, 4, 0)
496 GtkStyleContext *context;
497 GtkBorder border;
498
499 context = gtk_widget_get_style_context (console);
500 gtk_style_context_get_padding (context, GTK_STATE_FLAG_NORMAL, &border);
501 vte_border_x = border.left + border.right;
502 vte_border_y = border.top + border.bottom;
503 #elif VTE_CHECK_VERSION(0, 24, 0)
504 GtkBorder *border = NULL;
505
506 gtk_widget_style_get(console, "inner-border", &border, NULL);
507
508 if (border)
509 {
510 vte_border_x = border->left + border->right;
511 vte_border_y = border->top + border->bottom;
512 gtk_border_free(border);
513 }
514 else
515 vte_border_x = vte_border_y = 2;
516 #else /* VTE 0.24.0 */
517 /* VTE manual says "deprecated since 0.26", but it's since 0.24 */
518 vte_terminal_get_padding(program_terminal, &vte_border_x, &vte_border_y);
519 #endif /* VTE 0.24.0 */
520 pref_terminal_width += vte_border_x;
521 pref_terminal_height += vte_border_y;
522 pref_terminal_padding = FALSE;
523 }
524
525 if (openpty(&pty_master, &pty_slave, NULL, NULL, NULL) == 0 &&
526 grantpt(pty_master) == 0 && unlockpt(pty_master) == 0 &&
527 (pty_name = ttyname(pty_slave)) != NULL)
528 {
529 #if VTE_CHECK_VERSION(0, 25, 0)
530 GError *gerror = NULL;
531 VtePty *pty = vte_pty_new_foreign(pty_master, &gerror);
532
533 if (pty)
534 {
535 vte_terminal_set_pty_object(program_terminal, pty);
536 slave_pty_name = g_strdup(pty_name);
537 }
538 else
539 {
540 error = g_strdup(gerror->message);
541 g_error_free(gerror);
542 }
543 #else /* VTE 0.25.0 */
544 vte_terminal_set_pty(program_terminal, pty_master);
545 slave_pty_name = g_strdup(pty_name);
546 #endif /* VTE 0.25.0 */
547 }
548 else
549 error = g_strdup_printf("pty: %s", g_strerror(errno));
550
551 if (error)
552 {
553 gtk_widget_set_sensitive(program_window, FALSE);
554 gtk_widget_set_sensitive(GTK_WIDGET(terminal_show), FALSE);
555 msgwin_status_add(_("Scope: %s."), error);
556 g_free(error);
557 }
558 else
559 menu_connect("terminal_menu", &terminal_menu_info, GTK_WIDGET(program_terminal));
560 #else /* G_OS_UNIX */
561 gtk_widget_hide(get_widget("program_window"));
562 #endif /* G_OS_UNIX */
563
564 #ifdef G_OS_UNIX
565 if (pref_debug_console_vte)
566 {
567 console = vte_terminal_new();
568 gtk_widget_show(console);
569 debug_console = VTE_TERMINAL(console);
570 dc_output = console_output;
571 dc_output_nl = console_output_nl;
572 g_signal_connect_after(debug_console, "realize", G_CALLBACK(on_vte_realize), NULL);
573 menu_connect("console_menu", &console_menu_info, console);
574 }
575 else
576 #endif /* G_OS_UNIX */
577 {
578 static const char *const colors[NFD] = { "#00C0C0", "#C0C0C0", "#C00000",
579 "#C0C0C0", "#C000C0" };
580 guint i;
581
582 console = get_widget("debug_context");
583 context_apply_config(console);
584 debug_context = GTK_TEXT_VIEW(console);
585 dc_output = context_output;
586 dc_output_nl = context_output_nl;
587 context = gtk_text_view_get_buffer(debug_context);
588
589 for (i = 0; i < NFD; i++)
590 {
591 fd_tags[i] = gtk_text_buffer_create_tag(context, NULL, "foreground",
592 colors[i], NULL);
593 }
594 g_signal_connect(console, "button-press-event",
595 G_CALLBACK(on_console_button_3_press),
596 menu_connect("console_menu", &console_menu_info, NULL));
597 }
598
599 gtk_container_add(GTK_CONTAINER(get_widget("debug_window")), console);
600 g_signal_connect(console, "key-press-event", G_CALLBACK(on_console_key_press), NULL);
601 }
602
conterm_finalize(void)603 void conterm_finalize(void)
604 {
605 #ifdef G_OS_UNIX
606 g_object_unref(program_terminal);
607 g_free(slave_pty_name);
608 close(pty_slave);
609 /* close(pty_master) causes 100% CPU load on 0.24 and is not allowed on 0.26+ */
610 #endif /* G_OS_UNIX */
611 }
612