summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandros Frantzis <alexandros.frantzis@collabora.com>2020-04-21 18:23:37 +0300
committerPekka Paalanen <pq@iki.fi>2020-07-30 14:38:49 +0000
commit87c1679a0a4c6de48a9f87b6b17bf4df63de1e6b (patch)
tree5e41d8be020d10262098b34668e7bb204bf9963e
parentea4d13b3e30a599c4cbfec6a0c57f5e952ad25ac (diff)
kiosk-shell: Introduce kiosk/fullscreen shell for desktop apps
kiosk-shell is fullscreen shell for apps that use the xdg-shell protocol. The goal is to make life easier for people shipping embedded devices with simple fullscreen shell requirements, and reduce the proliferation of desktop-shell hacks. Top level surfaces are made fullscreen, whereas dialogs are placed on top in the center of the output and retain their natural sizes. Dialogs can be moved and (un)maximized, but resizing is currently not supported. An app can be directed to a particular output by populating the "app-ids" field with the app's XDG app id, in the relevant "[output]" section in the weston config file. Fixes: #277 Signed-off-by: Alexandros Frantzis <alexandros.frantzis@collabora.com>
-rw-r--r--doc/sphinx/index.rst1
-rw-r--r--doc/sphinx/toc/kiosk-shell.rst17
-rw-r--r--doc/sphinx/toc/meson.build1
-rw-r--r--kiosk-shell/kiosk-shell-grab.c314
-rw-r--r--kiosk-shell/kiosk-shell-grab.h43
-rw-r--r--kiosk-shell/kiosk-shell.c1071
-rw-r--r--kiosk-shell/kiosk-shell.h91
-rw-r--r--kiosk-shell/meson.build29
-rw-r--r--kiosk-shell/util.c168
-rw-r--r--kiosk-shell/util.h48
-rw-r--r--man/weston.ini.man6
-rw-r--r--meson.build1
-rw-r--r--meson_options.txt6
13 files changed, 1796 insertions, 0 deletions
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 <assert.h>
+#include <linux/input.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 <libweston-desktop/libweston-desktop.h>
+#include <libweston/libweston.h>
+
+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 <libweston/libweston.h>
+
+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(&region, 0, 0,
+ surface->width,
+ surface->height);
+
+ wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) {
+ pixman_region32_union_rect(&region, &region,
+ subsurface->position.x,
+ subsurface->position.y,
+ subsurface->surface->width,
+ subsurface->surface->height);
+ }
+
+ box = pixman_region32_extents(&region);
+ 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(&region);
+}
+
+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 <stdint.h>
+
+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',