Files
squeekboard/eekboard/eekboard-context-service.c
Dorota Czaplejewicz 92c9572ac2 services: Split out layout management from EekboardContextService
Layout management was pointlessly bound with the EekboardContextService with inheritance. Splitting it out will make it easier to further break apart layout state management, settings, and input method in the future.
2020-01-11 15:33:26 +00:00

401 lines
13 KiB
C

/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:eekboard-context-service
* @short_description: base server implementation of eekboard input
* context service
*
* The #EekboardService class provides a base server side
* implementation of eekboard input context service.
*/
#include "config.h"
#include <fcntl.h>
#include <stdio.h>
#define _XOPEN_SOURCE 500
#include <string.h>
#include <sys/mman.h>
#include <sys/random.h> // TODO: this is Linux-specific
#include <xkbcommon/xkbcommon.h>
#include <gio/gio.h>
#include "wayland.h"
#include "eek/eek-xml-layout.h"
#include "src/server-context-service.h"
#include "eekboard/eekboard-context-service.h"
enum {
PROP_0, // Magic: without this, keyboard is not useable in g_object_notify
PROP_KEYBOARD,
PROP_LAST
};
enum {
DESTROYED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0, };
#define EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServicePrivate))
struct _EekboardContextServicePrivate {
LevelKeyboard *keyboard; // currently used keyboard
GHashTable *keyboard_hash; // a table of available keyboards, per layout
char *overlay;
GSettings *settings; // Owned reference
uint32_t hint;
uint32_t purpose;
// Maybe TODO: it's used only for fetching layout type.
// Maybe let UI push the type to this structure?
ServerContextService *ui; // unowned reference
};
G_DEFINE_TYPE_WITH_PRIVATE (EekboardContextService, eekboard_context_service, G_TYPE_OBJECT);
static LevelKeyboard *
eekboard_context_service_real_create_keyboard (EekboardContextService *self,
const gchar *keyboard_type,
enum squeek_arrangement_kind t)
{
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard(keyboard_type, self, t);
if (!keyboard) {
g_error("Failed to create a keyboard");
}
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
g_error("No context created");
}
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, keymap_str,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap)
g_error("Bad keymap:\n%s", keymap_str);
xkb_context_unref(context);
keyboard->keymap = keymap;
keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
keyboard->keymap_len = strlen(keymap_str) + 1;
g_autofree char *path = strdup("/eek_keymap-XXXXXX");
char *r = &path[strlen(path) - 6];
getrandom(r, 6, GRND_NONBLOCK);
for (unsigned i = 0; i < 6; i++) {
r[i] = (r[i] & 0b1111111) | 0b1000000; // A-z
r[i] = r[i] > 'z' ? '?' : r[i]; // The randomizer doesn't need to be good...
}
int keymap_fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
if (keymap_fd < 0) {
g_error("Failed to set up keymap fd");
}
keyboard->keymap_fd = keymap_fd;
shm_unlink(path);
if (ftruncate(keymap_fd, (off_t)keyboard->keymap_len)) {
g_error("Failed to increase keymap fd size");
}
char *ptr = mmap(NULL, keyboard->keymap_len, PROT_WRITE, MAP_SHARED,
keymap_fd, 0);
if ((void*)ptr == (void*)-1) {
g_error("Failed to set up mmap");
}
strncpy(ptr, keymap_str, keyboard->keymap_len);
munmap(ptr, keyboard->keymap_len);
return keyboard;
}
static void
eekboard_context_service_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
(void)value;
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
eekboard_context_service_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE(object);
switch (prop_id) {
case PROP_KEYBOARD:
g_value_set_object (value, context->priv->keyboard);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
eekboard_context_service_dispose (GObject *object)
{
EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE(object);
if (context->priv->keyboard_hash) {
g_hash_table_destroy (context->priv->keyboard_hash);
context->priv->keyboard_hash = NULL;
}
G_OBJECT_CLASS (eekboard_context_service_parent_class)->
dispose (object);
}
static void
settings_get_layout(GSettings *settings, char **type, char **layout)
{
GVariant *inputs = g_settings_get_value(settings, "sources");
// current layout is always first
g_variant_get_child(inputs, 0, "(ss)", type, layout);
}
void
eekboard_context_service_update_layout(EekboardContextService *context, enum squeek_arrangement_kind t)
{
g_autofree gchar *keyboard_layout = NULL;
if (context->priv->overlay) {
keyboard_layout = g_strdup(context->priv->overlay);
} else {
g_autofree gchar *keyboard_type = NULL;
settings_get_layout(context->priv->settings,
&keyboard_type, &keyboard_layout);
}
if (!keyboard_layout) {
keyboard_layout = g_strdup("us");
}
EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context);
switch (priv->purpose) {
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER:
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE:
keyboard_layout = g_strdup("number");
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL:
keyboard_layout = g_strdup("terminal");
break;
default:
;
}
// generic part follows
LevelKeyboard *keyboard = eekboard_context_service_real_create_keyboard(context, keyboard_layout, t);
// set as current
LevelKeyboard *previous_keyboard = context->priv->keyboard;
context->priv->keyboard = keyboard;
// The keymap will get set even if the window is hidden.
// It's not perfect,
// but simpler than adding a check in the window showing procedure
eekboard_context_service_set_keymap(context, keyboard);
g_object_notify (G_OBJECT(context), "keyboard");
// replacing the keyboard above will cause the previous keyboard to get destroyed from the UI side (eek_gtk_keyboard_dispose)
if (previous_keyboard) {
level_keyboard_free(previous_keyboard);
}
}
static void update_layout_and_type(EekboardContextService *context) {
EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context);
enum squeek_arrangement_kind layout_kind = ARRANGEMENT_KIND_BASE;
if (priv->ui) {
layout_kind = server_context_service_get_layout_type(priv->ui);
}
eekboard_context_service_update_layout(context, layout_kind);
}
static gboolean
settings_handle_layout_changed(GSettings *s,
gpointer keys, gint n_keys,
gpointer user_data) {
(void)s;
(void)keys;
(void)n_keys;
EekboardContextService *context = user_data;
g_free(context->priv->overlay);
context->priv->overlay = NULL;
update_layout_and_type(context);
return TRUE;
}
static void
eekboard_context_service_constructed (GObject *object)
{
EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE (object);
context->virtual_keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
squeek_wayland->virtual_keyboard_manager,
squeek_wayland->seat);
if (!context->virtual_keyboard) {
g_error("Programmer error: Failed to receive a virtual keyboard instance");
}
update_layout_and_type(context);
}
static void
eekboard_context_service_class_init (EekboardContextServiceClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
gobject_class->constructed = eekboard_context_service_constructed;
gobject_class->set_property = eekboard_context_service_set_property;
gobject_class->get_property = eekboard_context_service_get_property;
gobject_class->dispose = eekboard_context_service_dispose;
/**
* EekboardContextService::destroyed:
* @context: an #EekboardContextService
*
* Emitted when @context is destroyed.
*/
signals[DESTROYED] =
g_signal_new (I_("destroyed"),
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(EekboardContextServiceClass, destroyed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* EekboardContextService:keyboard:
*
* An #EekKeyboard currently active in this context.
*/
pspec = g_param_spec_pointer("keyboard",
"Keyboard",
"Keyboard",
G_PARAM_READABLE);
g_object_class_install_property (gobject_class,
PROP_KEYBOARD,
pspec);
}
static void
eekboard_context_service_init (EekboardContextService *self)
{
self->priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(self);
self->priv->keyboard_hash =
g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify)g_object_unref);
self->priv->settings = g_settings_new ("org.gnome.desktop.input-sources");
gulong conn_id = g_signal_connect(self->priv->settings, "change-event",
G_CALLBACK(settings_handle_layout_changed),
self);
if (conn_id == 0) {
g_warning ("Could not connect to gsettings updates, layout"
" changing unavailable");
}
self->priv->overlay = NULL;
}
/**
* eekboard_context_service_destroy:
* @context: an #EekboardContextService
*
* Destroy @context.
*/
void
eekboard_context_service_destroy (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
g_free(context->priv->overlay);
g_signal_emit (context, signals[DESTROYED], 0);
}
/**
* eekboard_context_service_get_keyboard:
* @context: an #EekboardContextService
*
* Get keyboard currently active in @context.
* Returns: (transfer none): an #EekKeyboard
*/
LevelKeyboard *
eekboard_context_service_get_keyboard (EekboardContextService *context)
{
return context->priv->keyboard;
}
void eekboard_context_service_set_keymap(EekboardContextService *context,
const LevelKeyboard *keyboard)
{
zwp_virtual_keyboard_v1_keymap(context->virtual_keyboard,
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keyboard->keymap_fd, keyboard->keymap_len);
}
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
uint32_t hint, uint32_t purpose)
{
EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context);
if (priv->hint != hint || priv->purpose != purpose) {
priv->hint = hint;
priv->purpose = purpose;
update_layout_and_type(context);
}
}
void
eekboard_context_service_set_overlay(EekboardContextService *context, const char* name) {
context->priv->overlay = g_strdup(name);
update_layout_and_type(context);
}
const char*
eekboard_context_service_get_overlay(EekboardContextService *context) {
return context->priv->overlay;
}
EekboardContextService *eekboard_context_service_new()
{
return g_object_new (EEKBOARD_TYPE_CONTEXT_SERVICE, NULL);
}