+glui32
+keyval_to_glk_keycode(guint keyval, gboolean unicode)
+{
+ glui32 keycode;
+ switch(keyval) {
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up: return keycode_Up;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down: return keycode_Down;
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left: return keycode_Left;
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right: return keycode_Right;
+ case GDK_KEY_Linefeed:
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter: return keycode_Return;
+ case GDK_KEY_Delete:
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_KP_Delete: return keycode_Delete;
+ case GDK_KEY_Escape: return keycode_Escape;
+ case GDK_KEY_Tab:
+ case GDK_KEY_KP_Tab: return keycode_Tab;
+ case GDK_KEY_Page_Up:
+ case GDK_KEY_KP_Page_Up: return keycode_PageUp;
+ case GDK_KEY_Page_Down:
+ case GDK_KEY_KP_Page_Down: return keycode_PageDown;
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home: return keycode_Home;
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End: return keycode_End;
+ case GDK_KEY_F1:
+ case GDK_KEY_KP_F1: return keycode_Func1;
+ case GDK_KEY_F2:
+ case GDK_KEY_KP_F2: return keycode_Func2;
+ case GDK_KEY_F3:
+ case GDK_KEY_KP_F3: return keycode_Func3;
+ case GDK_KEY_F4:
+ case GDK_KEY_KP_F4: return keycode_Func4;
+ case GDK_KEY_F5: return keycode_Func5;
+ case GDK_KEY_F6: return keycode_Func6;
+ case GDK_KEY_F7: return keycode_Func7;
+ case GDK_KEY_F8: return keycode_Func8;
+ case GDK_KEY_F9: return keycode_Func9;
+ case GDK_KEY_F10: return keycode_Func10;
+ case GDK_KEY_F11: return keycode_Func11;
+ case GDK_KEY_F12: return keycode_Func12;
+ default:
+ keycode = gdk_keyval_to_unicode(keyval);
+ /* If keycode is 0, then keyval was not recognized; also return
+ unknown if Latin-1 input was requested and the character is not in
+ Latin-1 */
+ if(keycode == 0 || (!unicode && keycode > 255))
+ return keycode_Unknown;
+ return keycode;
+ }
+}
+
+void
+force_char_input_from_queue(winid_t win, event_t *event)
+{
+ ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+ guint keyval = GPOINTER_TO_UINT(g_async_queue_pop(glk_data->char_input_queue));
+
+ glk_cancel_char_event(win);
+
+ gdk_threads_enter();
+ ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
+ g_assert(glk);
+ g_signal_emit_by_name(glk, "char-input", win->rock, keyval);
+ gdk_threads_leave();
+
+ event->type = evtype_CharInput;
+ event->win = win;
+ event->val1 = keyval_to_glk_keycode(keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
+ event->val2 = 0;
+}
+
+void
+force_line_input_from_queue(winid_t win, event_t *event)
+{
+ ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+ const gchar *text = g_async_queue_pop(glk_data->line_input_queue);
+ glui32 chars_written = 0;
+
+ gdk_threads_enter();
+ if(win->type == wintype_TextBuffer)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ GtkTextIter start, end;
+
+ /* Remove signal handlers so the line input doesn't get picked up again */
+ g_signal_handler_block(buffer, win->insert_text_handler);
+ g_signal_handler_block(win->widget, win->line_input_keypress_handler);
+
+ /* Erase any text that was already typed */
+ GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
+ gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_delete(buffer, &start, &end);
+
+ /* Make the window uneditable again */
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
+
+ /* Insert the forced input into the window */
+ if(win->echo_current_line_input)
+ {
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gchar *text_to_insert = g_strconcat(text, "\n", NULL);
+ gtk_text_buffer_insert_with_tags_by_name(buffer, &end, text_to_insert, -1, "default", "input", "glk-input", NULL);
+ }
+
+ chars_written = finish_text_buffer_line_input(win, TRUE);
+ }
+ else if(win->type == wintype_TextGrid)
+ {
+ /* Remove signal handlers so the line input doesn't get picked up again */
+ g_signal_handler_block(win->widget, win->char_input_keypress_handler);
+
+ /* Insert the forced input into the window */
+ gtk_entry_set_text(GTK_ENTRY(win->input_entry), text);
+ chars_written = finish_text_grid_line_input(win, TRUE);
+ }
+ gdk_threads_leave();
+
+ event->type = evtype_LineInput;
+ event->win = win;
+ event->val1 = chars_written;
+ event->val2 = 0;
+}
+
+/*** Internal function: cancels any pending input requests on the window and presents a warning if not INPUT_REQUEST_NONE ***/
+void
+cancel_old_input_request(winid_t win)
+{
+ switch(win->input_request_type) {
+ case INPUT_REQUEST_NONE:
+ break; /* All is well */
+ case INPUT_REQUEST_CHARACTER:
+ case INPUT_REQUEST_CHARACTER_UNICODE:
+ glk_cancel_char_event(win);
+ WARNING("Cancelling pending char event");
+ break;
+ case INPUT_REQUEST_LINE:
+ case INPUT_REQUEST_LINE_UNICODE:
+ glk_cancel_line_event(win, NULL);
+ WARNING("Cancelling pending line event");
+ break;
+ default:
+ WARNING("Could not cancel pending input request: unknown input request");
+ }
+}
+
+/**
+ * glk_set_echo_line_event:
+ * @win: The window in which to change the echoing behavior.
+ * @val: Zero to turn off echoing, nonzero for normal echoing.
+ *
+ * Normally, after line input is completed or cancelled in a buffer window, the
+ * library ensures that the complete input line (or its latest state, after
+ * cancelling) is displayed at the end of the buffer, followed by a newline.
+ * This call allows you to suppress this behavior. If the @val argument is zero,
+ * all <emphasis>subsequent</emphasis> line input requests in the given window
+ * will leave the buffer unchanged after the input is completed or cancelled;
+ * the player's input will not be printed. If @val is nonzero, subsequent input
+ * requests will have the normal printing behavior.
+ *
+ * <note><para>
+ * Note that this feature is unrelated to the window's echo stream.
+ * </para></note>
+ *
+ * If you turn off line input echoing, you can reproduce the standard input
+ * behavior by following each line input event (or line input cancellation) by
+ * printing the input line, in the Input style, followed by a newline in the
+ * original style.
+ *
+ * The glk_set_echo_line_event() does not affect a pending line input request.
+ * It also has no effect in non-buffer windows.
+ * <note><para>
+ * In a grid window, the game can overwrite the input area at will, so there
+ * is no need for this distinction.
+ * </para></note>
+ *
+ * Not all libraries support this feature. You can test for it with
+ * %gestalt_LineInputEcho.
+ */
+void
+glk_set_echo_line_event(winid_t win, glui32 val)
+{
+ VALID_WINDOW(win, return);
+
+ if(win->type != wintype_TextBuffer)
+ return; /* Quietly do nothing */
+
+ win->echo_line_input = val? TRUE : FALSE;
+}
+
+/* Internal function to convert from a Glk keycode to a GDK keyval. */
+static unsigned
+keycode_to_gdk_keyval(glui32 keycode)
+{
+ switch (keycode)
+ {
+ case keycode_Left:
+ return GDK_KEY_Left;
+ case keycode_Right:
+ return GDK_KEY_Right;
+ case keycode_Up:
+ return GDK_KEY_Up;
+ case keycode_Down:
+ return GDK_KEY_Down;
+ case keycode_Return:
+ return GDK_KEY_Return;
+ case keycode_Delete:
+ return GDK_KEY_Delete;
+ case keycode_Escape:
+ return GDK_KEY_Escape;
+ case keycode_Tab:
+ return GDK_KEY_Tab;
+ case keycode_PageUp:
+ return GDK_KEY_Page_Up;
+ case keycode_PageDown:
+ return GDK_KEY_Page_Down;
+ case keycode_Home:
+ return GDK_KEY_Home;
+ case keycode_End:
+ return GDK_KEY_End;
+ case keycode_Func1:
+ return GDK_KEY_F1;
+ case keycode_Func2:
+ return GDK_KEY_F2;
+ case keycode_Func3:
+ return GDK_KEY_F3;
+ case keycode_Func4:
+ return GDK_KEY_F4;
+ case keycode_Func5:
+ return GDK_KEY_F5;
+ case keycode_Func6:
+ return GDK_KEY_F6;
+ case keycode_Func7:
+ return GDK_KEY_F7;
+ case keycode_Func8:
+ return GDK_KEY_F8;
+ case keycode_Func9:
+ return GDK_KEY_F9;
+ case keycode_Func10:
+ return GDK_KEY_F10;
+ case keycode_Func11:
+ return GDK_KEY_F11;
+ case keycode_Func12:
+ return GDK_KEY_F12;
+ case keycode_Erase:
+ return GDK_KEY_BackSpace;
+ }
+ unsigned keyval = gdk_unicode_to_keyval(keycode);
+ if(keyval < 0x01000000) /* magic number meaning illegal unicode point */
+ return keyval;
+ return 0;
+}
+
+/* Internal function to decide whether @keycode is a valid line terminator. */
+gboolean
+is_valid_line_terminator(glui32 keycode)
+{
+ switch(keycode) {
+ case keycode_Escape:
+ case keycode_Func1:
+ case keycode_Func2:
+ case keycode_Func3:
+ case keycode_Func4:
+ case keycode_Func5:
+ case keycode_Func6:
+ case keycode_Func7:
+ case keycode_Func8:
+ case keycode_Func9:
+ case keycode_Func10:
+ case keycode_Func11:
+ case keycode_Func12:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * glk_set_terminators_line_event:
+ * @win: The window for which to set the line input terminator keys.
+ * @keycodes: An array of <code>keycode_</code> constants, of length @count.
+ * @count: The array length of @keycodes.
+ *
+ * It is possible to request that other keystrokes complete line input as well.
+ * (This allows a game to intercept function keys or other special keys during
+ * line input.) To do this, call glk_set_terminators_line_event(), and pass an
+ * array of @count keycodes. These must all be special keycodes (see <link
+ * linkend="chimara-Character-Input">Character Input</link>). Do not include
+ * regular printable characters in the array, nor %keycode_Return (which
+ * represents the default <keycap>enter</keycap> key and will always be
+ * recognized). To return to the default behavior, pass a %NULL or empty array.
+ *
+ * The glk_set_terminators_line_event() affects <emphasis>subsequent</emphasis>
+ * line input requests in the given window. It does not affect a pending line
+ * input request.
+ *
+ * <note><para>
+ * This distinction makes life easier for interpreters that set up UI
+ * callbacks only at the start of input.
+ * </para></note>
+ *
+ * A library may not support this feature; if it does, it may not support all
+ * special keys as terminators. (Some keystrokes are reserved for OS or
+ * interpreter control.) You can test for this with %gestalt_LineTerminators and
+ * %gestalt_LineTerminatorKey.
+ */
+void
+glk_set_terminators_line_event(winid_t win, glui32 *keycodes, glui32 count)
+{
+ VALID_WINDOW(win, return);
+
+ g_slist_free(win->extra_line_terminators);
+ win->extra_line_terminators = NULL;
+
+ if(keycodes == NULL || count == 0)
+ return;
+
+ int i;
+ for(i = 0; i < count; i++)
+ {
+ unsigned key = keycode_to_gdk_keyval(keycodes[i]);
+ if(is_valid_line_terminator(keycodes[i]))
+ win->extra_line_terminators = g_slist_prepend(win->extra_line_terminators, GUINT_TO_POINTER(key));
+ else
+ WARNING_S("Ignoring invalid line terminator", gdk_keyval_name(key));
+ }
+}