Compare commits
	
		
			14 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9ce2cf254b | |||
| 6d7360a230 | |||
| 33039e65cb | |||
| 4007754de9 | |||
| 6abaa36db8 | |||
| 710509a671 | |||
| 16d7fcae7c | |||
| e504154571 | |||
| b19938da01 | |||
| b409df15bb | |||
| 7dd2866b17 | |||
| f6fc6c83dc | |||
| fa5c7c63d9 | |||
| 1093e32325 | 
@ -45,7 +45,7 @@
 | 
				
			|||||||
typedef struct _EekGtkKeyboardPrivate
 | 
					typedef struct _EekGtkKeyboardPrivate
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    EekRenderer *renderer; // owned, nullable
 | 
					    EekRenderer *renderer; // owned, nullable
 | 
				
			||||||
    EekboardContextService *eekboard_context; // unowned reference
 | 
					    LayoutHolder *eekboard_context; // unowned reference
 | 
				
			||||||
    struct submission *submission; // unowned reference
 | 
					    struct submission *submission; // unowned reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct squeek_layout_state *layout; // unowned
 | 
					    struct squeek_layout_state *layout; // unowned
 | 
				
			||||||
@ -124,9 +124,10 @@ eek_gtk_keyboard_real_size_allocate (GtkWidget     *self,
 | 
				
			|||||||
                (uint32_t)(allocation->width - allocation->x) * scale,
 | 
					                (uint32_t)(allocation->width - allocation->x) * scale,
 | 
				
			||||||
                (uint32_t)(allocation->height - allocation->y) * scale);
 | 
					                (uint32_t)(allocation->height - allocation->y) * scale);
 | 
				
			||||||
    if (priv->layout->arrangement != new_type) {
 | 
					    if (priv->layout->arrangement != new_type) {
 | 
				
			||||||
        priv->layout->arrangement = new_type;
 | 
					        struct squeek_layout_state layout = *priv->layout;
 | 
				
			||||||
 | 
					        layout.arrangement = new_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        eekboard_context_service_use_layout(priv->eekboard_context, priv->layout);
 | 
					        eek_layout_holder_use_layout(priv->eekboard_context, &layout);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (priv->renderer)
 | 
					    if (priv->renderer)
 | 
				
			||||||
@ -158,7 +159,7 @@ static void drag(EekGtkKeyboard *self,
 | 
				
			|||||||
    if (!priv->keyboard) {
 | 
					    if (!priv->keyboard) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    squeek_layout_drag(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout,
 | 
					    squeek_layout_drag(eek_layout_holder_get_keyboard(priv->eekboard_context)->layout,
 | 
				
			||||||
                       priv->submission,
 | 
					                       priv->submission,
 | 
				
			||||||
                       x, y, eek_renderer_get_transformation(priv->renderer), time,
 | 
					                       x, y, eek_renderer_get_transformation(priv->renderer), time,
 | 
				
			||||||
                       priv->eekboard_context, self);
 | 
					                       priv->eekboard_context, self);
 | 
				
			||||||
@ -170,7 +171,7 @@ static void release(EekGtkKeyboard *self, guint32 time)
 | 
				
			|||||||
    if (!priv->keyboard) {
 | 
					    if (!priv->keyboard) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    squeek_layout_release(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout,
 | 
					    squeek_layout_release(eek_layout_holder_get_keyboard(priv->eekboard_context)->layout,
 | 
				
			||||||
                          priv->submission,
 | 
					                          priv->submission,
 | 
				
			||||||
                          eek_renderer_get_transformation(priv->renderer), time,
 | 
					                          eek_renderer_get_transformation(priv->renderer), time,
 | 
				
			||||||
                          priv->eekboard_context, self);
 | 
					                          priv->eekboard_context, self);
 | 
				
			||||||
@ -343,7 +344,7 @@ on_notify_keyboard (GObject              *object,
 | 
				
			|||||||
                    EekGtkKeyboard *self) {
 | 
					                    EekGtkKeyboard *self) {
 | 
				
			||||||
    (void)spec;
 | 
					    (void)spec;
 | 
				
			||||||
    EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (self);
 | 
					    EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (self);
 | 
				
			||||||
    priv->keyboard = eekboard_context_service_get_keyboard(EEKBOARD_CONTEXT_SERVICE(object));
 | 
					    priv->keyboard = eek_layout_holder_get_keyboard(LAYOUT_HOLDER(object));
 | 
				
			||||||
    if (priv->renderer) {
 | 
					    if (priv->renderer) {
 | 
				
			||||||
        eek_renderer_free(priv->renderer);
 | 
					        eek_renderer_free(priv->renderer);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -356,7 +357,7 @@ on_notify_keyboard (GObject              *object,
 | 
				
			|||||||
 * Returns: a #GtkWidget
 | 
					 * Returns: a #GtkWidget
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
GtkWidget *
 | 
					GtkWidget *
 | 
				
			||||||
eek_gtk_keyboard_new (EekboardContextService *eekservice,
 | 
					eek_gtk_keyboard_new (LayoutHolder *eekservice,
 | 
				
			||||||
                      struct submission *submission,
 | 
					                      struct submission *submission,
 | 
				
			||||||
                      struct squeek_layout_state *layout)
 | 
					                      struct squeek_layout_state *layout)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,7 @@ struct _EekGtkKeyboardClass
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GType      eek_gtk_keyboard_get_type  (void) G_GNUC_CONST;
 | 
					GType      eek_gtk_keyboard_get_type  (void) G_GNUC_CONST;
 | 
				
			||||||
GtkWidget *eek_gtk_keyboard_new       (EekboardContextService *eekservice, struct submission *submission, struct squeek_layout_state *layout);
 | 
					GtkWidget *eek_gtk_keyboard_new       (LayoutHolder *eekservice, struct submission *submission, struct squeek_layout_state *layout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
G_END_DECLS
 | 
					G_END_DECLS
 | 
				
			||||||
#endif  /* EEK_GTK_KEYBOARD_H */
 | 
					#endif  /* EEK_GTK_KEYBOARD_H */
 | 
				
			||||||
 | 
				
			|||||||
@ -37,7 +37,7 @@ G_BEGIN_DECLS
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
typedef struct _EekBounds EekBounds;
 | 
					typedef struct _EekBounds EekBounds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct _EekboardContextService EekboardContextService;
 | 
					typedef struct _LayoutHolder LayoutHolder;
 | 
				
			||||||
typedef struct _ServerContextService ServerContextService;
 | 
					typedef struct _ServerContextService ServerContextService;
 | 
				
			||||||
typedef struct _LevelKeyboard LevelKeyboard;
 | 
					typedef struct _LevelKeyboard LevelKeyboard;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -35,28 +35,17 @@ enum {
 | 
				
			|||||||
    PROP_LAST
 | 
					    PROP_LAST
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum {
 | 
					#define LAYOUT_HOLDER_GET_PRIVATE(obj)                       \
 | 
				
			||||||
    DESTROYED,
 | 
					    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEKBOARD_TYPE_LAYOUT_HOLDER, LayoutHolderPrivate))
 | 
				
			||||||
    LAST_SIGNAL
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
static guint signals[LAST_SIGNAL] = { 0, };
 | 
					struct _LayoutHolderPrivate {
 | 
				
			||||||
 | 
					 | 
				
			||||||
#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
 | 
					    LevelKeyboard *keyboard; // currently used keyboard
 | 
				
			||||||
    GSettings *settings; // Owned reference
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Maybe TODO: it's used only for fetching layout type.
 | 
					 | 
				
			||||||
    // Maybe let UI push the type to this structure?
 | 
					 | 
				
			||||||
    ServerContextService *ui; // unowned reference
 | 
					 | 
				
			||||||
    /// Needed for keymap changes after keyboard updates
 | 
					    /// Needed for keymap changes after keyboard updates
 | 
				
			||||||
    struct submission *submission; // unowned
 | 
					    struct submission *submission; // unowned
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
G_DEFINE_TYPE_WITH_PRIVATE (EekboardContextService, eekboard_context_service, G_TYPE_OBJECT);
 | 
					G_DEFINE_TYPE_WITH_PRIVATE (LayoutHolder, layout_holder, G_TYPE_OBJECT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
eekboard_context_service_set_property (GObject      *object,
 | 
					eekboard_context_service_set_property (GObject      *object,
 | 
				
			||||||
@ -73,12 +62,12 @@ eekboard_context_service_set_property (GObject      *object,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
eekboard_context_service_get_property (GObject    *object,
 | 
					layout_holder_get_property (GObject    *object,
 | 
				
			||||||
                                       guint       prop_id,
 | 
					                                       guint       prop_id,
 | 
				
			||||||
                                       GValue     *value,
 | 
					                                       GValue     *value,
 | 
				
			||||||
                                       GParamSpec *pspec)
 | 
					                                       GParamSpec *pspec)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE(object);
 | 
					    LayoutHolder *context = LAYOUT_HOLDER(object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (prop_id) {
 | 
					    switch (prop_id) {
 | 
				
			||||||
    case PROP_KEYBOARD:
 | 
					    case PROP_KEYBOARD:
 | 
				
			||||||
@ -90,13 +79,6 @@ eekboard_context_service_get_property (GObject    *object,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
eekboard_context_service_dispose (GObject *object)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    G_OBJECT_CLASS (eekboard_context_service_parent_class)->
 | 
					 | 
				
			||||||
        dispose (object);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
settings_get_layout(GSettings *settings, char **type, char **layout)
 | 
					settings_get_layout(GSettings *settings, char **type, char **layout)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -116,7 +98,8 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *state) {
 | 
					eek_layout_holder_use_layout(LayoutHolder *context, struct squeek_layout_state *state) {
 | 
				
			||||||
 | 
					    *context->layout = *state;
 | 
				
			||||||
    gchar *layout_name = state->overlay_name;
 | 
					    gchar *layout_name = state->overlay_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (layout_name == NULL) {
 | 
					    if (layout_name == NULL) {
 | 
				
			||||||
@ -160,73 +143,22 @@ eekboard_context_service_use_layout(EekboardContextService *context, struct sque
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void eekboard_context_service_update_settings_layout(EekboardContextService *context) {
 | 
					static void
 | 
				
			||||||
    g_autofree gchar *keyboard_layout = NULL;
 | 
					layout_holder_init (LayoutHolder *self) {
 | 
				
			||||||
    g_autofree gchar *keyboard_type = NULL;
 | 
					    self->priv = LAYOUT_HOLDER_GET_PRIVATE(self);
 | 
				
			||||||
    settings_get_layout(context->priv->settings,
 | 
					 | 
				
			||||||
                        &keyboard_type, &keyboard_layout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (g_strcmp0(context->layout->layout_name, keyboard_layout) != 0 || context->layout->overlay_name) {
 | 
					 | 
				
			||||||
        g_free(context->layout->overlay_name);
 | 
					 | 
				
			||||||
        context->layout->overlay_name = NULL;
 | 
					 | 
				
			||||||
        if (keyboard_layout) {
 | 
					 | 
				
			||||||
            g_free(context->layout->layout_name);
 | 
					 | 
				
			||||||
            context->layout->layout_name = g_strdup(keyboard_layout);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // This must actually update the UI.
 | 
					 | 
				
			||||||
        eekboard_context_service_use_layout(context, context->layout);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
    eekboard_context_service_update_settings_layout(context);
 | 
					 | 
				
			||||||
    return TRUE;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
eekboard_context_service_constructed (GObject *object)
 | 
					layout_holder_class_init (LayoutHolderClass *klass)
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    (void)object;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
eekboard_context_service_class_init (EekboardContextServiceClass *klass)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 | 
					    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 | 
				
			||||||
    GParamSpec *pspec;
 | 
					    GParamSpec *pspec;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    gobject_class->constructed = eekboard_context_service_constructed;
 | 
					 | 
				
			||||||
    gobject_class->set_property = eekboard_context_service_set_property;
 | 
					    gobject_class->set_property = eekboard_context_service_set_property;
 | 
				
			||||||
    gobject_class->get_property = eekboard_context_service_get_property;
 | 
					    gobject_class->get_property = layout_holder_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 #LevelKeyboard currently active in this context.
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * An #EekKeyboard currently active in this context.
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    pspec = g_param_spec_pointer("keyboard",
 | 
					    pspec = g_param_spec_pointer("keyboard",
 | 
				
			||||||
                                 "Keyboard",
 | 
					                                 "Keyboard",
 | 
				
			||||||
@ -237,10 +169,91 @@ eekboard_context_service_class_init (EekboardContextServiceClass *klass)
 | 
				
			|||||||
                                     pspec);
 | 
					                                     pspec);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					/**
 | 
				
			||||||
eekboard_context_service_init (EekboardContextService *self)
 | 
					 * Get keyboard currently active in @context.
 | 
				
			||||||
 | 
					 * Returns: (transfer none): a LevelKeyboard
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					LevelKeyboard *
 | 
				
			||||||
 | 
					eek_layout_holder_get_keyboard (LayoutHolder *context)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    self->priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(self);
 | 
					    return context->priv->keyboard;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void eekboard_context_service_set_hint_purpose(LayoutHolder *context,
 | 
				
			||||||
 | 
					                                               uint32_t hint, uint32_t purpose)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (context->layout->hint != hint || context->layout->purpose != purpose) {
 | 
				
			||||||
 | 
					        context->layout->hint = hint;
 | 
				
			||||||
 | 
					        context->layout->purpose = purpose;
 | 
				
			||||||
 | 
					        eek_layout_holder_use_layout(context, context->layout);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					eekboard_context_service_set_overlay(LayoutHolder *context, const char* name) {
 | 
				
			||||||
 | 
					    if (g_strcmp0(context->layout->overlay_name, name)) {
 | 
				
			||||||
 | 
					        g_free(context->layout->overlay_name);
 | 
				
			||||||
 | 
					        context->layout->overlay_name = g_strdup(name);
 | 
				
			||||||
 | 
					        eek_layout_holder_use_layout(context, context->layout);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char*
 | 
				
			||||||
 | 
					eekboard_context_service_get_overlay(LayoutHolder *context) {
 | 
				
			||||||
 | 
					    return context->layout->overlay_name;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LayoutHolder *eek_layout_holder_new(struct squeek_layout_state *state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    LayoutHolder *context = g_object_new (EEKBOARD_TYPE_LAYOUT_HOLDER, NULL);
 | 
				
			||||||
 | 
					    context->layout = state;
 | 
				
			||||||
 | 
					    eek_layout_holder_use_layout(context, context->layout);
 | 
				
			||||||
 | 
					    return context;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void eek_layout_holder_set_submission(LayoutHolder *context, struct submission *submission) {
 | 
				
			||||||
 | 
					    context->priv->submission = submission;
 | 
				
			||||||
 | 
					    if (context->priv->submission) {
 | 
				
			||||||
 | 
					        submission_set_keyboard(context->priv->submission, context->priv->keyboard);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void settings_update_layout(struct gsettings_tracker *self) {
 | 
				
			||||||
 | 
					    // The layout in the param must be the same layout as held by context.
 | 
				
			||||||
 | 
					    g_autofree gchar *keyboard_layout = NULL;
 | 
				
			||||||
 | 
					    g_autofree gchar *keyboard_type = NULL;
 | 
				
			||||||
 | 
					    settings_get_layout(self->gsettings,
 | 
				
			||||||
 | 
					                        &keyboard_type, &keyboard_layout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (g_strcmp0(self->layout->layout_name, keyboard_layout) != 0 || self->layout->overlay_name) {
 | 
				
			||||||
 | 
					        g_free(self->layout->overlay_name);
 | 
				
			||||||
 | 
					        self->layout->overlay_name = NULL;
 | 
				
			||||||
 | 
					        if (keyboard_layout) {
 | 
				
			||||||
 | 
					            g_free(self->layout->layout_name);
 | 
				
			||||||
 | 
					            self->layout->layout_name = g_strdup(keyboard_layout);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // This must actually update the UI.
 | 
				
			||||||
 | 
					        eek_layout_holder_use_layout(self->context, self->layout);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static gboolean
 | 
				
			||||||
 | 
					handle_layout_changed(GSettings *s,
 | 
				
			||||||
 | 
					                               gpointer keys, gint n_keys,
 | 
				
			||||||
 | 
					                               gpointer user_data) {
 | 
				
			||||||
 | 
					    (void)s;
 | 
				
			||||||
 | 
					    (void)keys;
 | 
				
			||||||
 | 
					    (void)n_keys;
 | 
				
			||||||
 | 
					    struct gsettings_tracker *self = user_data;
 | 
				
			||||||
 | 
					    settings_update_layout(self);
 | 
				
			||||||
 | 
					    return TRUE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void eek_gsettings_tracker_init(struct gsettings_tracker *tracker, LayoutHolder *context, struct squeek_layout_state *layout)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    tracker->layout = layout;
 | 
				
			||||||
 | 
					    tracker->context = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const char *schema_name = "org.gnome.desktop.input-sources";
 | 
					    const char *schema_name = "org.gnome.desktop.input-sources";
 | 
				
			||||||
    GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default();
 | 
					    GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default();
 | 
				
			||||||
    if (ssrc) {
 | 
					    if (ssrc) {
 | 
				
			||||||
@ -250,10 +263,10 @@ eekboard_context_service_init (EekboardContextService *self)
 | 
				
			|||||||
        if (schema) {
 | 
					        if (schema) {
 | 
				
			||||||
            // Not referencing the found schema directly,
 | 
					            // Not referencing the found schema directly,
 | 
				
			||||||
            // because it's not clear how...
 | 
					            // because it's not clear how...
 | 
				
			||||||
            self->priv->settings = g_settings_new (schema_name);
 | 
					            tracker->gsettings = g_settings_new (schema_name);
 | 
				
			||||||
            gulong conn_id = g_signal_connect(self->priv->settings, "change-event",
 | 
					            gulong conn_id = g_signal_connect(tracker->gsettings, "change-event",
 | 
				
			||||||
                                              G_CALLBACK(settings_handle_layout_changed),
 | 
					                                              G_CALLBACK(handle_layout_changed),
 | 
				
			||||||
                                              self);
 | 
					                                              tracker);
 | 
				
			||||||
            if (conn_id == 0) {
 | 
					            if (conn_id == 0) {
 | 
				
			||||||
                g_warning ("Could not connect to gsettings updates, "
 | 
					                g_warning ("Could not connect to gsettings updates, "
 | 
				
			||||||
                           "automatic layout changing unavailable");
 | 
					                           "automatic layout changing unavailable");
 | 
				
			||||||
@ -265,74 +278,6 @@ eekboard_context_service_init (EekboardContextService *self)
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        g_warning("No gsettings schemas installed. Layout switching unavailable.");
 | 
					        g_warning("No gsettings schemas installed. Layout switching unavailable.");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					    settings_update_layout(tracker);
 | 
				
			||||||
 * 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_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_hint_purpose(EekboardContextService *context,
 | 
					 | 
				
			||||||
                                               uint32_t hint, uint32_t purpose)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if (context->layout->hint != hint || context->layout->purpose != purpose) {
 | 
					 | 
				
			||||||
        context->layout->hint = hint;
 | 
					 | 
				
			||||||
        context->layout->purpose = purpose;
 | 
					 | 
				
			||||||
        eekboard_context_service_use_layout(context, context->layout);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void
 | 
					 | 
				
			||||||
eekboard_context_service_set_overlay(EekboardContextService *context, const char* name) {
 | 
					 | 
				
			||||||
    if (g_strcmp0(context->layout->overlay_name, name)) {
 | 
					 | 
				
			||||||
        g_free(context->layout->overlay_name);
 | 
					 | 
				
			||||||
        context->layout->overlay_name = g_strdup(name);
 | 
					 | 
				
			||||||
        eekboard_context_service_use_layout(context, context->layout);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const char*
 | 
					 | 
				
			||||||
eekboard_context_service_get_overlay(EekboardContextService *context) {
 | 
					 | 
				
			||||||
    return context->layout->overlay_name;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EekboardContextService *eekboard_context_service_new(struct squeek_layout_state *state)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    EekboardContextService *context = g_object_new (EEKBOARD_TYPE_CONTEXT_SERVICE, NULL);
 | 
					 | 
				
			||||||
    context->layout = state;
 | 
					 | 
				
			||||||
    eekboard_context_service_update_settings_layout(context);
 | 
					 | 
				
			||||||
    eekboard_context_service_use_layout(context, context->layout);
 | 
					 | 
				
			||||||
    return context;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission) {
 | 
					 | 
				
			||||||
    context->priv->submission = submission;
 | 
					 | 
				
			||||||
    if (context->priv->submission) {
 | 
					 | 
				
			||||||
        submission_set_keyboard(context->priv->submission, context->priv->keyboard);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui) {
 | 
					 | 
				
			||||||
    context->priv->ui = ui;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -30,71 +30,60 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
G_BEGIN_DECLS
 | 
					G_BEGIN_DECLS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define EEKBOARD_CONTEXT_SERVICE_PATH "/org/fedorahosted/Eekboard/Context_%d"
 | 
					#define EEKBOARD_TYPE_LAYOUT_HOLDER (layout_holder_get_type())
 | 
				
			||||||
#define EEKBOARD_CONTEXT_SERVICE_INTERFACE "org.fedorahosted.Eekboard.Context"
 | 
					#define LAYOUT_HOLDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_LAYOUT_HOLDER, LayoutHolder))
 | 
				
			||||||
 | 
					 | 
				
			||||||
#define EEKBOARD_TYPE_CONTEXT_SERVICE (eekboard_context_service_get_type())
 | 
					 | 
				
			||||||
#define EEKBOARD_CONTEXT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextService))
 | 
					 | 
				
			||||||
#define EEKBOARD_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServiceClass))
 | 
					#define EEKBOARD_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServiceClass))
 | 
				
			||||||
#define EEKBOARD_IS_CONTEXT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE))
 | 
					#define EEKBOARD_IS_CONTEXT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE))
 | 
				
			||||||
#define EEKBOARD_IS_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEKBOARD_TYPE_CONTEXT_SERVICE))
 | 
					#define EEKBOARD_IS_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEKBOARD_TYPE_CONTEXT_SERVICE))
 | 
				
			||||||
#define EEKBOARD_CONTEXT_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServiceClass))
 | 
					#define EEKBOARD_CONTEXT_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServiceClass))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct _EekboardContextServiceClass EekboardContextServiceClass;
 | 
					typedef struct _LayoutHolderClass LayoutHolderClass;
 | 
				
			||||||
typedef struct _EekboardContextServicePrivate EekboardContextServicePrivate;
 | 
					typedef struct _LayoutHolderPrivate LayoutHolderPrivate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * EekboardContextService:
 | 
					 * Handles layout state, and virtual-keyboard.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Handles layout state, gsettings, and virtual-keyboard.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * TODO: Restrict to managing keyboard layouts, and maybe button repeats,
 | 
					 * TODO: Restrict to managing keyboard layouts, and maybe button repeats,
 | 
				
			||||||
 * and the virtual keyboard protocol.
 | 
					 * and the virtual keyboard protocol.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * The #EekboardContextService structure contains only private data
 | 
					 | 
				
			||||||
 * and should only be accessed using the provided API.
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
struct _EekboardContextService {
 | 
					struct _LayoutHolder {
 | 
				
			||||||
    GObject parent;
 | 
					    GObject parent;
 | 
				
			||||||
    EekboardContextServicePrivate *priv;
 | 
					    LayoutHolderPrivate *priv;
 | 
				
			||||||
    struct squeek_layout_state *layout; // Unowned
 | 
					    struct squeek_layout_state *layout; // Unowned
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					struct _LayoutHolderClass {
 | 
				
			||||||
 * EekboardContextServiceClass:
 | 
					 | 
				
			||||||
 * @create_keyboard: virtual function for create a keyboard from string
 | 
					 | 
				
			||||||
 * @enabled: class handler for #EekboardContextService::enabled signal
 | 
					 | 
				
			||||||
 * @disabled: class handler for #EekboardContextService::disabled signal
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
struct _EekboardContextServiceClass {
 | 
					 | 
				
			||||||
    /*< private >*/
 | 
					    /*< private >*/
 | 
				
			||||||
    GObjectClass parent_class;
 | 
					    GObjectClass parent_class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /*< public >*/
 | 
					 | 
				
			||||||
    /* signals */
 | 
					 | 
				
			||||||
    void         (*destroyed)          (EekboardContextService *self);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /*< private >*/
 | 
					    /*< private >*/
 | 
				
			||||||
    /* padding */
 | 
					    /* padding */
 | 
				
			||||||
    gpointer pdummy[24];
 | 
					    gpointer pdummy[24];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GType         eekboard_context_service_get_type
 | 
					GType         layout_holder_get_type(void) G_GNUC_CONST;
 | 
				
			||||||
                                              (void) G_GNUC_CONST;
 | 
					 | 
				
			||||||
EekboardContextService *eekboard_context_service_new(struct squeek_layout_state *state);
 | 
					 | 
				
			||||||
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission);
 | 
					 | 
				
			||||||
void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui);
 | 
					 | 
				
			||||||
void          eekboard_context_service_destroy (EekboardContextService *context);
 | 
					 | 
				
			||||||
LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void eekboard_context_service_set_keymap(EekboardContextService *context,
 | 
					/// Handles gsettings os-level keyboard layout switches.
 | 
				
			||||||
 | 
					struct gsettings_tracker {
 | 
				
			||||||
 | 
					    GSettings *gsettings; // Owned reference
 | 
				
			||||||
 | 
					    LayoutHolder *context; // Unowned
 | 
				
			||||||
 | 
					    struct squeek_layout_state *layout; // Unowned
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void eek_gsettings_tracker_init(struct gsettings_tracker* tracker, LayoutHolder *context, struct squeek_layout_state *layout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LayoutHolder *eek_layout_holder_new(struct squeek_layout_state *state);
 | 
				
			||||||
 | 
					void eek_layout_holder_set_submission(LayoutHolder *context, struct submission *submission);
 | 
				
			||||||
 | 
					LevelKeyboard *eek_layout_holder_get_keyboard(LayoutHolder *context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void eekboard_context_service_set_keymap(LayoutHolder *context,
 | 
				
			||||||
                                         const LevelKeyboard *keyboard);
 | 
					                                         const LevelKeyboard *keyboard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
 | 
					void eekboard_context_service_set_hint_purpose(LayoutHolder *context,
 | 
				
			||||||
                                               uint32_t hint,
 | 
					                                               uint32_t hint,
 | 
				
			||||||
                                               uint32_t purpose);
 | 
					                                               uint32_t purpose);
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *layout);
 | 
					eek_layout_holder_use_layout(LayoutHolder *context, struct squeek_layout_state *layout);
 | 
				
			||||||
G_END_DECLS
 | 
					G_END_DECLS
 | 
				
			||||||
#endif  /* EEKBOARD_CONTEXT_SERVICE_H */
 | 
					#endif  /* EEKBOARD_CONTEXT_SERVICE_H */
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,7 @@ static const struct zwp_input_method_v2_listener input_method_listener = {
 | 
				
			|||||||
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
 | 
					struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
 | 
				
			||||||
                                  struct zwp_virtual_keyboard_manager_v1 *vkmanager,
 | 
					                                  struct zwp_virtual_keyboard_manager_v1 *vkmanager,
 | 
				
			||||||
                                  struct wl_seat *seat,
 | 
					                                  struct wl_seat *seat,
 | 
				
			||||||
                                  EekboardContextService *state) {
 | 
					                                  LayoutHolder *state) {
 | 
				
			||||||
    struct zwp_input_method_v2 *im = NULL;
 | 
					    struct zwp_input_method_v2 *im = NULL;
 | 
				
			||||||
    if (immanager) {
 | 
					    if (immanager) {
 | 
				
			||||||
        im = zwp_input_method_manager_v2_get_input_method(immanager, seat);
 | 
					        im = zwp_input_method_manager_v2_get_input_method(immanager, seat);
 | 
				
			||||||
 | 
				
			|||||||
@ -47,7 +47,7 @@ void squeek_layout_release(struct squeek_layout *layout,
 | 
				
			|||||||
                           struct submission *submission,
 | 
					                           struct submission *submission,
 | 
				
			||||||
                           struct transformation widget_to_layout,
 | 
					                           struct transformation widget_to_layout,
 | 
				
			||||||
                           uint32_t timestamp,
 | 
					                           uint32_t timestamp,
 | 
				
			||||||
                           EekboardContextService *manager,
 | 
					                           LayoutHolder *manager,
 | 
				
			||||||
                           EekGtkKeyboard *ui_keyboard);
 | 
					                           EekGtkKeyboard *ui_keyboard);
 | 
				
			||||||
void squeek_layout_release_all_only(struct squeek_layout *layout,
 | 
					void squeek_layout_release_all_only(struct squeek_layout *layout,
 | 
				
			||||||
                                    struct submission *submission,
 | 
					                                    struct submission *submission,
 | 
				
			||||||
@ -61,7 +61,7 @@ void squeek_layout_drag(struct squeek_layout *layout,
 | 
				
			|||||||
                        struct submission *submission,
 | 
					                        struct submission *submission,
 | 
				
			||||||
                        double x_widget, double y_widget,
 | 
					                        double x_widget, double y_widget,
 | 
				
			||||||
                        struct transformation widget_to_layout,
 | 
					                        struct transformation widget_to_layout,
 | 
				
			||||||
                        uint32_t timestamp, EekboardContextService *manager,
 | 
					                        uint32_t timestamp, LayoutHolder *manager,
 | 
				
			||||||
                        EekGtkKeyboard *ui_keyboard);
 | 
					                        EekGtkKeyboard *ui_keyboard);
 | 
				
			||||||
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t     *cr, struct submission *submission);
 | 
					void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t     *cr, struct submission *submission);
 | 
				
			||||||
void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t     *cr);
 | 
					void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t     *cr);
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ sources = [
 | 
				
			|||||||
  'dbus.c',
 | 
					  'dbus.c',
 | 
				
			||||||
  'imservice.c',
 | 
					  'imservice.c',
 | 
				
			||||||
  'server-context-service.c',
 | 
					  'server-context-service.c',
 | 
				
			||||||
 | 
					  'ui_manager.c',
 | 
				
			||||||
  'wayland.c',
 | 
					  'wayland.c',
 | 
				
			||||||
  '../eek/eek.c',
 | 
					  '../eek/eek.c',
 | 
				
			||||||
  '../eek/eek-element.c',
 | 
					  '../eek/eek-element.c',
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										152
									
								
								src/outputs.rs
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								src/outputs.rs
									
									
									
									
									
								
							@ -1,5 +1,10 @@
 | 
				
			|||||||
 | 
					/* Copyright (C) 2019-2020 Purism SPC
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: GPL-3.0+
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*! Managing Wayland outputs */
 | 
					/*! Managing Wayland outputs */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::cell::RefCell;
 | 
				
			||||||
use std::vec::Vec;
 | 
					use std::vec::Vec;
 | 
				
			||||||
use ::logging;
 | 
					use ::logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,7 +22,7 @@ pub mod c {
 | 
				
			|||||||
    // Defined in C
 | 
					    // Defined in C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[repr(transparent)]
 | 
					    #[repr(transparent)]
 | 
				
			||||||
    #[derive(Clone, PartialEq, Copy)]
 | 
					    #[derive(Clone, PartialEq, Copy, Hash)]
 | 
				
			||||||
    pub struct WlOutput(*const c_void);
 | 
					    pub struct WlOutput(*const c_void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[repr(C)]
 | 
					    #[repr(C)]
 | 
				
			||||||
@ -63,7 +68,7 @@ pub mod c {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Map to `wl_output.transform` values
 | 
					    /// Map to `wl_output.transform` values
 | 
				
			||||||
    #[derive(Clone)]
 | 
					    #[derive(Clone, PartialEq)]
 | 
				
			||||||
    pub enum Transform {
 | 
					    pub enum Transform {
 | 
				
			||||||
        Normal = 0,
 | 
					        Normal = 0,
 | 
				
			||||||
        Rotated90 = 1,
 | 
					        Rotated90 = 1,
 | 
				
			||||||
@ -103,7 +108,7 @@ pub mod c {
 | 
				
			|||||||
        ) -> i32;
 | 
					        ) -> i32;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    type COutputs = ::util::c::Wrapped<Outputs>;
 | 
					    pub type COutputs = ::util::c::Wrapped<Outputs>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// A stable reference to an output.
 | 
					    /// A stable reference to an output.
 | 
				
			||||||
    #[derive(Clone)]
 | 
					    #[derive(Clone)]
 | 
				
			||||||
@ -121,6 +126,10 @@ pub mod c {
 | 
				
			|||||||
            let outputs = outputs.borrow();
 | 
					            let outputs = outputs.borrow();
 | 
				
			||||||
            find_output(&outputs, self.wl_output.clone()).map(|o| o.current.clone())
 | 
					            find_output(&outputs, self.wl_output.clone()).map(|o| o.current.clone())
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn get_id(&self) -> OutputId {
 | 
				
			||||||
 | 
					            OutputId { wl_output: self.wl_output }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Defined in Rust
 | 
					    // Defined in Rust
 | 
				
			||||||
@ -129,7 +138,7 @@ pub mod c {
 | 
				
			|||||||
        outputs: COutputs,
 | 
					        outputs: COutputs,
 | 
				
			||||||
        wl_output: WlOutput,
 | 
					        wl_output: WlOutput,
 | 
				
			||||||
        _x: i32, _y: i32,
 | 
					        _x: i32, _y: i32,
 | 
				
			||||||
        _phys_width: i32, _phys_height: i32,
 | 
					        phys_width: i32, phys_height: i32,
 | 
				
			||||||
        _subpixel: i32,
 | 
					        _subpixel: i32,
 | 
				
			||||||
        _make: *const c_char, _model: *const c_char,
 | 
					        _make: *const c_char, _model: *const c_char,
 | 
				
			||||||
        transform: i32,
 | 
					        transform: i32,
 | 
				
			||||||
@ -145,8 +154,23 @@ pub mod c {
 | 
				
			|||||||
        let output_state: Option<&mut OutputState>
 | 
					        let output_state: Option<&mut OutputState>
 | 
				
			||||||
            = find_output_mut(&mut collection, wl_output)
 | 
					            = find_output_mut(&mut collection, wl_output)
 | 
				
			||||||
                .map(|o| &mut o.pending);
 | 
					                .map(|o| &mut o.pending);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match output_state {
 | 
					        match output_state {
 | 
				
			||||||
            Some(state) => { state.transform = Some(transform) },
 | 
					            Some(state) => {
 | 
				
			||||||
 | 
					                state.transform = Some(transform);
 | 
				
			||||||
 | 
					                state.phys_size = {
 | 
				
			||||||
 | 
					                    if (phys_width > 0) & (phys_height > 0) {
 | 
				
			||||||
 | 
					                        Some(SizeMM { width: phys_width, height: phys_height })
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        log_print!(
 | 
				
			||||||
 | 
					                            logging::Level::Surprise,
 | 
				
			||||||
 | 
					                            "Impossible physical dimensions: {}mm × {}mm",
 | 
				
			||||||
 | 
					                            phys_width, phys_height,
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        None
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            None => log_print!(
 | 
					            None => log_print!(
 | 
				
			||||||
                logging::Level::Warning,
 | 
					                logging::Level::Warning,
 | 
				
			||||||
                "Got geometry on unknown output",
 | 
					                "Got geometry on unknown output",
 | 
				
			||||||
@ -187,19 +211,27 @@ pub mod c {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extern fn outputs_handle_done(
 | 
					    extern fn outputs_handle_done(
 | 
				
			||||||
        outputs: COutputs,
 | 
					        outputs_raw: COutputs,
 | 
				
			||||||
        wl_output: WlOutput,
 | 
					        wl_output: WlOutput,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        let outputs = outputs.clone_ref();
 | 
					        let outputs = outputs_raw.clone_ref();
 | 
				
			||||||
        let mut collection = outputs.borrow_mut();
 | 
					        {
 | 
				
			||||||
        let output = find_output_mut(&mut collection, wl_output);
 | 
					            let mut collection = RefCell::borrow_mut(&outputs);
 | 
				
			||||||
        match output {
 | 
					            let output = find_output_mut(&mut collection, wl_output);
 | 
				
			||||||
            Some(output) => { output.current = output.pending.clone(); }
 | 
					            match output {
 | 
				
			||||||
            None => log_print!(
 | 
					                Some(output) => { output.current = output.pending.clone(); }
 | 
				
			||||||
                logging::Level::Warning,
 | 
					                None => log_print!(
 | 
				
			||||||
                "Got done on unknown output",
 | 
					                    logging::Level::Warning,
 | 
				
			||||||
            ),
 | 
					                    "Got done on unknown output",
 | 
				
			||||||
        };
 | 
					                ),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let collection = RefCell::borrow(&outputs);
 | 
				
			||||||
 | 
					        if let Some(ref cb) = &collection.update_cb {
 | 
				
			||||||
 | 
					            let mut cb = RefCell::borrow_mut(cb);
 | 
				
			||||||
 | 
					            let cb = Box::as_mut(&mut cb);
 | 
				
			||||||
 | 
					            cb(OutputHandle { wl_output, outputs: outputs_raw });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extern fn outputs_handle_scale(
 | 
					    extern fn outputs_handle_scale(
 | 
				
			||||||
@ -224,7 +256,10 @@ pub mod c {
 | 
				
			|||||||
    #[no_mangle]
 | 
					    #[no_mangle]
 | 
				
			||||||
    pub extern "C"
 | 
					    pub extern "C"
 | 
				
			||||||
    fn squeek_outputs_new() -> COutputs {
 | 
					    fn squeek_outputs_new() -> COutputs {
 | 
				
			||||||
        COutputs::new(Outputs { outputs: Vec::new() })
 | 
					        COutputs::new(Outputs {
 | 
				
			||||||
 | 
					            outputs: Vec::new(),
 | 
				
			||||||
 | 
					            update_cb: None,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[no_mangle]
 | 
					    #[no_mangle]
 | 
				
			||||||
@ -293,22 +328,29 @@ pub mod c {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Generic size
 | 
					/// Generic size
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
pub struct Size {
 | 
					pub struct Size {
 | 
				
			||||||
    pub width: u32,
 | 
					    pub width: u32,
 | 
				
			||||||
    pub height: u32,
 | 
					    pub height: u32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct SizeMM {
 | 
				
			||||||
 | 
					    pub width: i32,
 | 
				
			||||||
 | 
					    pub height: i32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// wl_output mode
 | 
					/// wl_output mode
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone, PartialEq)]
 | 
				
			||||||
struct Mode {
 | 
					struct Mode {
 | 
				
			||||||
    width: i32,
 | 
					    width: i32,
 | 
				
			||||||
    height: i32,
 | 
					    height: i32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone, PartialEq)]
 | 
				
			||||||
pub struct OutputState {
 | 
					pub struct OutputState {
 | 
				
			||||||
    current_mode: Option<Mode>,
 | 
					    current_mode: Option<Mode>,
 | 
				
			||||||
 | 
					    phys_size: Option<SizeMM>,
 | 
				
			||||||
    transform: Option<c::Transform>,
 | 
					    transform: Option<c::Transform>,
 | 
				
			||||||
    pub scale: i32,
 | 
					    pub scale: i32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -323,6 +365,7 @@ impl OutputState {
 | 
				
			|||||||
    fn uninitialized() -> OutputState {
 | 
					    fn uninitialized() -> OutputState {
 | 
				
			||||||
        OutputState {
 | 
					        OutputState {
 | 
				
			||||||
            current_mode: None,
 | 
					            current_mode: None,
 | 
				
			||||||
 | 
					            phys_size: None,
 | 
				
			||||||
            transform: None,
 | 
					            transform: None,
 | 
				
			||||||
            scale: 1,
 | 
					            scale: 1,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -334,6 +377,35 @@ impl OutputState {
 | 
				
			|||||||
            OutputState {
 | 
					            OutputState {
 | 
				
			||||||
                current_mode: Some(Mode { width, height } ),
 | 
					                current_mode: Some(Mode { width, height } ),
 | 
				
			||||||
                transform: Some(transform),
 | 
					                transform: Some(transform),
 | 
				
			||||||
 | 
					                phys_size: _,
 | 
				
			||||||
 | 
					                scale: _,
 | 
				
			||||||
 | 
					            } => Some(
 | 
				
			||||||
 | 
					                match transform {
 | 
				
			||||||
 | 
					                    Transform::Normal
 | 
				
			||||||
 | 
					                    | Transform::Rotated180
 | 
				
			||||||
 | 
					                    | Transform::Flipped
 | 
				
			||||||
 | 
					                    | Transform::FlippedRotated180 => Size {
 | 
				
			||||||
 | 
					                        width: *width as u32,
 | 
				
			||||||
 | 
					                        height: *height as u32,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    _ => Size {
 | 
				
			||||||
 | 
					                        width: *height as u32,
 | 
				
			||||||
 | 
					                        height: *width as u32,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /// Returns transformed dimensions
 | 
				
			||||||
 | 
					    pub fn get_phys_size(&self) -> Option<Size> {
 | 
				
			||||||
 | 
					        use self::c::Transform;
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            OutputState {
 | 
				
			||||||
 | 
					                current_mode: _,
 | 
				
			||||||
 | 
					                transform: Some(transform),
 | 
				
			||||||
 | 
					                phys_size: Some(SizeMM { width, height }),
 | 
				
			||||||
                scale: _,
 | 
					                scale: _,
 | 
				
			||||||
            } => Some(
 | 
					            } => Some(
 | 
				
			||||||
                match transform {
 | 
					                match transform {
 | 
				
			||||||
@ -355,12 +427,52 @@ impl OutputState {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A comparable ID of an output
 | 
				
			||||||
 | 
					#[derive(Clone, PartialEq, Hash)]
 | 
				
			||||||
 | 
					pub struct OutputId {
 | 
				
			||||||
 | 
					    // WlOutput is a unique pointer, so it will not repeat
 | 
				
			||||||
 | 
					    // even if there are multiple output managers.
 | 
				
			||||||
 | 
					    wl_output: c::WlOutput,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Output {
 | 
					pub struct Output {
 | 
				
			||||||
    output: c::WlOutput,
 | 
					    output: c::WlOutput,
 | 
				
			||||||
    pending: OutputState,
 | 
					    pending: OutputState,
 | 
				
			||||||
    current: OutputState,
 | 
					    current: OutputState,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The manager of all outputs.
 | 
				
			||||||
 | 
					// This is the target of several callbacks,
 | 
				
			||||||
 | 
					// so it should only be used with a stable place in memory, like `Rc<RefCell>`.
 | 
				
			||||||
 | 
					// It should not be instantiated externally or copied,
 | 
				
			||||||
 | 
					// or it will not receive those callbacks and be somewhat of an empty shell.
 | 
				
			||||||
 | 
					// It should be safe to use as long as the fields are not `pub`,
 | 
				
			||||||
 | 
					// and there's no `Clone`, and this module's API only ever gives out
 | 
				
			||||||
 | 
					// references wrapped in `Rc<RefCell>`.
 | 
				
			||||||
 | 
					// For perfectness, it would only ever give out immutable opaque references,
 | 
				
			||||||
 | 
					// but that's not practical at the moment.
 | 
				
			||||||
 | 
					// `mem::swap` could replace the value inside,
 | 
				
			||||||
 | 
					// but as long as the swap is atomic,
 | 
				
			||||||
 | 
					// that should not cause an inconsistent state.
 | 
				
			||||||
pub struct Outputs {
 | 
					pub struct Outputs {
 | 
				
			||||||
    outputs: Vec<Output>,
 | 
					    outputs: Vec<Output>,
 | 
				
			||||||
 | 
					    // The RefCell is here to let the function be called
 | 
				
			||||||
 | 
					    // while holding only a read-reference to `Outputs`.
 | 
				
			||||||
 | 
					    // Otherwise anything trying to get useful data from OutputHandle
 | 
				
			||||||
 | 
					    // will fail to acquire reference to Outputs.
 | 
				
			||||||
 | 
					    // TODO: Maybe pass only current state along with Outputs and Output hash.
 | 
				
			||||||
 | 
					    // The only reason a full OutputHandle is here
 | 
				
			||||||
 | 
					    // is to be able to track the right Output.
 | 
				
			||||||
 | 
					    update_cb: Option<RefCell<Box<dyn FnMut(c::OutputHandle)>>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Outputs {
 | 
				
			||||||
 | 
					    /// The function will get called whenever
 | 
				
			||||||
 | 
					    /// any output changes or is removed or created.
 | 
				
			||||||
 | 
					    /// If output handle doesn't return state, the output just went down.
 | 
				
			||||||
 | 
					    /// It cannot modify anything in Outputs.
 | 
				
			||||||
 | 
					    // FIXME: handle output destruction
 | 
				
			||||||
 | 
					    pub fn set_update_cb(&mut self, callback: Box<dyn FnMut(c::OutputHandle)>) {
 | 
				
			||||||
 | 
					        self.update_cb = Some(RefCell::new(callback));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ typedef struct _ServerContextServiceClass ServerContextServiceClass;
 | 
				
			|||||||
struct _ServerContextService {
 | 
					struct _ServerContextService {
 | 
				
			||||||
    GObject parent;
 | 
					    GObject parent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EekboardContextService *state; // unowned
 | 
					    LayoutHolder *state; // unowned
 | 
				
			||||||
    /// Needed for instantiating the widget
 | 
					    /// Needed for instantiating the widget
 | 
				
			||||||
    struct submission *submission; // unowned
 | 
					    struct submission *submission; // unowned
 | 
				
			||||||
    struct squeek_layout_state *layout;
 | 
					    struct squeek_layout_state *layout;
 | 
				
			||||||
@ -139,6 +139,7 @@ make_window (ServerContextService *context)
 | 
				
			|||||||
        NULL
 | 
					        NULL
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    squeek_uiman_set_surface(context->manager, context->window);
 | 
				
			||||||
    g_object_connect (context->window,
 | 
					    g_object_connect (context->window,
 | 
				
			||||||
                      "signal::destroy", G_CALLBACK(on_destroy), context,
 | 
					                      "signal::destroy", G_CALLBACK(on_destroy), context,
 | 
				
			||||||
                      "signal::map", G_CALLBACK(on_notify_map), context,
 | 
					                      "signal::map", G_CALLBACK(on_notify_map), context,
 | 
				
			||||||
@ -311,7 +312,7 @@ server_context_service_init (ServerContextService *state) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ServerContextService *
 | 
					ServerContextService *
 | 
				
			||||||
server_context_service_new (EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman)
 | 
					server_context_service_new (LayoutHolder *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
 | 
					    ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
 | 
				
			||||||
    ui->submission = submission;
 | 
					    ui->submission = submission;
 | 
				
			||||||
 | 
				
			|||||||
@ -37,7 +37,7 @@ typedef struct _ServerContextService ServerContextService;
 | 
				
			|||||||
GType         server_context_service_get_type
 | 
					GType         server_context_service_get_type
 | 
				
			||||||
                                              (void) G_GNUC_CONST;
 | 
					                                              (void) G_GNUC_CONST;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ServerContextService *server_context_service_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman);
 | 
					ServerContextService *server_context_service_new(LayoutHolder *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman);
 | 
				
			||||||
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
 | 
					enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
 | 
				
			||||||
void server_context_service_show_keyboard (ServerContextService *context);
 | 
					void server_context_service_show_keyboard (ServerContextService *context);
 | 
				
			||||||
void server_context_service_hide_keyboard (ServerContextService *context);
 | 
					void server_context_service_hide_keyboard (ServerContextService *context);
 | 
				
			||||||
 | 
				
			|||||||
@ -42,11 +42,12 @@
 | 
				
			|||||||
struct squeekboard {
 | 
					struct squeekboard {
 | 
				
			||||||
    struct squeek_wayland wayland; // Just hooks.
 | 
					    struct squeek_wayland wayland; // Just hooks.
 | 
				
			||||||
    DBusHandler *dbus_handler; // Controls visibility of the OSK.
 | 
					    DBusHandler *dbus_handler; // Controls visibility of the OSK.
 | 
				
			||||||
    EekboardContextService *settings_context; // Gsettings hooks.
 | 
					    LayoutHolder *layout_holder; // Currently used layout & keyboard.
 | 
				
			||||||
    ServerContextService *ui_context; // mess, includes the entire UI
 | 
					    ServerContextService *ui_context; // mess, includes the entire UI
 | 
				
			||||||
    struct submission *submission; // Wayland text input handling.
 | 
					    struct submission *submission; // Wayland text input handling.
 | 
				
			||||||
    struct squeek_layout_state layout_choice; // Currently wanted layout.
 | 
					    struct squeek_layout_state layout_choice; // Currently wanted layout.
 | 
				
			||||||
    struct ui_manager *ui_manager; // UI shape tracker/chooser. TODO: merge with layuot choice
 | 
					    struct ui_manager *ui_manager; // UI shape tracker/chooser. TODO: merge with layuot choice
 | 
				
			||||||
 | 
					    struct gsettings_tracker gsettings_tracker; // Gsettings handling.
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -203,10 +204,10 @@ main (int argc, char **argv)
 | 
				
			|||||||
        g_warning("Wayland input method interface not available");
 | 
					        g_warning("Wayland input method interface not available");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    instance.ui_manager = squeek_uiman_new();
 | 
					    instance.ui_manager = squeek_uiman_new(instance.wayland.outputs);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    instance.settings_context = eekboard_context_service_new(&instance.layout_choice);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance.layout_holder = eek_layout_holder_new(&instance.layout_choice);
 | 
				
			||||||
 | 
					    eek_gsettings_tracker_init(&instance.gsettings_tracker, instance.layout_holder, &instance.layout_choice);
 | 
				
			||||||
    // set up dbus
 | 
					    // set up dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: make dbus errors non-always-fatal
 | 
					    // TODO: make dbus errors non-always-fatal
 | 
				
			||||||
@ -279,12 +280,12 @@ main (int argc, char **argv)
 | 
				
			|||||||
    instance.submission = get_submission(instance.wayland.input_method_manager,
 | 
					    instance.submission = get_submission(instance.wayland.input_method_manager,
 | 
				
			||||||
                                         instance.wayland.virtual_keyboard_manager,
 | 
					                                         instance.wayland.virtual_keyboard_manager,
 | 
				
			||||||
                                         instance.wayland.seat,
 | 
					                                         instance.wayland.seat,
 | 
				
			||||||
                                         instance.settings_context);
 | 
					                                         instance.layout_holder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    eekboard_context_service_set_submission(instance.settings_context, instance.submission);
 | 
					    eek_layout_holder_set_submission(instance.layout_holder, instance.submission);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ServerContextService *ui_context = server_context_service_new(
 | 
					    ServerContextService *ui_context = server_context_service_new(
 | 
				
			||||||
                instance.settings_context,
 | 
					                instance.layout_holder,
 | 
				
			||||||
                instance.submission,
 | 
					                instance.submission,
 | 
				
			||||||
                &instance.layout_choice,
 | 
					                &instance.layout_choice,
 | 
				
			||||||
                instance.ui_manager);
 | 
					                instance.ui_manager);
 | 
				
			||||||
@ -299,7 +300,6 @@ main (int argc, char **argv)
 | 
				
			|||||||
    if (instance.dbus_handler) {
 | 
					    if (instance.dbus_handler) {
 | 
				
			||||||
        dbus_handler_set_ui_context(instance.dbus_handler, instance.ui_context);
 | 
					        dbus_handler_set_ui_context(instance.dbus_handler, instance.ui_context);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    eekboard_context_service_set_ui(instance.settings_context, instance.ui_context);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    session_register();
 | 
					    session_register();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,10 +10,10 @@ struct submission;
 | 
				
			|||||||
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
 | 
					struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
 | 
				
			||||||
                                  struct zwp_virtual_keyboard_manager_v1 *vkmanager,
 | 
					                                  struct zwp_virtual_keyboard_manager_v1 *vkmanager,
 | 
				
			||||||
                                  struct wl_seat *seat,
 | 
					                                  struct wl_seat *seat,
 | 
				
			||||||
                                  EekboardContextService *state);
 | 
					                                  LayoutHolder *state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Defined in Rust
 | 
					// Defined in Rust
 | 
				
			||||||
struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, EekboardContextService *state);
 | 
					struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, LayoutHolder *state);
 | 
				
			||||||
void submission_set_ui(struct submission *self, ServerContextService *ui_context);
 | 
					void submission_set_ui(struct submission *self, ServerContextService *ui_context);
 | 
				
			||||||
void submission_set_keyboard(struct submission *self, LevelKeyboard *keyboard);
 | 
					void submission_set_keyboard(struct submission *self, LevelKeyboard *keyboard);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								src/ui_manager.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/ui_manager.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					#include "eek/layersurface.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void squeek_manager_set_surface_height(PhoshLayerSurface *surface, uint32_t desired_height) {
 | 
				
			||||||
 | 
					    phosh_layer_surface_set_size(surface, 0,
 | 
				
			||||||
 | 
					                             (gint)desired_height);
 | 
				
			||||||
 | 
					    phosh_layer_surface_set_exclusive_zone(surface, (gint)desired_height);
 | 
				
			||||||
 | 
					    phosh_layer_surface_wl_surface_commit (surface);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,13 +2,14 @@
 | 
				
			|||||||
#define UI_MANAGER__
 | 
					#define UI_MANAGER__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <inttypes.h>
 | 
					#include <inttypes.h>
 | 
				
			||||||
 | 
					#include "eek/layersurface.h"
 | 
				
			||||||
#include "outputs.h"
 | 
					#include "outputs.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ui_manager;
 | 
					struct ui_manager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ui_manager *squeek_uiman_new();
 | 
					struct ui_manager *squeek_uiman_new(struct squeek_outputs *outputs);
 | 
				
			||||||
void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output);
 | 
					void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output);
 | 
				
			||||||
 | 
					void squeek_uiman_set_surface(struct ui_manager *uiman, PhoshLayerSurface *surface);
 | 
				
			||||||
uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman);
 | 
					uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
				
			|||||||
@ -7,17 +7,48 @@
 | 
				
			|||||||
 * Coordinates this based on information collated from all possible sources.
 | 
					 * Coordinates this based on information collated from all possible sources.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::cell::RefCell;
 | 
				
			||||||
use std::cmp::min;
 | 
					use std::cmp::min;
 | 
				
			||||||
 | 
					use std::rc::Rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use ::logging;
 | 
				
			||||||
 | 
					use ::outputs::{ OutputId, Outputs, OutputState};
 | 
				
			||||||
use ::outputs::c::OutputHandle;
 | 
					use ::outputs::c::OutputHandle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Traits
 | 
				
			||||||
 | 
					use ::logging::Warn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod c {
 | 
					mod c {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					    use std::os::raw::c_void;
 | 
				
			||||||
 | 
					    use ::outputs::c::COutputs;
 | 
				
			||||||
    use ::util::c::Wrapped;
 | 
					    use ::util::c::Wrapped;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Clone, Copy)]
 | 
				
			||||||
 | 
					    #[repr(C)]
 | 
				
			||||||
 | 
					    pub struct PhoshLayerSurface(*const c_void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    extern "C" {
 | 
				
			||||||
 | 
					        // Rustc wrongly assumes
 | 
				
			||||||
 | 
					        // that COutputs allows C direct access to the underlying RefCell.
 | 
				
			||||||
 | 
					        #[allow(improper_ctypes)]
 | 
				
			||||||
 | 
					        pub fn squeek_manager_set_surface_height(
 | 
				
			||||||
 | 
					            surface: PhoshLayerSurface,
 | 
				
			||||||
 | 
					            height: u32,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[no_mangle]
 | 
					    #[no_mangle]
 | 
				
			||||||
    pub extern "C"
 | 
					    pub extern "C"
 | 
				
			||||||
    fn squeek_uiman_new() -> Wrapped<Manager> {
 | 
					    fn squeek_uiman_new(outputs: COutputs) -> Wrapped<Manager> {
 | 
				
			||||||
        Wrapped::new(Manager { output: None })
 | 
					        let uiman_raw = Wrapped::new(Manager::new());
 | 
				
			||||||
 | 
					        if !outputs.is_null() {
 | 
				
			||||||
 | 
					            let uiman = uiman_raw.clone_ref();
 | 
				
			||||||
 | 
					            let outputs = outputs.clone_ref();
 | 
				
			||||||
 | 
					            let mut outputs = outputs.borrow_mut();
 | 
				
			||||||
 | 
					            register_output_man(uiman, &mut outputs);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        uiman_raw
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Used to size the layer surface containing all the OSK widgets.
 | 
					    /// Used to size the layer surface containing all the OSK widgets.
 | 
				
			||||||
@ -29,7 +60,7 @@ mod c {
 | 
				
			|||||||
        let uiman = uiman.clone_ref();
 | 
					        let uiman = uiman.clone_ref();
 | 
				
			||||||
        let uiman = uiman.borrow();
 | 
					        let uiman = uiman.borrow();
 | 
				
			||||||
        // TODO: what to do when there's no output?
 | 
					        // TODO: what to do when there's no output?
 | 
				
			||||||
        uiman.get_perceptual_height().unwrap_or(0)
 | 
					        uiman.state.get_perceptual_height().unwrap_or(0)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[no_mangle]
 | 
					    #[no_mangle]
 | 
				
			||||||
@ -40,29 +71,75 @@ mod c {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        let uiman = uiman.clone_ref();
 | 
					        let uiman = uiman.clone_ref();
 | 
				
			||||||
        let mut uiman = uiman.borrow_mut();
 | 
					        let mut uiman = uiman.borrow_mut();
 | 
				
			||||||
        uiman.output = Some(output);
 | 
					        uiman.set_output(output)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[no_mangle]
 | 
				
			||||||
 | 
					    pub extern "C"
 | 
				
			||||||
 | 
					    fn squeek_uiman_set_surface(
 | 
				
			||||||
 | 
					        uiman: Wrapped<Manager>,
 | 
				
			||||||
 | 
					        surface: PhoshLayerSurface,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        let uiman = uiman.clone_ref();
 | 
				
			||||||
 | 
					        let mut uiman = uiman.borrow_mut();
 | 
				
			||||||
 | 
					        // Surface is not state, so doesn't need to propagate updates.
 | 
				
			||||||
 | 
					        uiman.surface = Some(surface);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Stores current state of all things influencing what the UI should look like.
 | 
					/// Stores current state of all things influencing what the UI should look like.
 | 
				
			||||||
pub struct Manager {
 | 
					#[derive(Clone, PartialEq)]
 | 
				
			||||||
    /// Shared output handle, current state updated whenever it's needed.
 | 
					pub struct ManagerState {
 | 
				
			||||||
    // TODO: Stop assuming that the output never changes.
 | 
					    current_output: Option<(OutputId, OutputState)>,
 | 
				
			||||||
    // (There's no way for the output manager to update the ui manager.)
 | 
					 | 
				
			||||||
    // FIXME: Turn into an OutputState and apply relevant connections elsewhere.
 | 
					 | 
				
			||||||
    // Otherwise testability and predictablity is low.
 | 
					 | 
				
			||||||
    output: Option<OutputHandle>,
 | 
					 | 
				
			||||||
    //// Pixel size of the surface. Needs explicit updating.
 | 
					    //// Pixel size of the surface. Needs explicit updating.
 | 
				
			||||||
    //surface_size: Option<Size>,
 | 
					    //surface_size: Option<Size>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Manager {
 | 
					impl ManagerState {
 | 
				
			||||||
 | 
					    /// The largest ideal heigth for the keyboard as a whole
 | 
				
			||||||
 | 
					    /// judged by the ease of hitting targets within.
 | 
				
			||||||
 | 
					    /// Ideally related to finger size, the crammedness of the layout,
 | 
				
			||||||
 | 
					    /// distance from display, and motor skills of the user.
 | 
				
			||||||
 | 
					    // FIXME: Start by making this aware of display's dpi,
 | 
				
			||||||
 | 
					    // then layout number of rows.
 | 
				
			||||||
 | 
					    fn get_max_target_height(output: &OutputState) -> u32 {
 | 
				
			||||||
 | 
					        let layout_rows = 4; // FIXME: use number from layout.
 | 
				
			||||||
 | 
					        let px_size = output.get_pixel_size();
 | 
				
			||||||
 | 
					        let phys_size = output.get_phys_size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let finger_height_px = match (px_size, phys_size) {
 | 
				
			||||||
 | 
					            (Some(px_size), Some(phys_size)) => {
 | 
				
			||||||
 | 
					                // Fudged to result in 420px from the original design.
 | 
				
			||||||
 | 
					                // That gives about 9.5mm per finger height.
 | 
				
			||||||
 | 
					                // Maybe floats are not the best choice here,
 | 
				
			||||||
 | 
					                // but it gets rounded ASAP. Consider rationals.
 | 
				
			||||||
 | 
					                let keyboard_fraction_of_display: f64 = 420. / 1440.;
 | 
				
			||||||
 | 
					                let keyboard_mm = keyboard_fraction_of_display * 130.;
 | 
				
			||||||
 | 
					                let finger_height_mm = keyboard_mm / 4.;
 | 
				
			||||||
 | 
					                // TODO: Take into account target shape/area, not just height.
 | 
				
			||||||
 | 
					                finger_height_mm * px_size.height as f64 / phys_size.height as f64
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            (_, None) => output.scale as f64 * 52.5, // match total 420px at scale 2 from original design
 | 
				
			||||||
 | 
					            (None, Some(_)) => {
 | 
				
			||||||
 | 
					                log_print!(
 | 
				
			||||||
 | 
					                    logging::Level::Surprise,
 | 
				
			||||||
 | 
					                    "Output has physical size data but no pixel info",
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                output.scale as f64 * 52.5
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        (layout_rows as f64 * finger_height_px) as u32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_perceptual_height(&self) -> Option<u32> {
 | 
					    fn get_perceptual_height(&self) -> Option<u32> {
 | 
				
			||||||
        let output_info = (&self.output).as_ref()
 | 
					        let output_info = (&self.current_output).as_ref()
 | 
				
			||||||
            .and_then(|o| o.get_state())
 | 
					            .map(|(_id, os)| (
 | 
				
			||||||
            .map(|os| (os.scale as u32, os.get_pixel_size()));
 | 
					                os.scale as u32,
 | 
				
			||||||
 | 
					                os.get_pixel_size(),
 | 
				
			||||||
 | 
					                ManagerState::get_max_target_height(&os),
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
        match output_info {
 | 
					        match output_info {
 | 
				
			||||||
            Some((scale, Some(px_size))) => Some({
 | 
					            Some((scale, Some(px_size), target_height)) => Some({
 | 
				
			||||||
                let height = if (px_size.width < 720) & (px_size.width > 0) {
 | 
					                let height = if (px_size.width < 720) & (px_size.width > 0) {
 | 
				
			||||||
                    px_size.width * 7 / 12 // to match 360×210
 | 
					                    px_size.width * 7 / 12 // to match 360×210
 | 
				
			||||||
                } else if px_size.width < 1080 {
 | 
					                } else if px_size.width < 1080 {
 | 
				
			||||||
@ -71,11 +148,94 @@ impl Manager {
 | 
				
			|||||||
                    360
 | 
					                    360
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Don't exceed half the display size
 | 
					                // Don't exceed half the display size.
 | 
				
			||||||
                min(height, px_size.height / 2) / scale
 | 
					                let height = min(height, px_size.height / 2);
 | 
				
			||||||
 | 
					                // Don't waste screen space by exceeding best target height.
 | 
				
			||||||
 | 
					                let height = min(height, target_height);
 | 
				
			||||||
 | 
					                height / scale
 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
            Some((scale, None)) => Some(360 / scale),
 | 
					            Some((scale, None, _)) => Some(360 / scale),
 | 
				
			||||||
            None => None,
 | 
					            None => None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Manager {
 | 
				
			||||||
 | 
					    state: ManagerState,
 | 
				
			||||||
 | 
					    surface: Option<c::PhoshLayerSurface>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Manager {
 | 
				
			||||||
 | 
					    fn new() -> Manager {
 | 
				
			||||||
 | 
					        Manager {
 | 
				
			||||||
 | 
					            state: ManagerState { current_output: None, },
 | 
				
			||||||
 | 
					            surface: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn set_output(&mut self, output: OutputHandle) {
 | 
				
			||||||
 | 
					        let output_state = output.get_state()
 | 
				
			||||||
 | 
					            .or_warn(
 | 
				
			||||||
 | 
					                &mut logging::Print,
 | 
				
			||||||
 | 
					                logging::Problem::Bug,
 | 
				
			||||||
 | 
					                // This is really bad. It only happens when the layer surface
 | 
				
			||||||
 | 
					                // is placed, and it happens once.
 | 
				
			||||||
 | 
					                // The layer surface is on an output that can't be tracked.
 | 
				
			||||||
 | 
					                "Tried to set output that's not known to exist. Ignoring.",
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        self.state.current_output = output_state.map(
 | 
				
			||||||
 | 
					            |state| (output.get_id(), state)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        // TODO: At the time of writing, this function is only used once,
 | 
				
			||||||
 | 
					        // before the layer surface is initialized.
 | 
				
			||||||
 | 
					        // Therefore it doesn't update anything. Maybe it should in the future,
 | 
				
			||||||
 | 
					        // if it sees more use.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle_output_change(&mut self, output: OutputHandle) {
 | 
				
			||||||
 | 
					        let (id, output_state) = match &self.state.current_output {
 | 
				
			||||||
 | 
					            Some((id, state)) => {
 | 
				
			||||||
 | 
					                if *id != output.get_id() { return } // Not the current output.
 | 
				
			||||||
 | 
					                else { (id.clone(), state.clone()) }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            None => return, // Keyboard isn't on any output.
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if let Some(new_output_state) = output.get_state() {
 | 
				
			||||||
 | 
					            if new_output_state != output_state {
 | 
				
			||||||
 | 
					                let new_state = ManagerState {
 | 
				
			||||||
 | 
					                    current_output: Some((id.clone(), new_output_state)),
 | 
				
			||||||
 | 
					                    ..self.state.clone()
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                if let Some(surface) = &self.surface {
 | 
				
			||||||
 | 
					                    let new_height = new_state.get_perceptual_height();
 | 
				
			||||||
 | 
					                    if new_height != self.state.get_perceptual_height() {
 | 
				
			||||||
 | 
					                        // TODO: here hard-size the keyboard and suggestion box too.
 | 
				
			||||||
 | 
					                        match new_height {
 | 
				
			||||||
 | 
					                            Some(new_height) => unsafe {
 | 
				
			||||||
 | 
					                                c::squeek_manager_set_surface_height(
 | 
				
			||||||
 | 
					                                    *surface,
 | 
				
			||||||
 | 
					                                    new_height,
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            None => log_print!(
 | 
				
			||||||
 | 
					                                logging::Level::Bug,
 | 
				
			||||||
 | 
					                                "Can't calculate new size",
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                self.state = new_state;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn register_output_man(
 | 
				
			||||||
 | 
					    ui_man: Rc<RefCell<Manager>>,
 | 
				
			||||||
 | 
					    output_man: &mut Outputs,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    let ui_man = ui_man.clone();
 | 
				
			||||||
 | 
					    output_man.set_update_cb(Box::new(move |output: OutputHandle| {
 | 
				
			||||||
 | 
					        let mut ui_man = ui_man.borrow_mut();
 | 
				
			||||||
 | 
					        ui_man.handle_output_change(output)
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -94,6 +94,7 @@ pub mod c {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        /// Extracts the reference to the data.
 | 
					        /// Extracts the reference to the data.
 | 
				
			||||||
        /// It may cause problems if attempted in more than one place
 | 
					        /// It may cause problems if attempted in more than one place
 | 
				
			||||||
 | 
					        // FIXME: check for null
 | 
				
			||||||
        pub unsafe fn unwrap(self) -> Rc<RefCell<T>> {
 | 
					        pub unsafe fn unwrap(self) -> Rc<RefCell<T>> {
 | 
				
			||||||
            Rc::from_raw(self.0)
 | 
					            Rc::from_raw(self.0)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -107,6 +108,10 @@ pub mod c {
 | 
				
			|||||||
            Rc::into_raw(used_rc); // prevent dropping the original reference
 | 
					            Rc::into_raw(used_rc); // prevent dropping the original reference
 | 
				
			||||||
            rc
 | 
					            rc
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn is_null(&self) -> bool {
 | 
				
			||||||
 | 
					            self.0.is_null()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    impl<T> Clone for Wrapped<T> {
 | 
					    impl<T> Clone for Wrapped<T> {
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user