Merge branch 'output_fix' into 'master'
Panel handling cleanup See merge request World/Phosh/squeekboard!529
This commit is contained in:
@ -55,6 +55,8 @@ typedef struct _EekGtkKeyboardPrivate
|
||||
|
||||
GdkEventSequence *sequence; // unowned reference
|
||||
LfbEvent *event;
|
||||
|
||||
gulong kb_signal;
|
||||
} EekGtkKeyboardPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (EekGtkKeyboard, eek_gtk_keyboard, GTK_TYPE_DRAWING_AREA)
|
||||
@ -307,12 +309,19 @@ eek_gtk_keyboard_set_property (GObject *object,
|
||||
}
|
||||
}
|
||||
|
||||
// This may actually get called multiple times in a row
|
||||
// if both a parent object and its parent get destroyed
|
||||
static void
|
||||
eek_gtk_keyboard_dispose (GObject *object)
|
||||
{
|
||||
EekGtkKeyboard *self = EEK_GTK_KEYBOARD (object);
|
||||
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
|
||||
|
||||
if (priv->kb_signal != 0) {
|
||||
g_signal_handler_disconnect(priv->eekboard_context, priv->kb_signal);
|
||||
priv->kb_signal = 0;
|
||||
}
|
||||
|
||||
if (priv->renderer) {
|
||||
eek_renderer_free(priv->renderer);
|
||||
priv->renderer = NULL;
|
||||
@ -424,7 +433,7 @@ eek_gtk_keyboard_new (EekboardContextService *eekservice,
|
||||
};
|
||||
priv->render_geometry = initial_geometry;
|
||||
|
||||
g_signal_connect (eekservice,
|
||||
priv->kb_signal = g_signal_connect (eekservice,
|
||||
"notify::keyboard",
|
||||
G_CALLBACK(on_notify_keyboard),
|
||||
ret);
|
||||
|
||||
@ -60,10 +60,10 @@ struct _EekboardContextService {
|
||||
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.
|
||||
// TODO: can the main loop access submission to change the key maps instead?
|
||||
// This should probably land together with passing buttons through state,
|
||||
// to avoid race conditions between setting buttons and key maps.
|
||||
struct submission *submission; // unowned
|
||||
};
|
||||
|
||||
@ -297,6 +297,8 @@ eekboard_context_service_get_keyboard (EekboardContextService *context)
|
||||
return context->keyboard;
|
||||
}
|
||||
|
||||
// Used from Rust.
|
||||
// TODO: move hint management to Rust entirely
|
||||
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
|
||||
uint32_t hint, uint32_t purpose)
|
||||
{
|
||||
@ -340,7 +342,3 @@ void eekboard_context_service_set_submission(EekboardContextService *context, st
|
||||
submission_use_layout(context->submission, context->keyboard->layout, time);
|
||||
}
|
||||
}
|
||||
|
||||
void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui) {
|
||||
context->ui = ui;
|
||||
}
|
||||
|
||||
@ -39,16 +39,12 @@ G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD,
|
||||
|
||||
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,
|
||||
const LevelKeyboard *keyboard);
|
||||
|
||||
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
|
||||
uint32_t hint,
|
||||
uint32_t purpose);
|
||||
void
|
||||
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *layout, uint32_t timestamp);
|
||||
G_END_DECLS
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::main::PixelSize;
|
||||
use crate::outputs::OutputId;
|
||||
use crate::panel::PixelSize;
|
||||
|
||||
/// The keyboard should hide after this has elapsed to prevent flickering.
|
||||
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
|
||||
|
||||
@ -151,7 +151,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::animation;
|
||||
use crate::imservice::{ ContentHint, ContentPurpose };
|
||||
use crate::main::PanelCommand;
|
||||
use crate::panel;
|
||||
use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
|
||||
use crate::state::test::application_with_fake_output;
|
||||
|
||||
@ -176,13 +176,13 @@ mod test {
|
||||
|
||||
let l = State::new(state, now);
|
||||
let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now);
|
||||
assert_matches!(commands.panel_visibility, Some(PanelCommand::Show{..}));
|
||||
assert_matches!(commands.panel_visibility, Some(panel::Command::Show{..}));
|
||||
assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT));
|
||||
|
||||
now += animation::HIDING_TIMEOUT;
|
||||
|
||||
let (l, commands) = handle_event(l, Event::TimeoutReached(now), now);
|
||||
assert_eq!(commands.panel_visibility, Some(PanelCommand::Hide));
|
||||
assert_eq!(commands.panel_visibility, Some(panel::Command::Hide));
|
||||
assert_eq!(l.scheduled_wakeup, None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "submission.h"
|
||||
#include "input-method-unstable-v2-client-protocol.h"
|
||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include "submission.h"
|
||||
|
||||
struct imservice;
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ mod locale;
|
||||
mod main;
|
||||
mod manager;
|
||||
mod outputs;
|
||||
mod panel;
|
||||
mod popover;
|
||||
mod resources;
|
||||
mod state;
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#include "eek/eek-types.h"
|
||||
#include "dbus.h"
|
||||
#include "panel.h"
|
||||
|
||||
|
||||
struct receiver;
|
||||
@ -24,7 +25,7 @@ struct rsobjects {
|
||||
struct squeek_wayland *wayland;
|
||||
};
|
||||
|
||||
void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler);
|
||||
void register_ui_loop_handler(struct receiver *receiver, struct panel_manager *panel, EekboardContextService *hint_manager, DBusHandler *dbus_handler);
|
||||
|
||||
struct rsobjects squeek_init(void);
|
||||
|
||||
|
||||
76
src/main.rs
76
src/main.rs
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
/*! Glue for the main loop. */
|
||||
use crate::outputs::OutputId;
|
||||
use crate::panel;
|
||||
use crate::debug;
|
||||
use crate::state;
|
||||
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
|
||||
@ -20,19 +20,21 @@ mod c {
|
||||
use crate::imservice::IMService;
|
||||
use crate::imservice::c::InputMethod;
|
||||
use crate::outputs::Outputs;
|
||||
use crate::outputs::c::WlOutput;
|
||||
use crate::state;
|
||||
use crate::submission::Submission;
|
||||
use crate::util::c::Wrapped;
|
||||
use crate::vkeyboard::c::ZwpVirtualKeyboardV1;
|
||||
|
||||
/// ServerContextService*
|
||||
#[repr(transparent)]
|
||||
pub struct UIManager(*const c_void);
|
||||
|
||||
/// DbusHandler*
|
||||
#[repr(transparent)]
|
||||
pub struct DBusHandler(*const c_void);
|
||||
|
||||
/// EekboardContextService* in the role of a hint receiver
|
||||
// The clone/copy is a concession to C style of programming.
|
||||
// It would be hard to get rid of it.
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct HintManager(*const c_void);
|
||||
|
||||
/// Holds the Rust structures that are interesting from C.
|
||||
#[repr(C)]
|
||||
@ -76,9 +78,7 @@ mod c {
|
||||
extern "C" {
|
||||
#[allow(improper_ctypes)]
|
||||
fn init_wayland(wayland: *mut Wayland);
|
||||
fn server_context_service_update_keyboard(service: *const UIManager, output: WlOutput, scaled_height: u32);
|
||||
fn server_context_service_real_hide_keyboard(service: *const UIManager);
|
||||
fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32);
|
||||
fn eekboard_context_service_set_hint_purpose(service: HintManager, hint: u32, purpose: u32);
|
||||
// This should probably only get called from the gtk main loop,
|
||||
// given that dbus handler is using glib.
|
||||
fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8);
|
||||
@ -124,18 +124,20 @@ mod c {
|
||||
pub extern "C"
|
||||
fn register_ui_loop_handler(
|
||||
receiver: Wrapped<Receiver<Commands>>,
|
||||
ui_manager: *const UIManager,
|
||||
panel_manager: panel::c::PanelManager,
|
||||
hint_manager: HintManager,
|
||||
dbus_handler: *const DBusHandler,
|
||||
) {
|
||||
let receiver = unsafe { receiver.unwrap() };
|
||||
let receiver = Rc::try_unwrap(receiver).expect("References still present");
|
||||
let receiver = receiver.into_inner();
|
||||
let panel_manager = Wrapped::new(panel::Manager::new(panel_manager));
|
||||
let ctx = MainContext::default();
|
||||
let _acqu = ctx.acquire();
|
||||
receiver.attach(
|
||||
Some(&ctx),
|
||||
move |msg| {
|
||||
main_loop_handle_message(msg, ui_manager, dbus_handler);
|
||||
main_loop_handle_message(msg, panel_manager.clone(), hint_manager, dbus_handler);
|
||||
Continue(true)
|
||||
},
|
||||
);
|
||||
@ -149,18 +151,13 @@ mod c {
|
||||
/// and doesn't lend itself to testing other than integration.
|
||||
fn main_loop_handle_message(
|
||||
msg: Commands,
|
||||
ui_manager: *const UIManager,
|
||||
panel_manager: Wrapped<panel::Manager>,
|
||||
hint_manager: HintManager,
|
||||
dbus_handler: *const DBusHandler,
|
||||
) {
|
||||
match msg.panel_visibility {
|
||||
Some(PanelCommand::Show { output, height }) => unsafe {
|
||||
server_context_service_update_keyboard(ui_manager, output.0, height.as_scaled_ceiling());
|
||||
},
|
||||
Some(PanelCommand::Hide) => unsafe {
|
||||
server_context_service_real_hide_keyboard(ui_manager);
|
||||
},
|
||||
None => {},
|
||||
};
|
||||
if let Some(visibility) = msg.panel_visibility {
|
||||
panel::Manager::update(panel_manager, visibility);
|
||||
}
|
||||
|
||||
if let Some(visible) = msg.dbus_visible_set {
|
||||
if dbus_handler != std::ptr::null() {
|
||||
@ -170,8 +167,8 @@ mod c {
|
||||
|
||||
if let Some(hints) = msg.layout_hint_set {
|
||||
unsafe {
|
||||
server_context_service_set_hint_purpose(
|
||||
ui_manager,
|
||||
eekboard_context_service_set_hint_purpose(
|
||||
hint_manager,
|
||||
hints.hint.bits(),
|
||||
hints.purpose.clone() as u32,
|
||||
)
|
||||
@ -180,42 +177,11 @@ mod c {
|
||||
}
|
||||
}
|
||||
|
||||
/// Size in pixels that is aware of scaling
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct PixelSize {
|
||||
pub pixels: u32,
|
||||
pub scale_factor: u32,
|
||||
}
|
||||
|
||||
fn div_ceil(a: u32, b: u32) -> u32 {
|
||||
// Given that it's for pixels on a screen, an overflow is unlikely.
|
||||
(a + b - 1) / b
|
||||
}
|
||||
|
||||
impl PixelSize {
|
||||
pub fn as_scaled_floor(&self) -> u32 {
|
||||
self.pixels / self.scale_factor
|
||||
}
|
||||
|
||||
pub fn as_scaled_ceiling(&self) -> u32 {
|
||||
div_ceil(self.pixels, self.scale_factor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PanelCommand {
|
||||
Show {
|
||||
output: OutputId,
|
||||
height: PixelSize,
|
||||
},
|
||||
Hide,
|
||||
}
|
||||
|
||||
/// The commands consumed by the main loop,
|
||||
/// to be sent out to external components.
|
||||
#[derive(Clone)]
|
||||
pub struct Commands {
|
||||
pub panel_visibility: Option<PanelCommand>,
|
||||
pub panel_visibility: Option<panel::Command>,
|
||||
pub layout_hint_set: Option<state::InputMethodDetails>,
|
||||
pub dbus_visible_set: Option<bool>,
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ sources = [
|
||||
config_h,
|
||||
'dbus.c',
|
||||
'imservice.c',
|
||||
'panel.c',
|
||||
'popover.c',
|
||||
'server-context-service.c',
|
||||
'wayland.c',
|
||||
|
||||
130
src/panel.c
Normal file
130
src/panel.c
Normal file
@ -0,0 +1,130 @@
|
||||
#include "eekboard/eekboard-context-service.h"
|
||||
#include "wayland.h"
|
||||
#include "panel.h"
|
||||
|
||||
|
||||
// Called from rust
|
||||
/// Destroys the widget
|
||||
void
|
||||
panel_manager_hide(struct panel_manager *self)
|
||||
{
|
||||
if (self->window) {
|
||||
gtk_widget_destroy (GTK_WIDGET (self->window));
|
||||
}
|
||||
if (self->widget) {
|
||||
gtk_widget_destroy (GTK_WIDGET (self->widget));
|
||||
}
|
||||
self->window = NULL;
|
||||
self->widget = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
on_destroy (struct panel_manager *self, GtkWidget *widget)
|
||||
{
|
||||
g_assert (widget == GTK_WIDGET(self->window));
|
||||
panel_manager_hide(self);
|
||||
}
|
||||
|
||||
|
||||
/// panel::Manager. Only needed for this callback
|
||||
struct squeek_panel_manager;
|
||||
|
||||
/// Calls back into Rust
|
||||
void squeek_panel_manager_configured(struct squeek_panel_manager *mgr, uint32_t width, uint32_t height);
|
||||
|
||||
static void
|
||||
on_surface_configure(struct squeek_panel_manager *self, PhoshLayerSurface *surface)
|
||||
{
|
||||
gint width;
|
||||
gint height;
|
||||
g_return_if_fail (PHOSH_IS_LAYER_SURFACE (surface));
|
||||
|
||||
g_object_get(G_OBJECT(surface),
|
||||
"configured-width", &width,
|
||||
"configured-height", &height,
|
||||
NULL);
|
||||
squeek_panel_manager_configured(self, width, height);
|
||||
}
|
||||
|
||||
static void
|
||||
make_widget (struct panel_manager *self)
|
||||
{
|
||||
if (self->widget) {
|
||||
g_error("Widget already present");
|
||||
}
|
||||
self->widget = eek_gtk_keyboard_new (self->state, self->submission, self->layout);
|
||||
|
||||
gtk_widget_set_has_tooltip (self->widget, TRUE);
|
||||
gtk_container_add (GTK_CONTAINER(self->window), self->widget);
|
||||
gtk_widget_show_all(self->widget);
|
||||
}
|
||||
|
||||
|
||||
// Called from rust
|
||||
/// Creates a new panel widget
|
||||
void
|
||||
panel_manager_request_widget (struct panel_manager *self, struct wl_output *output, uint32_t height, struct squeek_panel_manager *mgr)
|
||||
{
|
||||
if (self->window) {
|
||||
g_error("Window already present");
|
||||
}
|
||||
|
||||
self->window = g_object_new (
|
||||
PHOSH_TYPE_LAYER_SURFACE,
|
||||
"layer-shell", squeek_wayland->layer_shell,
|
||||
"wl-output", output,
|
||||
"height", height,
|
||||
"anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
|
||||
| ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
|
||||
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT,
|
||||
"layer", ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
||||
"kbd-interactivity", FALSE,
|
||||
"exclusive-zone", height,
|
||||
"namespace", "osk",
|
||||
NULL
|
||||
);
|
||||
|
||||
g_object_connect (self->window,
|
||||
"swapped-signal::destroy", G_CALLBACK(on_destroy), self,
|
||||
"swapped-signal::configured", G_CALLBACK(on_surface_configure), mgr,
|
||||
NULL);
|
||||
|
||||
// The properties below are just to make hacking easier.
|
||||
// The way we use layer-shell overrides some,
|
||||
// and there's no space in the protocol for others.
|
||||
// Those may still be useful in the future,
|
||||
// or for hacks with regular windows.
|
||||
gtk_widget_set_can_focus (GTK_WIDGET(self->window), FALSE);
|
||||
g_object_set (G_OBJECT(self->window), "accept_focus", FALSE, NULL);
|
||||
gtk_window_set_title (GTK_WINDOW(self->window), "Squeekboard");
|
||||
gtk_window_set_icon_name (GTK_WINDOW(self->window), "squeekboard");
|
||||
gtk_window_set_keep_above (GTK_WINDOW(self->window), TRUE);
|
||||
|
||||
make_widget(self);
|
||||
|
||||
gtk_widget_show (GTK_WIDGET(self->window));
|
||||
}
|
||||
|
||||
// Called from rust
|
||||
/// Updates the size
|
||||
void
|
||||
panel_manager_resize (struct panel_manager *self, uint32_t height)
|
||||
{
|
||||
phosh_layer_surface_set_size(self->window, 0, height);
|
||||
phosh_layer_surface_set_exclusive_zone(self->window, height);
|
||||
phosh_layer_surface_wl_surface_commit(self->window);
|
||||
}
|
||||
|
||||
|
||||
struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout)
|
||||
{
|
||||
struct panel_manager mgr = {
|
||||
.state = state,
|
||||
.submission = submission,
|
||||
.layout = layout,
|
||||
.window = NULL,
|
||||
.widget = NULL,
|
||||
.current_output = NULL,
|
||||
};
|
||||
return mgr;
|
||||
}
|
||||
21
src/panel.h
Normal file
21
src/panel.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "eek/layersurface.h"
|
||||
#include "src/layout.h"
|
||||
#include "src/submission.h"
|
||||
|
||||
// Stores the objects that the panel and its widget will refer to
|
||||
struct panel_manager {
|
||||
EekboardContextService *state; // unowned
|
||||
/// Needed for instantiating the widget
|
||||
struct submission *submission; // unowned
|
||||
struct squeek_layout_state *layout;
|
||||
|
||||
PhoshLayerSurface *window;
|
||||
GtkWidget *widget; // nullable
|
||||
|
||||
// Those should be held in Rust
|
||||
struct wl_output *current_output;
|
||||
};
|
||||
|
||||
struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout);
|
||||
248
src/panel.rs
Normal file
248
src/panel.rs
Normal file
@ -0,0 +1,248 @@
|
||||
/* Copyright (C) 2022 Purism SPC
|
||||
* SPDX-License-Identifier: GPL-3.0+
|
||||
*/
|
||||
|
||||
/*! Panel state management.
|
||||
*
|
||||
* This is effectively a mirror of the previous C code,
|
||||
* with an explicit state machine managing the panel size.
|
||||
*
|
||||
* It still relies on a callback from Wayland to accept the panel size,
|
||||
* which makes this code somewhat prone to mistakes.
|
||||
*
|
||||
* An alternative to the callback would be
|
||||
* to send a message all the way to `state::State`
|
||||
* every time the allocated size changes.
|
||||
* That would allow for a more holistic view
|
||||
* of interactions of different pieces of state.
|
||||
*
|
||||
* However, `state::State` already has the potential to become a ball of mud,
|
||||
* tightly coupling different functionality and making it difficult to see independent units.
|
||||
*
|
||||
* For this reason, I'm taking a light touch approach with the panel manager,
|
||||
* and moving it just a bit closer to `state::State`.
|
||||
* Hopefully ths still allows us to expose assumptions that were not stated yet
|
||||
* (e.g. can the output disappear between size request andallocation?).
|
||||
*
|
||||
* Tight coupling, e.g. a future one between presented hints and layout size,
|
||||
* will have to be taken into account later.
|
||||
*/
|
||||
|
||||
use crate::logging;
|
||||
use crate::outputs::OutputId;
|
||||
use crate::util::c::Wrapped;
|
||||
|
||||
|
||||
pub mod c {
|
||||
use super::*;
|
||||
use glib;
|
||||
use gtk::Continue;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use crate::outputs::c::WlOutput;
|
||||
|
||||
/// struct panel_manager*
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PanelManager(*const c_void);
|
||||
|
||||
extern "C" {
|
||||
#[allow(improper_ctypes)]
|
||||
pub fn panel_manager_request_widget(
|
||||
service: PanelManager,
|
||||
output: WlOutput,
|
||||
height: u32,
|
||||
// for callbacks
|
||||
panel: Wrapped<Manager>,
|
||||
);
|
||||
pub fn panel_manager_resize(service: PanelManager, height: u32);
|
||||
pub fn panel_manager_hide(service: PanelManager);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_panel_manager_configured(panel: Wrapped<Manager>, width: u32, height: u32) {
|
||||
// This is why this needs to be moved into state::State:
|
||||
// it's getting too coupled to glib.
|
||||
glib::idle_add_local(move || {
|
||||
let panel = panel.clone_ref();
|
||||
panel.borrow_mut().set_configured(Size{width, height});
|
||||
Continue(false)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Size in pixels that is aware of scaling
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct PixelSize {
|
||||
pub pixels: u32,
|
||||
pub scale_factor: u32,
|
||||
}
|
||||
|
||||
fn div_ceil(a: u32, b: u32) -> u32 {
|
||||
// Given that it's for pixels on a screen, an overflow is unlikely.
|
||||
(a + b - 1) / b
|
||||
}
|
||||
|
||||
impl PixelSize {
|
||||
pub fn as_scaled_floor(&self) -> u32 {
|
||||
self.pixels / self.scale_factor
|
||||
}
|
||||
|
||||
pub fn as_scaled_ceiling(&self) -> u32 {
|
||||
div_ceil(self.pixels, self.scale_factor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Size {
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
/// This state requests the Wayland layer shell protocol synchronization:
|
||||
/// the application asks for some size,
|
||||
/// and then receives a size that the compositor thought appropriate.
|
||||
/// Stores raw values passed to Wayland, i.e. scaled dimensions.
|
||||
#[derive(Clone, Debug)]
|
||||
enum State {
|
||||
Hidden,
|
||||
SizeRequested {
|
||||
output: OutputId,
|
||||
height: u32,
|
||||
//width: u32,
|
||||
},
|
||||
SizeAllocated {
|
||||
output: OutputId,
|
||||
wanted_height: u32,
|
||||
allocated: Size,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Command {
|
||||
Show {
|
||||
output: OutputId,
|
||||
height: PixelSize,
|
||||
},
|
||||
Hide,
|
||||
}
|
||||
|
||||
/// Tries to contain all the panel sizing duties.
|
||||
pub struct Manager {
|
||||
panel: c::PanelManager,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new(panel: c::PanelManager) -> Self {
|
||||
Self {
|
||||
panel,
|
||||
state: State::Hidden,
|
||||
}
|
||||
}
|
||||
// TODO: mabe send the allocated size back to state::State,
|
||||
// to perform layout adjustments
|
||||
fn set_configured(&mut self, size: Size) {
|
||||
self.state = match self.state.clone() {
|
||||
State::Hidden => {
|
||||
// This may happen if a hide is scheduled immediately after a show.
|
||||
log_print!(
|
||||
logging::Level::Surprise,
|
||||
"Panel has been configured, but no request is pending. Ignoring",
|
||||
);
|
||||
State::Hidden
|
||||
},
|
||||
State::SizeAllocated{output, wanted_height, ..} => {
|
||||
log_print!(
|
||||
logging::Level::Surprise,
|
||||
"Panel received new configuration without asking",
|
||||
);
|
||||
State::SizeAllocated{output, wanted_height, allocated: size}
|
||||
},
|
||||
State::SizeRequested{output, height} => State::SizeAllocated {
|
||||
output,
|
||||
wanted_height: height,
|
||||
allocated: size,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update(mgr: Wrapped<Manager>, cmd: Command) {
|
||||
let copied = mgr.clone();
|
||||
|
||||
let mgr = mgr.clone_ref();
|
||||
let mut mgr = mgr.borrow_mut();
|
||||
|
||||
(*mgr).state = match (cmd, mgr.state.clone()) {
|
||||
(Command::Hide, State::Hidden) => State::Hidden,
|
||||
(Command::Hide, State::SizeAllocated{..}) => {
|
||||
unsafe { c::panel_manager_hide(mgr.panel); }
|
||||
State::Hidden
|
||||
},
|
||||
(Command::Hide, State::SizeRequested{..}) => {
|
||||
unsafe { c::panel_manager_hide(mgr.panel); }
|
||||
State::Hidden
|
||||
},
|
||||
(Command::Show{output, height}, State::Hidden) => {
|
||||
let height = height.as_scaled_ceiling();
|
||||
unsafe { c::panel_manager_request_widget(mgr.panel, output.0, height, copied); }
|
||||
State::SizeRequested{output, height}
|
||||
},
|
||||
(
|
||||
Command::Show{output, height},
|
||||
State::SizeRequested{output: req_output, height: req_height},
|
||||
) => {
|
||||
let height = height.as_scaled_ceiling();
|
||||
if output == req_output && height == req_height {
|
||||
State::SizeRequested{output: req_output, height: req_height}
|
||||
} else if output == req_output {
|
||||
// I'm not sure about that.
|
||||
// This could cause a busy loop,
|
||||
// when two requests are being processed at the same time:
|
||||
// one message in the compositor to allocate size A,
|
||||
// causing the state to update to height A'
|
||||
// the other from the state wanting height B',
|
||||
// causing the compositor to change size to B.
|
||||
// So better cut this short here, despite artifacts.
|
||||
// Out of simplicty, just ignore the new request.
|
||||
// If that causes problems, the request in flight could be stored
|
||||
// for the purpose of handling it better somehow.
|
||||
State::SizeRequested{output: req_output, height: req_height}
|
||||
} else {
|
||||
// This looks weird, but should be safe.
|
||||
// The stack seems to handle
|
||||
// configure events on a dead surface.
|
||||
unsafe {
|
||||
c::panel_manager_hide(mgr.panel);
|
||||
c::panel_manager_request_widget(mgr.panel, output.0, height, copied);
|
||||
}
|
||||
State::SizeRequested{output, height}
|
||||
}
|
||||
},
|
||||
(
|
||||
Command::Show{output, height},
|
||||
State::SizeAllocated{output: alloc_output, allocated, wanted_height},
|
||||
) => {
|
||||
let height = height.as_scaled_ceiling();
|
||||
if output == alloc_output && height == wanted_height {
|
||||
State::SizeAllocated{output: alloc_output, wanted_height, allocated}
|
||||
} else if output == alloc_output && height == allocated.height {
|
||||
State::SizeAllocated{output: alloc_output, wanted_height: height, allocated}
|
||||
} else if output == alloc_output {
|
||||
// Should *all* other heights cause a resize?
|
||||
// What about those between wanted and allocated?
|
||||
unsafe { c::panel_manager_resize(mgr.panel, height); }
|
||||
State::SizeRequested{output, height}
|
||||
} else {
|
||||
unsafe {
|
||||
c::panel_manager_hide(mgr.panel);
|
||||
c::panel_manager_request_widget(mgr.panel, output.0, height, copied);
|
||||
}
|
||||
State::SizeRequested{output, height}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,14 +20,7 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "eek/eek.h"
|
||||
#include "eek/eek-gtk-keyboard.h"
|
||||
#include "eek/layersurface.h"
|
||||
#include "eekboard/eekboard-context-service.h"
|
||||
#include "submission.h"
|
||||
#include "wayland.h"
|
||||
#include "server-context-service.h"
|
||||
#include "wayland-client-protocol.h"
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
@ -37,149 +30,13 @@ enum {
|
||||
|
||||
struct _ServerContextService {
|
||||
GObject parent;
|
||||
|
||||
EekboardContextService *state; // unowned
|
||||
/// Needed for instantiating the widget
|
||||
struct submission *submission; // unowned
|
||||
struct squeek_layout_state *layout;
|
||||
struct squeek_state_manager *state_manager; // shared reference
|
||||
|
||||
PhoshLayerSurface *window;
|
||||
GtkWidget *widget; // nullable
|
||||
|
||||
struct wl_output *current_output;
|
||||
guint last_requested_height;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(ServerContextService, server_context_service, G_TYPE_OBJECT);
|
||||
|
||||
static void
|
||||
on_destroy (ServerContextService *self, GtkWidget *widget)
|
||||
{
|
||||
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
|
||||
|
||||
g_assert (widget == GTK_WIDGET(self->window));
|
||||
|
||||
self->window = NULL;
|
||||
self->widget = NULL;
|
||||
|
||||
//eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
|
||||
}
|
||||
|
||||
static void
|
||||
make_window (ServerContextService *self, struct wl_output *output, uint32_t height)
|
||||
{
|
||||
if (self->window) {
|
||||
g_error("Window already present");
|
||||
}
|
||||
|
||||
self->window = g_object_new (
|
||||
PHOSH_TYPE_LAYER_SURFACE,
|
||||
"layer-shell", squeek_wayland->layer_shell,
|
||||
"wl-output", output,
|
||||
"height", height,
|
||||
"anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
|
||||
| ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
|
||||
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT,
|
||||
"layer", ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
||||
"kbd-interactivity", FALSE,
|
||||
"exclusive-zone", height,
|
||||
"namespace", "osk",
|
||||
NULL
|
||||
);
|
||||
|
||||
g_object_connect (self->window,
|
||||
"swapped-signal::destroy", G_CALLBACK(on_destroy), self,
|
||||
//"swapped-signal::configured", G_CALLBACK(on_surface_configure), self,
|
||||
NULL);
|
||||
|
||||
// The properties below are just to make hacking easier.
|
||||
// The way we use layer-shell overrides some,
|
||||
// and there's no space in the protocol for others.
|
||||
// Those may still be useful in the future,
|
||||
// or for hacks with regular windows.
|
||||
gtk_widget_set_can_focus (GTK_WIDGET(self->window), FALSE);
|
||||
g_object_set (G_OBJECT(self->window), "accept_focus", FALSE, NULL);
|
||||
gtk_window_set_title (GTK_WINDOW(self->window), "Squeekboard");
|
||||
gtk_window_set_icon_name (GTK_WINDOW(self->window), "squeekboard");
|
||||
gtk_window_set_keep_above (GTK_WINDOW(self->window), TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_window (ServerContextService *self)
|
||||
{
|
||||
gtk_widget_destroy (GTK_WIDGET (self->window));
|
||||
self->window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
make_widget (ServerContextService *self)
|
||||
{
|
||||
if (self->widget) {
|
||||
gtk_widget_destroy(self->widget);
|
||||
self->widget = NULL;
|
||||
}
|
||||
self->widget = eek_gtk_keyboard_new (self->state, self->submission, self->layout);
|
||||
|
||||
gtk_widget_set_has_tooltip (self->widget, TRUE);
|
||||
gtk_container_add (GTK_CONTAINER(self->window), self->widget);
|
||||
gtk_widget_show_all(self->widget);
|
||||
}
|
||||
|
||||
// Called from rust
|
||||
/// Updates the type of hiddenness
|
||||
void
|
||||
server_context_service_real_hide_keyboard (ServerContextService *self)
|
||||
{
|
||||
//self->desired_height = 0;
|
||||
self->current_output = NULL;
|
||||
if (self->window) {
|
||||
gtk_widget_hide (GTK_WIDGET(self->window));
|
||||
}
|
||||
}
|
||||
|
||||
// Called from rust
|
||||
/// Updates the type of visibility.
|
||||
/// Height is in scaled units.
|
||||
void
|
||||
server_context_service_update_keyboard (ServerContextService *self, struct wl_output *output, uint32_t scaled_height)
|
||||
{
|
||||
if (output != self->current_output) {
|
||||
// Recreate on a new output
|
||||
server_context_service_real_hide_keyboard(self);
|
||||
} else {
|
||||
gint h;
|
||||
PhoshLayerSurface *surface = self->window;
|
||||
g_object_get(G_OBJECT(surface),
|
||||
"configured-height", &h,
|
||||
NULL);
|
||||
|
||||
if ((uint32_t)h != scaled_height) {
|
||||
|
||||
//TODO: make sure that redrawing happens in the correct place (it doesn't now).
|
||||
phosh_layer_surface_set_size(self->window, 0, scaled_height);
|
||||
phosh_layer_surface_set_exclusive_zone(self->window, scaled_height);
|
||||
phosh_layer_surface_wl_surface_commit(self->window);
|
||||
|
||||
self->current_output = output;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->current_output = output;
|
||||
|
||||
if (!self->window) {
|
||||
make_window (self, output, scaled_height);
|
||||
}
|
||||
if (!self->widget) {
|
||||
make_widget (self);
|
||||
}
|
||||
gtk_widget_show (GTK_WIDGET(self->window));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
server_context_service_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
@ -210,17 +67,6 @@ server_context_service_get_property (GObject *object,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
server_context_service_dispose (GObject *object)
|
||||
{
|
||||
ServerContextService *self = SERVER_CONTEXT_SERVICE(object);
|
||||
|
||||
destroy_window (self);
|
||||
self->widget = NULL;
|
||||
|
||||
G_OBJECT_CLASS (server_context_service_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
server_context_service_class_init (ServerContextServiceClass *klass)
|
||||
{
|
||||
@ -229,7 +75,6 @@ server_context_service_class_init (ServerContextServiceClass *klass)
|
||||
|
||||
gobject_class->set_property = server_context_service_set_property;
|
||||
gobject_class->get_property = server_context_service_get_property;
|
||||
gobject_class->dispose = server_context_service_dispose;
|
||||
|
||||
/**
|
||||
* ServerContextServie:keyboard:
|
||||
@ -250,41 +95,29 @@ server_context_service_class_init (ServerContextServiceClass *klass)
|
||||
static void
|
||||
server_context_service_init (ServerContextService *self) {}
|
||||
|
||||
static void
|
||||
init (ServerContextService *self) {
|
||||
|
||||
ServerContextService *
|
||||
server_context_service_new (struct squeek_state_manager *state_manager)
|
||||
{
|
||||
ServerContextService *holder = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
|
||||
holder->state_manager = state_manager;
|
||||
|
||||
const char *schema_name = "org.gnome.desktop.a11y.applications";
|
||||
GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default();
|
||||
g_autoptr(GSettingsSchema) schema = NULL;
|
||||
|
||||
if (!ssrc) {
|
||||
g_warning("No gsettings schemas installed.");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
schema = g_settings_schema_source_lookup(ssrc, schema_name, TRUE);
|
||||
if (schema) {
|
||||
g_autoptr(GSettings) settings = g_settings_new (schema_name);
|
||||
g_settings_bind (settings, "screen-keyboard-enabled",
|
||||
self, "enabled", G_SETTINGS_BIND_GET);
|
||||
holder, "enabled", G_SETTINGS_BIND_GET);
|
||||
} else {
|
||||
g_warning("Gsettings schema %s is not installed on the system. "
|
||||
"Enabling by default.", schema_name);
|
||||
}
|
||||
}
|
||||
|
||||
ServerContextService *
|
||||
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager)
|
||||
{
|
||||
ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
|
||||
ui->submission = submission;
|
||||
ui->state = self;
|
||||
ui->layout = layout;
|
||||
ui->state_manager = state_manager;
|
||||
init(ui);
|
||||
return ui;
|
||||
}
|
||||
|
||||
// Used from Rust
|
||||
void server_context_service_set_hint_purpose(ServerContextService *self, uint32_t hint,
|
||||
uint32_t purpose) {
|
||||
eekboard_context_service_set_hint_purpose(self->state, hint, purpose);
|
||||
return holder;
|
||||
}
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
*/
|
||||
#ifndef SERVER_CONTEXT_SERVICE_H
|
||||
#define SERVER_CONTEXT_SERVICE_H 1
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "src/layout.h"
|
||||
#include "src/submission.h"
|
||||
#include "main.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@ -28,8 +28,8 @@ G_BEGIN_DECLS
|
||||
/** Manages the lifecycle of the window displaying layouts. */
|
||||
G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject)
|
||||
|
||||
ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager);
|
||||
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
|
||||
ServerContextService *server_context_service_new(struct squeek_state_manager *state_manager);
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SERVER_CONTEXT_SERVICE_H */
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#include "layout.h"
|
||||
#include "main.h"
|
||||
#include "outputs.h"
|
||||
#include "panel.h"
|
||||
#include "submission.h"
|
||||
#include "server-context-service.h"
|
||||
#include "wayland.h"
|
||||
@ -51,8 +52,10 @@ typedef enum _SqueekboardDebugFlags {
|
||||
struct squeekboard {
|
||||
struct squeek_wayland wayland; // Just hooks.
|
||||
DBusHandler *dbus_handler; // Controls visibility of the OSK.
|
||||
EekboardContextService *settings_context; // Gsettings hooks.
|
||||
ServerContextService *ui_context; // mess, includes the entire UI
|
||||
EekboardContextService *settings_context; // Gsettings hooks for layouts.
|
||||
/// Gsettings hook for visibility. TODO: this does not belong in gsettings.
|
||||
ServerContextService *settings_handler;
|
||||
struct panel_manager panel_manager; // Controls the shape of the panel.
|
||||
/// Currently wanted layout. TODO: merge into state::Application
|
||||
struct squeek_layout_state layout_choice;
|
||||
};
|
||||
@ -435,20 +438,21 @@ main (int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
eekboard_context_service_set_submission(instance.settings_context, rsobjects.submission);
|
||||
|
||||
ServerContextService *ui_context = server_context_service_new(
|
||||
instance.settings_context,
|
||||
rsobjects.submission,
|
||||
&instance.layout_choice,
|
||||
ServerContextService *setting_listener = server_context_service_new(
|
||||
rsobjects.state_manager);
|
||||
if (!ui_context) {
|
||||
g_error("Could not initialize GUI");
|
||||
exit(1);
|
||||
if (!setting_listener) {
|
||||
g_warning ("could not connect to gsettings");
|
||||
}
|
||||
|
||||
instance.ui_context = ui_context;
|
||||
register_ui_loop_handler(rsobjects.receiver, instance.ui_context, instance.dbus_handler);
|
||||
instance.settings_handler = setting_listener;
|
||||
|
||||
eekboard_context_service_set_submission(instance.settings_context, rsobjects.submission);
|
||||
|
||||
instance.panel_manager = panel_manager_new(instance.settings_context,
|
||||
rsobjects.submission,
|
||||
&instance.layout_choice);
|
||||
|
||||
register_ui_loop_handler(rsobjects.receiver, &instance.panel_manager, instance.settings_context, instance.dbus_handler);
|
||||
|
||||
session_register();
|
||||
|
||||
|
||||
@ -8,9 +8,11 @@
|
||||
use crate::animation;
|
||||
use crate::debug;
|
||||
use crate::imservice::{ ContentHint, ContentPurpose };
|
||||
use crate::main::{ Commands, PanelCommand, PixelSize };
|
||||
use crate::main::Commands;
|
||||
use crate::outputs;
|
||||
use crate::outputs::{Millimeter, OutputId, OutputState};
|
||||
use crate::panel;
|
||||
use crate::panel::PixelSize;
|
||||
use crate::util::Rational;
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
@ -118,8 +120,8 @@ impl Outcome {
|
||||
// FIXME: handle switching outputs
|
||||
let (dbus_visible_set, panel_visibility) = match new_state.visibility {
|
||||
animation::Outcome::Visible{output, height}
|
||||
=> (Some(true), Some(PanelCommand::Show{output, height})),
|
||||
animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)),
|
||||
=> (Some(true), Some(panel::Command::Show{output, height})),
|
||||
animation::Outcome::Hidden => (Some(false), Some(panel::Command::Hide)),
|
||||
};
|
||||
|
||||
Commands {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
#ifndef __SUBMISSION_H
|
||||
#define __SUBMISSION_H
|
||||
|
||||
#include "input-method-unstable-v2-client-protocol.h"
|
||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||
#include "inttypes.h"
|
||||
|
||||
#include "eek/eek-types.h"
|
||||
#include "main.h"
|
||||
|
||||
struct squeek_layout;
|
||||
struct submission;
|
||||
|
||||
// Defined in Rust
|
||||
uint8_t submission_hint_available(struct submission *self);
|
||||
|
||||
Reference in New Issue
Block a user