1 /* licensing and copyright information here */
6 #include <glib/gi18n-lib.h>
8 #include <pango/pango.h>
9 #include "chimara-glk.h"
10 #include "chimara-glk-private.h"
18 #define CHIMARA_GLK_MIN_WIDTH 0
19 #define CHIMARA_GLK_MIN_HEIGHT 0
23 * @short_description: Widget which executes a Glk program
24 * @stability: Unstable
25 * @include: chimara/chimara-glk.h
27 * The ChimaraGlk widget opens and runs a Glk program. The program must be
28 * compiled as a plugin module, with a function <function>glk_main()</function>
29 * that the Glk library can hook into.
31 * On Linux systems, this is a file with a name like
32 * <filename>plugin.so</filename>. For portability, you can use libtool and
35 * pkglib_LTLIBRARIES = plugin.la
36 * plugin_la_SOURCES = plugin.c foo.c bar.c
37 * plugin_la_LDFLAGS = -module -shared -avoid-version -export-symbols-regex "^glk_main$$"
39 * This will produce <filename>plugin.la</filename> which is a text file
40 * containing the correct plugin file to open (see the relevant section of the
42 * url="http://www.gnu.org/software/libtool/manual/html_node/Finding-the-dlname.html">
43 * Libtool manual</ulink>).
46 typedef void (* glk_main_t) (void);
47 typedef int (* glkunix_startup_code_t) (glkunix_startup_t*);
53 PROP_DEFAULT_FONT_DESCRIPTION,
54 PROP_MONOSPACE_FONT_DESCRIPTION,
65 static guint chimara_glk_signals[LAST_SIGNAL] = { 0 };
67 G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
70 chimara_glk_init(ChimaraGlk *self)
72 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW);
74 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
77 priv->interactive = TRUE;
78 priv->protect = FALSE;
79 priv->default_font_desc = pango_font_description_from_string("Sans");
80 priv->monospace_font_desc = pango_font_description_from_string("Monospace");
81 priv->css_file = "style.css";
82 priv->default_styles = g_hash_table_new(g_str_hash, g_str_equal);
85 priv->event_queue = NULL;
86 priv->event_lock = NULL;
87 priv->event_queue_not_empty = NULL;
88 priv->event_queue_not_full = NULL;
89 priv->abort_lock = NULL;
90 priv->abort_signalled = FALSE;
91 priv->arrange_lock = NULL;
92 priv->rearranged = NULL;
93 priv->needs_rearrange = FALSE;
94 priv->ignore_next_arrange_event = FALSE;
95 priv->interrupt_handler = NULL;
96 priv->root_window = NULL;
97 priv->fileref_list = NULL;
98 priv->current_stream = NULL;
99 priv->stream_list = NULL;
101 priv->in_startup = FALSE;
102 priv->current_dir = NULL;
106 chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
108 ChimaraGlk *glk = CHIMARA_GLK(object);
112 case PROP_INTERACTIVE:
113 chimara_glk_set_interactive( glk, g_value_get_boolean(value) );
116 chimara_glk_set_protect( glk, g_value_get_boolean(value) );
118 case PROP_DEFAULT_FONT_DESCRIPTION:
119 chimara_glk_set_default_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) );
121 case PROP_MONOSPACE_FONT_DESCRIPTION:
122 chimara_glk_set_monospace_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) );
125 chimara_glk_set_spacing( glk, g_value_get_uint(value) );
128 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
133 chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
135 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object);
139 case PROP_INTERACTIVE:
140 g_value_set_boolean(value, priv->interactive);
143 g_value_set_boolean(value, priv->protect);
145 case PROP_DEFAULT_FONT_DESCRIPTION:
146 g_value_set_pointer(value, priv->default_font_desc);
148 case PROP_MONOSPACE_FONT_DESCRIPTION:
149 g_value_set_pointer(value, priv->monospace_font_desc);
152 g_value_set_uint(value, priv->spacing);
155 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
160 chimara_glk_finalize(GObject *object)
162 ChimaraGlk *self = CHIMARA_GLK(object);
163 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
165 /* Free the event queue */
166 g_mutex_lock(priv->event_lock);
167 g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL);
168 g_queue_free(priv->event_queue);
169 g_cond_free(priv->event_queue_not_empty);
170 g_cond_free(priv->event_queue_not_full);
171 priv->event_queue = NULL;
172 g_mutex_unlock(priv->event_lock);
173 g_mutex_free(priv->event_lock);
175 /* Free the abort signalling mechanism */
176 g_mutex_lock(priv->abort_lock);
177 /* Make sure no other thread is busy with this */
178 g_mutex_unlock(priv->abort_lock);
179 g_mutex_free(priv->abort_lock);
180 priv->abort_lock = NULL;
182 /* Free the window arrangement signalling */
183 g_mutex_lock(priv->arrange_lock);
184 g_cond_free(priv->rearranged);
185 g_mutex_unlock(priv->arrange_lock);
186 g_mutex_free(priv->arrange_lock);
187 priv->arrange_lock = NULL;
189 /* Free private data */
190 pango_font_description_free(priv->default_font_desc);
191 pango_font_description_free(priv->monospace_font_desc);
192 g_free(priv->current_dir);
193 g_hash_table_destroy(priv->default_styles);
195 G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
198 /* Internal function: Recursively get the Glk window tree's size request */
200 request_recurse(winid_t win, GtkRequisition *requisition, guint spacing)
202 if(win->type == wintype_Pair)
204 /* Get children's size requests */
205 GtkRequisition child1, child2;
206 request_recurse(win->window_node->children->data, &child1, spacing);
207 request_recurse(win->window_node->children->next->data, &child2, spacing);
209 glui32 division = win->split_method & winmethod_DivisionMask;
210 glui32 direction = win->split_method & winmethod_DirMask;
212 /* If the split is fixed, get the size of the fixed child */
213 if(division == winmethod_Fixed)
218 child1.width = win->key_window?
219 win->constraint_size * win->key_window->unit_width
222 case winmethod_Right:
223 child2.width = win->key_window?
224 win->constraint_size * win->key_window->unit_width
227 case winmethod_Above:
228 child1.height = win->key_window?
229 win->constraint_size * win->key_window->unit_height
232 case winmethod_Below:
233 child2.height = win->key_window?
234 win->constraint_size * win->key_window->unit_height
240 /* Add the children's requests */
244 case winmethod_Right:
245 requisition->width = child1.width + child2.width + spacing;
246 requisition->height = MAX(child1.height, child2.height);
248 case winmethod_Above:
249 case winmethod_Below:
250 requisition->width = MAX(child1.width, child2.width);
251 requisition->height = child1.height + child2.height + spacing;
256 /* For non-pair windows, just use the size that GTK requests */
258 gtk_widget_size_request(win->frame, requisition);
261 /* Overrides gtk_widget_size_request */
263 chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition)
265 g_return_if_fail(widget);
266 g_return_if_fail(requisition);
267 g_return_if_fail(CHIMARA_IS_GLK(widget));
269 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
271 /* For now, just pass the size request on to the root Glk window */
272 if(priv->root_window)
274 request_recurse(priv->root_window->data, requisition, priv->spacing);
275 requisition->width += 2 * GTK_CONTAINER(widget)->border_width;
276 requisition->height += 2 * GTK_CONTAINER(widget)->border_width;
280 requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * GTK_CONTAINER(widget)->border_width;
281 requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * GTK_CONTAINER(widget)->border_width;
285 /* Recursively give the Glk windows their allocated space. Returns a window
286 containing all children of this window that must be redrawn, or NULL if there
287 are no children that require redrawing. */
289 allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing)
291 if(win->type == wintype_Pair)
293 glui32 division = win->split_method & winmethod_DivisionMask;
294 glui32 direction = win->split_method & winmethod_DirMask;
296 /* If the space gets too small to honor the spacing property, then just
297 ignore spacing in this window and below. */
298 if( (spacing > allocation->width && (direction == winmethod_Left || direction == winmethod_Right))
299 || (spacing > allocation->height && (direction == winmethod_Above || direction == winmethod_Below)) )
302 GtkAllocation child1, child2;
303 child1.x = allocation->x;
304 child1.y = allocation->y;
306 if(division == winmethod_Fixed)
308 /* If the key window has been closed, then default to 0; otherwise
309 use the key window to determine the size */
313 child1.width = win->key_window?
314 CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing)
317 case winmethod_Right:
318 child2.width = win->key_window?
319 CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing)
322 case winmethod_Above:
323 child1.height = win->key_window?
324 CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing)
327 case winmethod_Below:
328 child2.height = win->key_window?
329 CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing)
334 else /* proportional */
336 gdouble fraction = win->constraint_size / 100.0;
340 child1.width = MAX(0, (gint)ceil(fraction * (allocation->width - spacing)) );
342 case winmethod_Right:
343 child2.width = MAX(0, (gint)ceil(fraction * (allocation->width - spacing)) );
345 case winmethod_Above:
346 child1.height = MAX(0, (gint)ceil(fraction * (allocation->height - spacing)) );
348 case winmethod_Below:
349 child2.height = MAX(0, (gint)ceil(fraction * (allocation->height - spacing)) );
354 /* Fill in the rest of the size requisitions according to the child specified above */
358 child2.width = MAX(0, allocation->width - spacing - child1.width);
359 child2.x = child1.x + child1.width + spacing;
361 child1.height = child2.height = allocation->height;
363 case winmethod_Right:
364 child1.width = MAX(0, allocation->width - spacing - child2.width);
365 child2.x = child1.x + child1.width + spacing;
367 child1.height = child2.height = allocation->height;
369 case winmethod_Above:
370 child2.height = MAX(0, allocation->height - spacing - child1.height);
372 child2.y = child1.y + child1.height + spacing;
373 child1.width = child2.width = allocation->width;
375 case winmethod_Below:
376 child1.height = MAX(0, allocation->height - spacing - child2.height);
378 child2.y = child1.y + child1.height + spacing;
379 child1.width = child2.width = allocation->width;
384 winid_t arrange1 = allocate_recurse(win->window_node->children->data, &child1, spacing);
385 winid_t arrange2 = allocate_recurse(win->window_node->children->next->data, &child2, spacing);
393 else if(win->type == wintype_TextGrid)
395 /* Pass the size allocation on to the framing widget */
396 gtk_widget_size_allocate(win->frame, allocation);
397 /* It says in the spec that when a text grid window is resized smaller,
398 the bottom or right area is thrown away; when it is resized larger, the
399 bottom or right area is filled with blanks. */
400 glui32 newwidth = (glui32)(win->widget->allocation.width / win->unit_width);
401 glui32 newheight = (glui32)(win->widget->allocation.height / win->unit_height);
403 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
404 GtkTextIter start, end;
406 for(line = 0; line < win->height; line++)
408 gtk_text_buffer_get_iter_at_line(textbuffer, &start, line);
409 /* If this line is going to fall off the bottom, delete it */
410 if(line >= newheight)
413 gtk_text_iter_forward_to_line_end(&end);
414 gtk_text_iter_forward_char(&end);
415 gtk_text_buffer_delete(textbuffer, &start, &end);
418 /* If this line is not long enough, add spaces on the end */
419 if(newwidth > win->width)
421 gchar *spaces = g_strnfill(newwidth - win->width, ' ');
422 gtk_text_iter_forward_to_line_end(&start);
423 gtk_text_buffer_insert(textbuffer, &start, spaces, -1);
426 /* But if it's too long, delete characters from the end */
427 else if(newwidth < win->width)
430 gtk_text_iter_forward_chars(&start, newwidth);
431 gtk_text_iter_forward_to_line_end(&end);
432 gtk_text_buffer_delete(textbuffer, &start, &end);
434 /* Note: if the widths are equal, do nothing */
436 /* Add blank lines if there aren't enough lines to fit the new size */
437 if(newheight > win->height)
439 gchar *blanks = g_strnfill(win->width, ' ');
440 gchar **blanklines = g_new0(gchar *, (newheight - win->height) + 1);
442 for(count = 0; count < newheight - win->height; count++)
443 blanklines[count] = blanks;
444 blanklines[newheight - win->height] = NULL;
445 gchar *text = g_strjoinv("\n", blanklines);
446 g_free(blanklines); /* not g_strfreev() */
449 gtk_text_buffer_get_end_iter(textbuffer, &start);
450 gtk_text_buffer_insert(textbuffer, &start, "\n", -1);
451 gtk_text_buffer_insert(textbuffer, &start, text, -1);
455 gboolean arrange = !(win->width == newwidth && win->height == newheight);
456 win->width = newwidth;
457 win->height = newheight;
458 return arrange? win : NULL;
461 /* For non-pair, non-text-grid windows, just give them the size */
462 gtk_widget_size_allocate(win->frame, allocation);
466 /* Overrides gtk_widget_size_allocate */
468 chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
470 g_return_if_fail(widget);
471 g_return_if_fail(allocation);
472 g_return_if_fail(CHIMARA_IS_GLK(widget));
474 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
476 widget->allocation = *allocation;
478 if(priv->root_window) {
480 child.x = allocation->x + GTK_CONTAINER(widget)->border_width;
481 child.y = allocation->y + GTK_CONTAINER(widget)->border_width;
482 child.width = CLAMP(allocation->width - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->width);
483 child.height = CLAMP(allocation->height - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->height);
484 winid_t arrange = allocate_recurse(priv->root_window->data, &child, priv->spacing);
486 /* arrange points to a window that contains all text grid and graphics
487 windows which have been resized */
488 g_mutex_lock(priv->arrange_lock);
489 if(!priv->ignore_next_arrange_event)
492 event_throw(CHIMARA_GLK(widget), evtype_Arrange, arrange == priv->root_window->data? NULL : arrange, 0, 0);
495 priv->ignore_next_arrange_event = FALSE;
496 priv->needs_rearrange = FALSE;
497 g_cond_signal(priv->rearranged);
498 g_mutex_unlock(priv->arrange_lock);
502 /* Recursively invoke callback() on the GtkWidget of each non-pair window in the tree */
504 forall_recurse(winid_t win, GtkCallback callback, gpointer callback_data)
506 if(win->type == wintype_Pair)
508 forall_recurse(win->window_node->children->data, callback, callback_data);
509 forall_recurse(win->window_node->children->next->data, callback, callback_data);
512 (*callback)(win->frame, callback_data);
515 /* Overrides gtk_container_forall */
517 chimara_glk_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data)
519 g_return_if_fail(container);
520 g_return_if_fail(CHIMARA_IS_GLK(container));
522 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container);
524 /* All the children are "internal" */
525 if(!include_internals)
528 if(priv->root_window)
529 forall_recurse(priv->root_window->data, callback, callback_data);
533 chimara_glk_stopped(ChimaraGlk *self)
535 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
537 /* Free the plugin */
538 if( priv->program && !g_module_close(priv->program) )
539 g_warning( "Error closing module: %s", g_module_error() );
543 chimara_glk_started(ChimaraGlk *self)
545 /* TODO: Add default signal handler implementation here */
548 /* G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */
549 #ifndef G_PARAM_STATIC_STRINGS
550 #define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)
554 chimara_glk_class_init(ChimaraGlkClass *klass)
556 /* Override methods of parent classes */
557 GObjectClass *object_class = G_OBJECT_CLASS(klass);
558 object_class->set_property = chimara_glk_set_property;
559 object_class->get_property = chimara_glk_get_property;
560 object_class->finalize = chimara_glk_finalize;
562 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
563 widget_class->size_request = chimara_glk_size_request;
564 widget_class->size_allocate = chimara_glk_size_allocate;
566 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
567 container_class->forall = chimara_glk_forall;
570 klass->stopped = chimara_glk_stopped;
571 klass->started = chimara_glk_started;
573 * ChimaraGlk::stopped:
574 * @glk: The widget that received the signal
576 * The ::stopped signal is emitted when the a Glk program finishes
577 * executing in the widget, whether it ended normally, or was interrupted.
579 chimara_glk_signals[STOPPED] = g_signal_new("stopped",
580 G_OBJECT_CLASS_TYPE(klass), 0,
581 G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL,
582 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
584 * ChimaraGlk::started:
585 * @glk: The widget that received the signal
587 * The ::started signal is emitted when a Glk program starts executing in
590 chimara_glk_signals[STARTED] = g_signal_new ("started",
591 G_OBJECT_CLASS_TYPE (klass), 0,
592 G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL,
593 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
597 * ChimaraGlk:interactive:
599 * Sets whether the widget is interactive. A Glk widget is normally
600 * interactive, but in non-interactive mode, keyboard and mouse input are
601 * ignored and the Glk program is controlled by chimara_glk_feed_text().
602 * <quote>More</quote> prompts when a lot of text is printed to a text
603 * buffer are also disabled. This is typically used when you wish to control
604 * an interpreter program by feeding it a predefined list of commands.
606 g_object_class_install_property( object_class, PROP_INTERACTIVE,
607 g_param_spec_boolean("interactive", _("Interactive"),
608 _("Whether user input is expected in the Glk program"),
610 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
613 * ChimaraGlk:protect:
615 * Sets whether the Glk program is allowed to do file operations. In protect
616 * mode, all file operations will fail.
618 g_object_class_install_property(object_class, PROP_PROTECT,
619 g_param_spec_boolean("protect", _("Protected"),
620 _("Whether the Glk program is barred from doing file operations"),
622 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
624 /* We can't use G_PARAM_CONSTRUCT on these because then the constructor will
625 initialize them with NULL */
627 * ChimaraGlk:default-font-description:
629 * Pointer to a #PangoFontDescription describing the default proportional
630 * font, to be used in text buffer windows for example.
632 * Default value: font description created from the string
633 * <quote>Sans</quote>
635 g_object_class_install_property(object_class, PROP_DEFAULT_FONT_DESCRIPTION,
636 g_param_spec_pointer("default-font-description", _("Default Font"),
637 _("Font description of the default proportional font"),
638 G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
641 * ChimaraGlk:monospace-font-description:
643 * Pointer to a #PangoFontDescription describing the default monospace font,
644 * to be used in text grid windows and %style_Preformatted, for example.
646 * Default value: font description created from the string
647 * <quote>Monospace</quote>
649 g_object_class_install_property(object_class, PROP_MONOSPACE_FONT_DESCRIPTION,
650 g_param_spec_pointer("monospace-font-description", _("Monospace Font"),
651 _("Font description of the default monospace font"),
652 G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
655 * ChimaraGlk:spacing:
657 * The amount of space between the Glk windows.
659 g_object_class_install_property(object_class, PROP_SPACING,
660 g_param_spec_uint("spacing", _("Spacing"),
661 _("The amount of space between Glk windows"),
663 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
666 g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
669 /* PUBLIC FUNCTIONS */
674 * Creates and initializes a new #ChimaraGlk widget.
676 * Return value: a #ChimaraGlk widget, with a floating reference.
679 chimara_glk_new(void)
681 /* This is a library entry point; initialize the library */
684 ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
685 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
687 priv->event_queue = g_queue_new();
688 priv->event_lock = g_mutex_new();
689 priv->event_queue_not_empty = g_cond_new();
690 priv->event_queue_not_full = g_cond_new();
691 priv->abort_lock = g_mutex_new();
692 priv->arrange_lock = g_mutex_new();
693 priv->rearranged = g_cond_new();
695 return GTK_WIDGET(self);
699 * chimara_glk_set_interactive:
700 * @glk: a #ChimaraGlk widget
701 * @interactive: whether the widget should expect user input
703 * Sets the #ChimaraGlk:interactive property of @glk.
706 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
708 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
710 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
711 priv->interactive = interactive;
715 * chimara_glk_get_interactive:
716 * @glk: a #ChimaraGlk widget
718 * Returns whether @glk is interactive (expecting user input). See
719 * #ChimaraGlk:interactive.
721 * Return value: %TRUE if @glk is interactive.
724 chimara_glk_get_interactive(ChimaraGlk *glk)
726 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
728 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
729 return priv->interactive;
733 * chimara_glk_set_protect:
734 * @glk: a #ChimaraGlk widget
735 * @protect: whether the widget should allow the Glk program to do file
738 * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk
739 * program is not allowed to do file operations.
742 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
744 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
746 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
747 priv->protect = protect;
751 * chimara_glk_get_protect:
752 * @glk: a #ChimaraGlk widget
754 * Returns whether @glk is in protect mode (banned from doing file operations).
755 * See #ChimaraGlk:protect.
757 * Return value: %TRUE if @glk is in protect mode.
760 chimara_glk_get_protect(ChimaraGlk *glk)
762 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
764 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
765 return priv->protect;
769 * chimara_glk_set_default_font_description:
770 * @glk: a #ChimaraGlk widget
771 * @font: a #PangoFontDescription
773 * Sets @glk's default proportional font. See
774 * #ChimaraGlk:default-font-description.
777 chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font)
779 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
780 g_return_if_fail(font);
782 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
783 pango_font_description_free(priv->default_font_desc);
784 priv->default_font_desc = pango_font_description_copy(font);
786 /* TODO: Apply the font description to all the windows and recalculate the sizes */
790 * chimara_glk_set_default_font_string:
791 * @glk: a #ChimaraGlk widget
792 * @font: string representation of a font description
794 * Sets @glk's default proportional font according to the string @font, which
795 * must be a string in the form <quote><replaceable>FAMILY-LIST</replaceable>
796 * [<replaceable>STYLE-OPTIONS</replaceable>]
797 * [<replaceable>SIZE</replaceable>]</quote>, such as <quote>Charter,Utopia
798 * Italic 12</quote> or <quote>Sans</quote>. See
799 * #ChimaraGlk:default-font-description.
802 chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font)
804 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
805 g_return_if_fail(font || *font);
807 PangoFontDescription *fontdesc = pango_font_description_from_string(font);
808 g_return_if_fail(fontdesc);
810 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
811 pango_font_description_free(priv->default_font_desc);
812 priv->default_font_desc = fontdesc;
814 /* TODO: Apply the font description to all the windows and recalculate the sizes */
818 * chimara_glk_get_default_font_description:
819 * @glk: a #ChimaraGlk widget
821 * Returns @glk's default proportional font.
823 * Return value: a newly-allocated #PangoFontDescription which must be freed
824 * using pango_font_description_free(), or %NULL on error.
826 PangoFontDescription *
827 chimara_glk_get_default_font_description(ChimaraGlk *glk)
829 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL);
831 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
832 return pango_font_description_copy(priv->default_font_desc);
836 * chimara_glk_set_monospace_font_description:
837 * @glk: a #ChimaraGlk widget
838 * @font: a #PangoFontDescription
840 * Sets @glk's default monospace font. See
841 * #ChimaraGlk:monospace-font-description.
844 chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font)
846 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
847 g_return_if_fail(font);
849 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
850 pango_font_description_free(priv->monospace_font_desc);
851 priv->monospace_font_desc = pango_font_description_copy(font);
853 /* TODO: Apply the font description to all the windows and recalculate the sizes */
857 * chimara_glk_set_monospace_font_string:
858 * @glk: a #ChimaraGlk widget
859 * @font: string representation of a font description
861 * Sets @glk's default monospace font according to the string @font, which must
862 * be a string in the form <quote><replaceable>FAMILY-LIST</replaceable>
863 * [<replaceable>STYLE-OPTIONS</replaceable>]
864 * [<replaceable>SIZE</replaceable>]</quote>, such as <quote>Courier
865 * Bold 12</quote> or <quote>Monospace</quote>. See
866 * #ChimaraGlk:monospace-font-description.
869 chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font)
871 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
872 g_return_if_fail(font || *font);
874 PangoFontDescription *fontdesc = pango_font_description_from_string(font);
875 g_return_if_fail(fontdesc);
877 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
878 pango_font_description_free(priv->monospace_font_desc);
879 priv->monospace_font_desc = fontdesc;
881 /* TODO: Apply the font description to all the windows and recalculate the sizes */
885 * chimara_glk_get_monospace_font_description:
886 * @glk: a #ChimaraGlk widget
888 * Returns @glk's default monospace font.
890 * Return value: a newly-allocated #PangoFontDescription which must be freed
891 * using pango_font_description_free(), or %NULL on error.
893 PangoFontDescription *
894 chimara_glk_get_monospace_font_description(ChimaraGlk *glk)
896 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL);
898 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
899 return pango_font_description_copy(priv->monospace_font_desc);
903 * chimara_glk_set_spacing:
904 * @glk: a #ChimaraGlk widget
905 * @spacing: the number of pixels to put between Glk windows
907 * Sets the #ChimaraGlk:spacing property of @glk, which is the border width in
908 * pixels between Glk windows.
911 chimara_glk_set_spacing(ChimaraGlk *glk, guint spacing)
913 g_return_if_fail( glk || CHIMARA_IS_GLK(glk) );
915 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
916 priv->spacing = spacing;
920 * chimara_glk_get_spacing:
921 * @glk: a #ChimaraGlk widget
923 * Gets the value set by chimara_glk_set_spacing().
925 * Return value: pixels of spacing between Glk windows
928 chimara_glk_get_spacing(ChimaraGlk *glk)
930 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), 0);
932 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
933 return priv->spacing;
938 glkunix_startup_code_t glkunix_startup_code;
939 glkunix_startup_t args;
940 ChimaraGlkPrivate *glk_data;
943 /* glk_enter() is the actual function called in the new thread in which glk_main() runs. */
945 glk_enter(struct StartupData *startup)
947 extern GPrivate *glk_data_key;
948 g_private_set(glk_data_key, startup->glk_data);
950 /* Run startup function */
951 if(startup->glkunix_startup_code) {
952 startup->glk_data->in_startup = TRUE;
953 int result = startup->glkunix_startup_code(&startup->args);
954 startup->glk_data->in_startup = FALSE;
957 while(i < startup->args.argc)
958 g_free(startup->args.argv[i++]);
959 g_free(startup->args.argv);
965 /* Run main function */
966 g_signal_emit_by_name(startup->glk_data->self, "started");
967 (startup->glk_main)();
968 g_signal_emit_by_name(startup->glk_data->self, "stopped");
969 g_slice_free(struct StartupData, startup);
975 * @glk: a #ChimaraGlk widget
976 * @plugin: path to a plugin module compiled with <filename
977 * class="header">glk.h</filename>
978 * @argc: Number of command line arguments in @argv
979 * @argv: Array of command line arguments to pass to the plugin
980 * @error: location to store a <link linkend="glib-GError">GError</link>, or
983 * Opens a Glk program compiled as a plugin. Sorts out its command line
984 * arguments from #glkunix_arguments, calls its startup function
985 * glkunix_startup_code(), and then calls its main function glk_main() in
986 * a separate thread. On failure, returns %FALSE and sets @error.
988 * The plugin must at least export a glk_main() function; #glkunix_arguments and
989 * glkunix_startup_code() are optional.
991 * Return value: %TRUE if the Glk program was started successfully.
994 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, int argc, char *argv[], GError **error)
996 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
997 g_return_val_if_fail(plugin, FALSE);
999 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
1000 struct StartupData *startup = g_slice_new0(struct StartupData);
1002 /* Open the module to run */
1003 g_assert( g_module_supported() );
1004 priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY);
1008 g_warning( "Error opening module: %s", g_module_error() );
1011 if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &startup->glk_main) )
1013 g_warning( "Error finding glk_main(): %s", g_module_error() );
1017 if( g_module_symbol(priv->program, "glkunix_startup_code", (gpointer *) &startup->glkunix_startup_code) )
1019 glkunix_argumentlist_t *glkunix_arguments;
1021 if( !(g_module_symbol(priv->program, "glkunix_arguments", (gpointer *) &glkunix_arguments)
1022 && parse_command_line(glkunix_arguments, argc, argv, &startup->args)) )
1024 /* arguments could not be parsed, so create data ourselves */
1025 startup->args.argc = 1;
1026 startup->args.argv = g_new0(gchar *, 1);
1029 /* Set the program name */
1030 startup->args.argv[0] = g_strdup(plugin);
1032 startup->glk_data = priv;
1034 /* Run in a separate thread */
1035 priv->thread = g_thread_create((GThreadFunc)glk_enter, startup, TRUE, error);
1037 return !(priv->thread == NULL);
1042 * @glk: a #ChimaraGlk widget
1044 * Signals the Glk program running in @glk to abort. Note that if the program is
1045 * caught in an infinite loop in which glk_tick() is not called, this may not
1049 chimara_glk_stop(ChimaraGlk *glk)
1051 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
1052 /* TODO: check if glk is actually running a program */
1053 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
1054 if(priv->abort_lock) {
1055 g_mutex_lock(priv->abort_lock);
1056 priv->abort_signalled = TRUE;
1057 g_mutex_unlock(priv->abort_lock);
1058 /* Stop blocking on the event queue condition */
1059 event_throw(glk, evtype_Abort, NULL, 0, 0);
1065 * @glk: a #ChimaraGlk widget
1067 * Holds up the main thread and waits for the Glk program running in @glk to
1071 chimara_glk_wait(ChimaraGlk *glk)
1073 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
1075 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
1076 g_thread_join(priv->thread);