diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index d29dc0f8..f952e645 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -7,6 +7,7 @@ Welcome to Weston documentation! toc/libweston.rst toc/test-suite.rst + toc/kiosk-shell.rst Weston ------ diff --git a/doc/sphinx/toc/kiosk-shell.rst b/doc/sphinx/toc/kiosk-shell.rst new file mode 100644 index 00000000..dedf6a9c --- /dev/null +++ b/doc/sphinx/toc/kiosk-shell.rst @@ -0,0 +1,17 @@ +Weston kiosk-shell +================== + +Weston's kiosk-shell is a simple shell targeted at single-app/kiosk use cases. +It makes all top-level application windows fullscreen, and supports defining +which applications to place on particular outputs. This is achieved with the +``app-ids=`` field in the corresponding output section in weston.ini. For +example: + +.. code-block:: ini + + [output] + name=screen0 + app-ids=org.domain.app1,com.domain.app2 + +To run weston with kiosk-shell set ``shell=kiosk-shell.so`` in weston.ini, or +use the ``--shell=kiosk-shell.so`` command-line option. diff --git a/doc/sphinx/toc/meson.build b/doc/sphinx/toc/meson.build index 6ab3cda5..f91e460d 100644 --- a/doc/sphinx/toc/meson.build +++ b/doc/sphinx/toc/meson.build @@ -1,5 +1,6 @@ # you need to add here any files you add to the toc directory as well files = [ + 'kiosk-shell.rst', 'libweston.rst', 'test-suite.rst', 'test-suite-api.rst', diff --git a/kiosk-shell/kiosk-shell-grab.c b/kiosk-shell/kiosk-shell-grab.c new file mode 100644 index 00000000..3ea0156c --- /dev/null +++ b/kiosk-shell/kiosk-shell-grab.c @@ -0,0 +1,314 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "kiosk-shell-grab.h" +#include "shared/helpers.h" + +struct kiosk_shell_grab { + struct kiosk_shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; + + struct weston_pointer_grab pointer_grab; + struct weston_touch_grab touch_grab; + wl_fixed_t dx, dy; + bool active; +}; + +static void +kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab); + +/* + * pointer_move_grab_interface + */ + +static void +pointer_move_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_move_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +pointer_move_grab_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ +} + +static void +pointer_move_grab_frame(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_move_grab_motion(struct weston_pointer_grab *pointer_grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + struct weston_pointer *pointer = pointer_grab->pointer; + struct kiosk_shell_surface *shsurf = shgrab->shsurf; + struct weston_surface *surface; + int dx, dy; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + dx = wl_fixed_to_int(pointer->x + shgrab->dx); + dy = wl_fixed_to_int(pointer->y + shgrab->dy); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +pointer_move_grab_button(struct weston_pointer_grab *pointer_grab, + const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + struct weston_pointer *pointer = pointer_grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) + kiosk_shell_grab_destroy(shgrab); +} + +static void +pointer_move_grab_cancel(struct weston_pointer_grab *pointer_grab) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + + kiosk_shell_grab_destroy(shgrab); +} + +static const struct weston_pointer_grab_interface pointer_move_grab_interface = { + pointer_move_grab_focus, + pointer_move_grab_motion, + pointer_move_grab_button, + pointer_move_grab_axis, + pointer_move_grab_axis_source, + pointer_move_grab_frame, + pointer_move_grab_cancel, +}; + +/* + * touch_move_grab_interface + */ + +static void +touch_move_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ +} + +static void +touch_move_grab_up(struct weston_touch_grab *touch_grab, + const struct timespec *time, int touch_id) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + + if (touch_id == 0) + shgrab->active = false; + + if (touch_grab->touch->num_tp == 0) + kiosk_shell_grab_destroy(shgrab); +} + +static void +touch_move_grab_motion(struct weston_touch_grab *touch_grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + struct weston_touch *touch = touch_grab->touch; + struct kiosk_shell_surface *shsurf = shgrab->shsurf; + struct weston_surface *surface; + int dx, dy; + + if (!shsurf || !shgrab->active) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + dx = wl_fixed_to_int(touch->grab_x + shgrab->dx); + dy = wl_fixed_to_int(touch->grab_y + shgrab->dy); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +touch_move_grab_frame(struct weston_touch_grab *grab) +{ +} + +static void +touch_move_grab_cancel(struct weston_touch_grab *touch_grab) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + + kiosk_shell_grab_destroy(shgrab); +} + +static const struct weston_touch_grab_interface touch_move_grab_interface = { + touch_move_grab_down, + touch_move_grab_up, + touch_move_grab_motion, + touch_move_grab_frame, + touch_move_grab_cancel, +}; + +/* + * kiosk_shell_grab + */ + +static void +kiosk_shell_grab_handle_shsurf_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_grab *shgrab = + container_of(listener, struct kiosk_shell_grab, + shsurf_destroy_listener); + + shgrab->shsurf = NULL; +} + +static struct kiosk_shell_grab * +kiosk_shell_grab_create(struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_grab *shgrab; + + shgrab = zalloc(sizeof *shgrab); + if (!shgrab) + return NULL; + + shgrab->shsurf = shsurf; + shgrab->shsurf_destroy_listener.notify = + kiosk_shell_grab_handle_shsurf_destroy; + wl_signal_add(&shsurf->destroy_signal, + &shgrab->shsurf_destroy_listener); + + shsurf->grabbed = true; + + return shgrab; +} + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, + struct weston_pointer *pointer) +{ + struct kiosk_shell_grab *shgrab; + + if (!shsurf) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return KIOSK_SHELL_GRAB_RESULT_IGNORED; + + shgrab = kiosk_shell_grab_create(shsurf); + if (!shgrab) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + pointer->grab_x; + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + pointer->grab_y; + shgrab->active = true; + + weston_seat_break_desktop_grabs(pointer->seat); + + shgrab->pointer_grab.interface = &pointer_move_grab_interface; + weston_pointer_start_grab(pointer, &shgrab->pointer_grab); + + return KIOSK_SHELL_GRAB_RESULT_OK; +} + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, + struct weston_touch *touch) +{ + struct kiosk_shell_grab *shgrab; + + if (!shsurf) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return KIOSK_SHELL_GRAB_RESULT_IGNORED; + + shgrab = kiosk_shell_grab_create(shsurf); + if (!shgrab) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + touch->grab_x; + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + touch->grab_y; + shgrab->active = true; + + weston_seat_break_desktop_grabs(touch->seat); + + shgrab->touch_grab.interface = &touch_move_grab_interface; + weston_touch_start_grab(touch, &shgrab->touch_grab); + + return KIOSK_SHELL_GRAB_RESULT_OK; +} + +static void +kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab) +{ + if (shgrab->shsurf) { + wl_list_remove(&shgrab->shsurf_destroy_listener.link); + shgrab->shsurf->grabbed = false; + } + + if (shgrab->pointer_grab.pointer) + weston_pointer_end_grab(shgrab->pointer_grab.pointer); + else if (shgrab->touch_grab.touch) + weston_touch_end_grab(shgrab->touch_grab.touch); + + free(shgrab); +} diff --git a/kiosk-shell/kiosk-shell-grab.h b/kiosk-shell/kiosk-shell-grab.h new file mode 100644 index 00000000..bf78384e --- /dev/null +++ b/kiosk-shell/kiosk-shell-grab.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WESTON_KIOSK_SHELL_GRAB_H +#define WESTON_KIOSK_SHELL_GRAB_H + +#include "kiosk-shell.h" + +enum kiosk_shell_grab_result { + KIOSK_SHELL_GRAB_RESULT_OK, + KIOSK_SHELL_GRAB_RESULT_IGNORED, + KIOSK_SHELL_GRAB_RESULT_ERROR, +}; + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, + struct weston_pointer *pointer); + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, + struct weston_touch *touch); + +#endif /* WESTON_KIOSK_SHELL_GRAB_H */ diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c new file mode 100644 index 00000000..ac9c8684 --- /dev/null +++ b/kiosk-shell/kiosk-shell.c @@ -0,0 +1,1071 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "kiosk-shell.h" +#include "kiosk-shell-grab.h" +#include "compositor/weston.h" +#include "shared/helpers.h" +#include "util.h" + +static struct kiosk_shell_surface * +get_kiosk_shell_surface(struct weston_surface *surface) +{ + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + if (desktop_surface) + return weston_desktop_surface_get_user_data(desktop_surface); + + return NULL; +} + +static void +kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data); + +static struct kiosk_shell_seat * +get_kiosk_shell_seat(struct weston_seat *seat) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&seat->destroy_signal, + kiosk_shell_seat_handle_destroy); + assert(listener != NULL); + + return container_of(listener, + struct kiosk_shell_seat, seat_destroy_listener); +} + +/* + * kiosk_shell_surface + */ + +static void +kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, + struct weston_output *output); +static void +kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, + struct kiosk_shell_surface *parent); + +static void +kiosk_shell_surface_notify_parent_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_surface *shsurf = + container_of(listener, + struct kiosk_shell_surface, parent_destroy_listener); + + kiosk_shell_surface_set_parent(shsurf, shsurf->parent->parent); +} + +static void +kiosk_shell_surface_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_surface *shsurf = + container_of(listener, + struct kiosk_shell_surface, output_destroy_listener); + + kiosk_shell_surface_set_output(shsurf, NULL); +} + +static struct kiosk_shell_surface * +kiosk_shell_surface_get_parent_root(struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_surface *root = shsurf; + while (root->parent) + root = root->parent; + return root; +} + +static bool +kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, + const char *app_id); + +static struct weston_output * +kiosk_shell_surface_find_best_output(struct kiosk_shell_surface *shsurf) +{ + struct weston_output *output; + struct kiosk_shell_output *shoutput; + struct kiosk_shell_surface *root; + const char *app_id; + + /* Always use current output if any. */ + if (shsurf->output) + return shsurf->output; + + /* Check if we have a designated output for this app. */ + app_id = weston_desktop_surface_get_app_id(shsurf->desktop_surface); + if (app_id) { + wl_list_for_each(shoutput, &shsurf->shell->output_list, link) { + if (kiosk_shell_output_has_app_id(shoutput, app_id)) + return shoutput->output; + } + } + + /* Group all related windows in the same output. */ + root = kiosk_shell_surface_get_parent_root(shsurf); + if (root->output) + return root->output; + + output = get_focused_output(shsurf->shell->compositor); + if (output) + return output; + + output = get_default_output(shsurf->shell->compositor); + if (output) + return output; + + return NULL; +} + +static void +kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, + struct weston_output *output) +{ + shsurf->output = output; + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->output) + return; + + shsurf->output_destroy_listener.notify = + kiosk_shell_surface_notify_output_destroy; + wl_signal_add(&shsurf->output->destroy_signal, + &shsurf->output_destroy_listener); +} + +static void +kiosk_shell_surface_set_fullscreen(struct kiosk_shell_surface *shsurf, + struct weston_output *output) +{ + if (!output) + output = kiosk_shell_surface_find_best_output(shsurf); + + kiosk_shell_surface_set_output(shsurf, output); + + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, true); + if (shsurf->output) + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->output->width, + shsurf->output->height); +} + +static void +kiosk_shell_surface_set_maximized(struct kiosk_shell_surface *shsurf) +{ + struct weston_output *output = + kiosk_shell_surface_find_best_output(shsurf); + + kiosk_shell_surface_set_output(shsurf, output); + + weston_desktop_surface_set_maximized(shsurf->desktop_surface, true); + if (shsurf->output) + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->output->width, + shsurf->output->height); +} + +static void +kiosk_shell_surface_set_normal(struct kiosk_shell_surface *shsurf) +{ + if (!shsurf->output) + kiosk_shell_surface_set_output(shsurf, + kiosk_shell_surface_find_best_output(shsurf)); + + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, false); + weston_desktop_surface_set_maximized(shsurf->desktop_surface, false); + weston_desktop_surface_set_size(shsurf->desktop_surface, 0, 0); +} + +static void +kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, + struct kiosk_shell_surface *parent) +{ + if (shsurf->parent_destroy_listener.notify) { + wl_list_remove(&shsurf->parent_destroy_listener.link); + shsurf->parent_destroy_listener.notify = NULL; + } + + shsurf->parent = parent; + + if (shsurf->parent) { + shsurf->parent_destroy_listener.notify = + kiosk_shell_surface_notify_parent_destroy; + wl_signal_add(&shsurf->parent->destroy_signal, + &shsurf->parent_destroy_listener); + kiosk_shell_surface_set_output(shsurf, NULL); + kiosk_shell_surface_set_normal(shsurf); + } else { + kiosk_shell_surface_set_fullscreen(shsurf, shsurf->output); + } +} + +static void +kiosk_shell_surface_reconfigure_for_output(struct kiosk_shell_surface *shsurf) +{ + struct weston_desktop_surface *desktop_surface; + + if (!shsurf->output) + return; + + desktop_surface = shsurf->desktop_surface; + + if (weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_fullscreen(desktop_surface)) { + weston_desktop_surface_set_size(desktop_surface, + shsurf->output->width, + shsurf->output->height); + } + + center_on_output(shsurf->view, shsurf->output); + weston_view_update_transform(shsurf->view); +} + +static void +kiosk_shell_surface_destroy(struct kiosk_shell_surface *shsurf) +{ + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); + shsurf->desktop_surface = NULL; + + weston_desktop_surface_unlink_view(shsurf->view); + + weston_view_destroy(shsurf->view); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (shsurf->parent_destroy_listener.notify) { + wl_list_remove(&shsurf->parent_destroy_listener.link); + shsurf->parent_destroy_listener.notify = NULL; + shsurf->parent = NULL; + } + + free(shsurf); +} + +static struct kiosk_shell_surface * +kiosk_shell_surface_create(struct kiosk_shell *shell, + struct weston_desktop_surface *desktop_surface) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + struct weston_view *view; + struct kiosk_shell_surface *shsurf; + + view = weston_desktop_surface_create_view(desktop_surface); + if (!view) + return NULL; + + shsurf = zalloc(sizeof *shsurf); + if (!shsurf) { + if (wl_client) + wl_client_post_no_memory(wl_client); + else + weston_log("no memory to allocate shell surface\n"); + return NULL; + } + + shsurf->desktop_surface = desktop_surface; + shsurf->view = view; + shsurf->shell = shell; + + weston_desktop_surface_set_user_data(desktop_surface, shsurf); + + wl_signal_init(&shsurf->destroy_signal); + + return shsurf; +} + +/* + * kiosk_shell_seat + */ + +static void +kiosk_shell_seat_handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct kiosk_shell_seat *shseat = get_kiosk_shell_seat(keyboard->seat); + + if (shseat->focused_surface) { + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(shseat->focused_surface); + if (shsurf && --shsurf->focus_count == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, + false); + } + + shseat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + + if (shseat->focused_surface) { + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(shseat->focused_surface); + if (shsurf && shsurf->focus_count++ == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, + true); + } +} + +static void +kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_seat *shseat = + container_of(listener, + struct kiosk_shell_seat, seat_destroy_listener); + + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_remove(&shseat->caps_changed_listener.link); + wl_list_remove(&shseat->seat_destroy_listener.link); + free(shseat); +} + +static void +kiosk_shell_seat_handle_caps_changed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard; + struct kiosk_shell_seat *shseat; + + shseat = container_of(listener, struct kiosk_shell_seat, + caps_changed_listener); + keyboard = weston_seat_get_keyboard(shseat->seat); + + if (keyboard && + wl_list_empty(&shseat->keyboard_focus_listener.link)) { + wl_signal_add(&keyboard->focus_signal, + &shseat->keyboard_focus_listener); + } else if (!keyboard) { + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_init(&shseat->keyboard_focus_listener.link); + } +} + +static struct kiosk_shell_seat * +kiosk_shell_seat_create(struct weston_seat *seat) +{ + struct kiosk_shell_seat *shseat; + + shseat = zalloc(sizeof *shseat); + if (!shseat) { + weston_log("no memory to allocate shell seat\n"); + return NULL; + } + + shseat->seat = seat; + + shseat->seat_destroy_listener.notify = kiosk_shell_seat_handle_destroy; + wl_signal_add(&seat->destroy_signal, &shseat->seat_destroy_listener); + + shseat->keyboard_focus_listener.notify = kiosk_shell_seat_handle_keyboard_focus; + wl_list_init(&shseat->keyboard_focus_listener.link); + + shseat->caps_changed_listener.notify = kiosk_shell_seat_handle_caps_changed; + wl_signal_add(&seat->updated_caps_signal, + &shseat->caps_changed_listener); + kiosk_shell_seat_handle_caps_changed(&shseat->caps_changed_listener, NULL); + + return shseat; +} + +/* + * kiosk_shell_output + */ + +static int +kiosk_shell_background_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + return snprintf(buf, len, "kiosk shell background surface"); +} + +static void +kiosk_shell_output_recreate_background(struct kiosk_shell_output *shoutput) +{ + struct kiosk_shell *shell = shoutput->shell; + struct weston_output *output = shoutput->output; + + if (shoutput->background_view) + weston_surface_destroy(shoutput->background_view->surface); + + if (!output) + return; + + shoutput->background_view = + create_colored_surface(shoutput->shell->compositor, + 0.5, 0.5, 0.5, + output->x, output->y, + output->width, + output->height); + + weston_surface_set_role(shoutput->background_view->surface, + "kiosk-shell-background", NULL, 0); + weston_surface_set_label_func(shoutput->background_view->surface, + kiosk_shell_background_surface_get_label); + + weston_layer_entry_insert(&shell->background_layer.view_list, + &shoutput->background_view->layer_link); + + shoutput->background_view->is_mapped = true; + shoutput->background_view->surface->is_mapped = true; + shoutput->background_view->surface->output = output; + weston_view_set_output(shoutput->background_view, output); +} + +static void +kiosk_shell_output_destroy(struct kiosk_shell_output *shoutput) +{ + shoutput->output = NULL; + shoutput->output_destroy_listener.notify = NULL; + + if (shoutput->background_view) + weston_surface_destroy(shoutput->background_view->surface); + + wl_list_remove(&shoutput->output_destroy_listener.link); + wl_list_remove(&shoutput->link); + + free(shoutput->app_ids); + + free(shoutput); +} + +static bool +kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, + const char *app_id) +{ + char *cur; + size_t app_id_len; + + if (!shoutput->app_ids) + return false; + + cur = shoutput->app_ids; + app_id_len = strlen(app_id); + + while ((cur = strstr(cur, app_id))) { + /* Check whether we have found a complete match of app_id. */ + if ((cur[app_id_len] == ',' || cur[app_id_len] == '\0') && + (cur == shoutput->app_ids || cur[-1] == ',')) + return true; + cur++; + } + + return false; +} + +static void +kiosk_shell_output_configure(struct kiosk_shell_output *shoutput) +{ + struct weston_config *wc = wet_get_config(shoutput->shell->compositor); + struct weston_config_section *section = + weston_config_get_section(wc, "output", "name", shoutput->output->name); + + assert(shoutput->app_ids == NULL); + + if (section) { + weston_config_section_get_string(section, "app-ids", + &shoutput->app_ids, NULL); + } +} + +static void +kiosk_shell_output_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_output *shoutput = + container_of(listener, + struct kiosk_shell_output, output_destroy_listener); + + kiosk_shell_output_destroy(shoutput); +} + +static struct kiosk_shell_output * +kiosk_shell_output_create(struct kiosk_shell *shell, struct weston_output *output) +{ + struct kiosk_shell_output *shoutput; + + shoutput = zalloc(sizeof *shoutput); + if (shoutput == NULL) + return NULL; + + shoutput->output = output; + shoutput->shell = shell; + + shoutput->output_destroy_listener.notify = + kiosk_shell_output_notify_output_destroy; + wl_signal_add(&shoutput->output->destroy_signal, + &shoutput->output_destroy_listener); + + wl_list_insert(shell->output_list.prev, &shoutput->link); + + kiosk_shell_output_recreate_background(shoutput); + kiosk_shell_output_configure(shoutput); + + return shoutput; +} + +/* + * libweston-desktop + */ + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct kiosk_shell *shell = data; + struct kiosk_shell_surface *shsurf; + struct weston_seat *seat; + + shsurf = kiosk_shell_surface_create(shell, desktop_surface); + if (!shsurf) + return; + + kiosk_shell_surface_set_fullscreen(shsurf, NULL); + + wl_list_for_each(seat, &shell->compositor->seat_list, link) + weston_view_activate(shsurf->view, seat, 0); +} + +/* Return the view that should gain focus after the specified shsurf is + * destroyed. We prefer the top remaining view from the same parent surface, + * but if we can't find one we fall back to the top view regardless of + * parentage. */ +static struct weston_view * +find_focus_successor(struct weston_layer *layer, + struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_surface *parent_root = + kiosk_shell_surface_get_parent_root(shsurf); + struct weston_view *top_view = NULL; + struct weston_view *view; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + struct kiosk_shell_surface *view_shsurf; + struct kiosk_shell_surface *root; + + if (!view->is_mapped || view == shsurf->view) + continue; + + view_shsurf = get_kiosk_shell_surface(view->surface); + if (!view_shsurf) + continue; + + if (!top_view) + top_view = view; + + root = kiosk_shell_surface_get_parent_root(view_shsurf); + if (root == parent_root) + return view; + } + + return top_view; +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct kiosk_shell *shell = data; + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_view *focus_view; + struct weston_seat *seat; + + if (!shsurf) + return; + + focus_view = find_focus_successor(&shell->normal_layer, shsurf); + + if (focus_view) { + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = seat->keyboard_state; + if (keyboard && keyboard->focus == surface) + weston_view_activate(focus_view, seat, 0); + } + } + + kiosk_shell_surface_destroy(shsurf); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + int32_t sx, int32_t sy, void *data) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + bool is_resized; + bool is_fullscreen; + + if (surface->width == 0) + return; + + /* TODO: When the top-level surface is committed with a new size after an + * output resize, sometimes the view appears scaled. What state are we not + * updating? + */ + + is_resized = surface->width != shsurf->last_width || + surface->height != shsurf->last_height; + is_fullscreen = weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_fullscreen(desktop_surface); + + if (!weston_surface_is_mapped(surface) || (is_resized && is_fullscreen)) { + if (is_fullscreen || !shsurf->xwayland.is_set) { + center_on_output(shsurf->view, shsurf->output); + } else { + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(desktop_surface); + float x = shsurf->xwayland.x - geometry.x; + float y = shsurf->xwayland.y - geometry.y; + + weston_view_set_position(shsurf->view, x, y); + } + + weston_view_update_transform(shsurf->view); + } + + if (!weston_surface_is_mapped(surface)) { + weston_layer_entry_insert(&shsurf->shell->normal_layer.view_list, + &shsurf->view->layer_link); + shsurf->view->is_mapped = true; + surface->is_mapped = true; + } + + if (!is_fullscreen && (sx != 0 || sy != 0)) { + float from_x, from_y; + float to_x, to_y; + float x, y; + + weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); + weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); + x = shsurf->view->geometry.x + to_x - from_x; + y = shsurf->view->geometry.y + to_y - from_y; + + weston_view_set_position(shsurf->view, x, y); + weston_view_update_transform(shsurf->view); + } + + shsurf->last_width = surface->width; + shsurf->last_height = surface->height; +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface *focus; + + if (pointer && + pointer->focus && + pointer->button_count > 0 && + pointer->grab_serial == serial) { + focus = weston_surface_get_main_surface(pointer->focus->surface); + if ((focus == surface) && + (kiosk_shell_grab_start_for_pointer_move(shsurf, pointer) == + KIOSK_SHELL_GRAB_RESULT_ERROR)) + wl_resource_post_no_memory(surface->resource); + } + else if (touch && + touch->focus && + touch->grab_serial == serial) { + focus = weston_surface_get_main_surface(touch->focus->surface); + if ((focus == surface) && + (kiosk_shell_grab_start_for_touch_move(shsurf, touch) == + KIOSK_SHELL_GRAB_RESULT_ERROR)) + wl_resource_post_no_memory(surface->resource); + } +} + +static void +desktop_surface_resize(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *shell) +{ +} + +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct kiosk_shell_surface *shsurf_parent = + parent ? weston_desktop_surface_get_user_data(parent) : NULL; + + kiosk_shell_surface_set_parent(shsurf, shsurf_parent); +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, + bool fullscreen, + struct weston_output *output, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* We should normally be able to ignore fullscreen requests for + * top-level surfaces, since we set them as fullscreen at creation + * time. However, xwayland surfaces set their internal WM state + * regardless of what the shell wants, so they may remove fullscreen + * state before informing weston-desktop of this request. Since we + * always want top-level surfaces to be fullscreen, we need to reapply + * the fullscreen state to force the correct xwayland WM state. + * + * TODO: Explore a model where the XWayland WM doesn't set the internal + * WM surface state itself, rather letting the shell make the decision. + */ + + if (!shsurf->parent || fullscreen) + kiosk_shell_surface_set_fullscreen(shsurf, output); + else + kiosk_shell_surface_set_normal(shsurf); +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* Since xwayland surfaces may have already applied the max/min states + * internally, reapply fullscreen to force the correct xwayland WM state. + * Also see comment in desktop_surface_fullscreen_requested(). */ + if (!shsurf->parent) + kiosk_shell_surface_set_fullscreen(shsurf, NULL); + else if (maximized) + kiosk_shell_surface_set_maximized(shsurf); + else + kiosk_shell_surface_set_normal(shsurf); +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, + void *shell) +{ +} + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, + void *shell_) +{ +} + +static void +desktop_surface_pong(struct weston_desktop_client *desktop_client, + void *shell_) +{ +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *desktop_surface, + int32_t x, int32_t y, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + shsurf->xwayland.x = x; + shsurf->xwayland.y = y; + shsurf->xwayland.is_set = true; +} + +static const struct weston_desktop_api kiosk_shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .set_parent = desktop_surface_set_parent, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .set_xwayland_position = desktop_surface_set_xwayland_position, +}; + +/* + * kiosk_shell + */ + +static struct kiosk_shell_output * +kiosk_shell_find_shell_output(struct kiosk_shell *shell, + struct weston_output *output) +{ + struct kiosk_shell_output *shoutput; + + wl_list_for_each(shoutput, &shell->output_list, link) { + if (shoutput->output == output) + return shoutput; + } + + return NULL; +} + +static void +kiosk_shell_activate_view(struct kiosk_shell *shell, + struct weston_view *view, + struct weston_seat *seat, + uint32_t flags) +{ + struct weston_surface *main_surface = + weston_surface_get_main_surface(view->surface); + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(main_surface); + + if (!shsurf) + return; + + /* If the view belongs to a child window bring it to the front. + * We don't do this for the parent top-level, since that would + * obscure all children. + */ + if (shsurf->parent) { + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&shell->normal_layer.view_list, + &view->layer_link); + weston_view_geometry_dirty(view); + weston_surface_damage(view->surface); + } + + weston_view_activate(view, seat, flags); +} + +static void +kiosk_shell_click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + struct kiosk_shell *shell = data; + + if (pointer->grab != &pointer->default_grab) + return; + if (pointer->focus == NULL) + return; + + kiosk_shell_activate_view(shell, pointer->focus, pointer->seat, + WESTON_ACTIVATE_FLAG_CLICKED); +} + +static void +kiosk_shell_touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + struct kiosk_shell *shell = data; + + if (touch->grab != &touch->default_grab) + return; + if (touch->focus == NULL) + return; + + kiosk_shell_activate_view(shell, touch->focus, touch->seat, + WESTON_ACTIVATE_FLAG_NONE); +} + +static void +kiosk_shell_add_bindings(struct kiosk_shell *shell) +{ + weston_compositor_add_button_binding(shell->compositor, BTN_LEFT, 0, + kiosk_shell_click_to_activate_binding, + shell); + weston_compositor_add_button_binding(shell->compositor, BTN_RIGHT, 0, + kiosk_shell_click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(shell->compositor, 0, + kiosk_shell_touch_to_activate_binding, + shell); +} + +static void +kiosk_shell_handle_output_created(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_created_listener); + struct weston_output *output = data; + + kiosk_shell_output_create(shell, output); +} + +static void +kiosk_shell_handle_output_resized(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_resized_listener); + struct weston_output *output = data; + struct kiosk_shell_output *shoutput = + kiosk_shell_find_shell_output(shell, output); + struct weston_view *view; + + kiosk_shell_output_recreate_background(shoutput); + + wl_list_for_each(view, &shell->normal_layer.view_list.link, + layer_link.link) { + struct kiosk_shell_surface *shsurf; + if (view->output != output) + continue; + shsurf = get_kiosk_shell_surface(view->surface); + if (!shsurf) + continue; + kiosk_shell_surface_reconfigure_for_output(shsurf); + } +} + +static void +kiosk_shell_handle_output_moved(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_moved_listener); + struct weston_output *output = data; + struct weston_view *view; + + wl_list_for_each(view, &shell->background_layer.view_list.link, + layer_link.link) { + if (view->output != output) + continue; + weston_view_set_position(view, + view->geometry.x + output->move_x, + view->geometry.y + output->move_y); + } + + wl_list_for_each(view, &shell->normal_layer.view_list.link, + layer_link.link) { + if (view->output != output) + continue; + weston_view_set_position(view, + view->geometry.x + output->move_x, + view->geometry.y + output->move_y); + } +} + +static void +kiosk_shell_handle_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + kiosk_shell_seat_create(seat); +} + +static void +kiosk_shell_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, destroy_listener); + struct kiosk_shell_output *shoutput, *tmp; + + wl_list_remove(&shell->destroy_listener.link); + wl_list_remove(&shell->output_created_listener.link); + wl_list_remove(&shell->output_resized_listener.link); + wl_list_remove(&shell->output_moved_listener.link); + wl_list_remove(&shell->seat_created_listener.link); + + wl_list_for_each_safe(shoutput, tmp, &shell->output_list, link) { + kiosk_shell_output_destroy(shoutput); + } + + weston_desktop_destroy(shell->desktop); + + free(shell); +} + +WL_EXPORT int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct kiosk_shell *shell; + struct weston_seat *seat; + struct weston_output *output; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = ec; + + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + kiosk_shell_destroy)) { + free(shell); + return 0; + } + + weston_layer_init(&shell->background_layer, ec); + weston_layer_init(&shell->normal_layer, ec); + + weston_layer_set_position(&shell->background_layer, + WESTON_LAYER_POSITION_BACKGROUND); + /* We use the NORMAL layer position, so that xwayland surfaces, which + * are placed at NORMAL+1, are visible. */ + weston_layer_set_position(&shell->normal_layer, + WESTON_LAYER_POSITION_NORMAL); + + shell->desktop = weston_desktop_create(ec, &kiosk_shell_desktop_api, + shell); + if (!shell->desktop) + return -1; + + wl_list_for_each(seat, &ec->seat_list, link) + kiosk_shell_seat_create(seat); + shell->seat_created_listener.notify = kiosk_shell_handle_seat_created; + wl_signal_add(&ec->seat_created_signal, &shell->seat_created_listener); + + wl_list_init(&shell->output_list); + wl_list_for_each(output, &ec->output_list, link) + kiosk_shell_output_create(shell, output); + + shell->output_created_listener.notify = kiosk_shell_handle_output_created; + wl_signal_add(&ec->output_created_signal, &shell->output_created_listener); + + shell->output_resized_listener.notify = kiosk_shell_handle_output_resized; + wl_signal_add(&ec->output_resized_signal, &shell->output_resized_listener); + + shell->output_moved_listener.notify = kiosk_shell_handle_output_moved; + wl_signal_add(&ec->output_moved_signal, &shell->output_moved_listener); + + kiosk_shell_add_bindings(shell); + + return 0; +} diff --git a/kiosk-shell/kiosk-shell.h b/kiosk-shell/kiosk-shell.h new file mode 100644 index 00000000..09f5a777 --- /dev/null +++ b/kiosk-shell/kiosk-shell.h @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WESTON_KIOSK_SHELL_H +#define WESTON_KIOSK_SHELL_H + +#include +#include + +struct kiosk_shell { + struct weston_compositor *compositor; + struct weston_desktop *desktop; + + struct wl_listener destroy_listener; + struct wl_listener output_created_listener; + struct wl_listener output_resized_listener; + struct wl_listener output_moved_listener; + struct wl_listener seat_created_listener; + + struct weston_layer background_layer; + struct weston_layer normal_layer; + + struct wl_list output_list; +}; + +struct kiosk_shell_surface { + struct weston_desktop_surface *desktop_surface; + struct weston_view *view; + + struct kiosk_shell *shell; + + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct wl_signal destroy_signal; + struct wl_listener parent_destroy_listener; + struct kiosk_shell_surface *parent; + + int focus_count; + + int32_t last_width, last_height; + bool grabbed; + + struct { + bool is_set; + int32_t x; + int32_t y; + } xwayland; +}; + +struct kiosk_shell_seat { + struct weston_seat *seat; + struct wl_listener seat_destroy_listener; + struct weston_surface *focused_surface; + + struct wl_listener caps_changed_listener; + struct wl_listener keyboard_focus_listener; +}; + +struct kiosk_shell_output { + struct weston_output *output; + struct wl_listener output_destroy_listener; + struct weston_view *background_view; + + struct kiosk_shell *shell; + struct wl_list link; + + char *app_ids; +}; + +#endif /* WESTON_KIOSK_SHELL_H */ diff --git a/kiosk-shell/meson.build b/kiosk-shell/meson.build new file mode 100644 index 00000000..e838614e --- /dev/null +++ b/kiosk-shell/meson.build @@ -0,0 +1,29 @@ +if get_option('shell-kiosk') + srcs_shell_kiosk = [ + 'kiosk-shell.c', + 'kiosk-shell-grab.c', + 'util.c', + weston_desktop_shell_server_protocol_h, + weston_desktop_shell_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + deps_shell_kiosk = [ + dep_libm, + dep_libexec_weston, + dep_libshared, + dep_lib_desktop, + dep_libweston_public, + ] + plugin_shell_kiosk = shared_library( + 'kiosk-shell', + srcs_shell_kiosk, + include_directories: common_inc, + dependencies: deps_shell_kiosk, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'kiosk-shell.so=@0@;'.format(plugin_shell_kiosk.full_path()) +endif diff --git a/kiosk-shell/util.c b/kiosk-shell/util.c new file mode 100644 index 00000000..ad3e0d9b --- /dev/null +++ b/kiosk-shell/util.c @@ -0,0 +1,168 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* Helper functions for kiosk-shell */ + +/* TODO: These functions are useful to many shells, and, in fact, + * much of content in this file was copied from desktop-shell. We should + * create a shared shell utility collection to deduplicate this code. */ + +#include "util.h" +#include "shared/helpers.h" +#include + +struct weston_output * +get_default_output(struct weston_compositor *compositor) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +struct weston_output * +get_focused_output(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_output *output = NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + /* Priority has touch focus, then pointer and + * then keyboard focus. We should probably have + * three for loops and check first for touch, + * then for pointer, etc. but unless somebody has some + * objections, I think this is sufficient. */ + if (touch && touch->focus) + output = touch->focus->output; + else if (pointer && pointer->focus) + output = pointer->focus->output; + else if (keyboard && keyboard->focus) + output = keyboard->focus->output; + + if (output) + break; + } + + return output; +} + +/* This is a copy of the same function from desktop-shell. + * TODO: Fix this function to take into account nested subsurfaces. */ +static void +surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, + int32_t *y, int32_t *w, int32_t *h) { + pixman_region32_t region; + pixman_box32_t *box; + struct weston_subsurface *subsurface; + + pixman_region32_init_rect(®ion, 0, 0, + surface->width, + surface->height); + + wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { + pixman_region32_union_rect(®ion, ®ion, + subsurface->position.x, + subsurface->position.y, + subsurface->surface->width, + subsurface->surface->height); + } + + box = pixman_region32_extents(®ion); + if (x) + *x = box->x1; + if (y) + *y = box->y1; + if (w) + *w = box->x2 - box->x1; + if (h) + *h = box->y2 - box->y1; + + pixman_region32_fini(®ion); +} + +void +center_on_output(struct weston_view *view, struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + + surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, &width, &height); + + x = output->x + (output->width - width) / 2 - surf_x / 2; + y = output->y + (output->height - height) / 2 - surf_y / 2; + + weston_view_set_position(view, x, y); +} + +static void +colored_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +struct weston_view * +create_colored_surface(struct weston_compositor *compositor, + float r, float g, float b, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(compositor); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + view = weston_view_create(surface); + if (surface == NULL) { + weston_log("no memory\n"); + weston_surface_destroy(surface); + return NULL; + } + + surface->committed = colored_surface_committed; + surface->committed_private = NULL; + + weston_surface_set_color(surface, r, g, b, 1.0); + pixman_region32_fini(&surface->opaque); + pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, w, h); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} diff --git a/kiosk-shell/util.h b/kiosk-shell/util.h new file mode 100644 index 00000000..e60aa3b5 --- /dev/null +++ b/kiosk-shell/util.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* Helper functions adapted from desktop-shell */ + +#include + +struct weston_compositor; +struct weston_layer; +struct weston_output; +struct weston_surface; +struct weston_view; + +struct weston_output * +get_default_output(struct weston_compositor *compositor); + +struct weston_output * +get_focused_output(struct weston_compositor *compositor); + +void +center_on_output(struct weston_view *view, struct weston_output *output); + +struct weston_view * +create_colored_surface(struct weston_compositor *compositor, + float r, float g, float b, + float x, float y, int w, int h); diff --git a/man/weston.ini.man b/man/weston.ini.man index bce5628b..e1364c63 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -554,6 +554,12 @@ content-protection can actually be realized, only if the hardware (source and sink) support HDCP, and the backend has the implementation of content-protection protocol. Currently, HDCP is supported by drm-backend. .RE +.TP 7 +.BI "app-ids=" app-id[,app_id]* +A comma separated list of the IDs of applications to place on this output. +These IDs should match the application IDs as set with the xdg_shell.set_app_id +request. Currently, this option is supported by kiosk-shell. +.RE .SH "INPUT-METHOD SECTION" .TP 7 .BI "path=" "@weston_libexecdir@/weston-keyboard" diff --git a/meson.build b/meson.build index 090fc713..862f54cf 100644 --- a/meson.build +++ b/meson.build @@ -158,6 +158,7 @@ subdir('compositor') subdir('desktop-shell') subdir('fullscreen-shell') subdir('ivi-shell') +subdir('kiosk-shell') subdir('remoting') subdir('pipewire') subdir('clients') diff --git a/meson_options.txt b/meson_options.txt index 9f6b08c3..239bd2da 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -124,6 +124,12 @@ option( value: true, description: 'Weston shell UI: IVI (automotive)' ) +option( + 'shell-kiosk', + type: 'boolean', + value: true, + description: 'Weston shell UI: kiosk (desktop apps)' +) option( 'desktop-shell-client-default',