From e91eccd70981446f899d32cf179c0c2b04be9a17 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 1 Nov 2021 19:35:05 +0000 Subject: [PATCH] lua-shell: Add lua-shell lua-shell is a new meta-shell for Weston. It does nothing in and of itself, but is defined to be user-scriptable and configurable. A supplied Lua script will be interpreted and executed by Weston, allowing full control over window management in response to events. This includes a shell.lua example script which behaves as a tiling WM. Co-authored-by: Michael Tretter Co-authored-by: Marius Vlad Signed-off-by: Derek Foreman --- .gitlab-ci.yml | 2 +- .gitlab-ci/debian-install.sh | 1 + frontend/main.c | 6 + lua-shell/lua-shell.c | 2220 ++++++++++++++++++++++++ lua-shell/lua-shell.h | 259 +++ lua-shell/meson.build | 33 + lua-shell/shell.lua | 338 ++++ man/weston.ini.man | 4 + man/weston.man | 12 + meson.build | 1 + meson_options.txt | 6 + tests/lua-shell-test.c | 173 ++ tests/meson.build | 14 + tests/weston-test-fixture-compositor.c | 1 + tests/weston-test-fixture-compositor.h | 2 + weston.ini.in | 1 + 16 files changed, 3072 insertions(+), 1 deletion(-) create mode 100644 lua-shell/lua-shell.c create mode 100644 lua-shell/lua-shell.h create mode 100644 lua-shell/meson.build create mode 100644 lua-shell/shell.lua create mode 100644 tests/lua-shell-test.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f3a52b396..8d1ecb77d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ variables: FDO_UPSTREAM_REPO: wayland/weston FDO_REPO_SUFFIX: "$BUILD_OS-$FDO_DISTRIBUTION_VERSION/$BUILD_ARCH" - FDO_DISTRIBUTION_TAG: '2025-05-15-vulkan' + FDO_DISTRIBUTION_TAG: '2025-05-21-liblua5.4-dev' include: diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index eb410c70d..1fe7d57f3 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -69,6 +69,7 @@ apt-get -y --no-install-recommends install \ libjack-jackd2-dev \ libjpeg-dev \ libjpeg-dev \ + liblua5.4-dev \ libmtdev-dev \ libpam0g-dev \ libpango1.0-dev \ diff --git a/frontend/main.c b/frontend/main.c index d2909a254..ee58356e8 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -825,6 +825,12 @@ usage(int error_code) " --no-input\t\tDont create input devices\n\n"); #endif +#if defined(BUILD_LUA_SHELL) + fprintf(out, + "Options for lua-shell:\n\n" + " --lua-script=script\t\tLua script\n\n"); +#endif + exit(error_code); } diff --git a/lua-shell/lua-shell.c b/lua-shell/lua-shell.c new file mode 100644 index 000000000..82f7584f8 --- /dev/null +++ b/lua-shell/lua-shell.c @@ -0,0 +1,2220 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020,2025 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 + +#include "lua-shell.h" +#include "frontend/weston.h" +#include "shared/helpers.h" +#include "shared/weston-assert.h" +#include "shared/xalloc.h" +#include "libweston/shell-utils.h" + +#include + +static void +lua_dump_stack(struct lua_State *L) +{ + int top=lua_gettop(L); + + weston_log("=== Lua Shell stack dump ===\n"); + for (int i=1; i <= top; i++) { + weston_log_continue(STAMP_SPACE "%d\t%s\t", i, luaL_typename(L,i)); + switch (lua_type(L, i)) { + case LUA_TNIL: + weston_log_continue(STAMP_SPACE "%s\n", "nil"); + break; + case LUA_TNUMBER: + weston_log_continue(STAMP_SPACE "%g\n",lua_tonumber(L,i)); + break; + case LUA_TBOOLEAN: + weston_log_continue(STAMP_SPACE "%s\n", (lua_toboolean(L, i) ? "true" : "false")); + break; + case LUA_TSTRING: + weston_log_continue(STAMP_SPACE "%s\n",lua_tostring(L,i)); + break; + case LUA_TTABLE: + case LUA_TFUNCTION: + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: + case LUA_TTHREAD: + default: + weston_log_continue(STAMP_SPACE "%p\n",lua_topointer(L,i)); + break; + } + } + weston_log_continue(STAMP_SPACE "============================\n"); +} + +static void * +lxzalloc(struct lua_State *lua, size_t size, const char *meta) +{ + struct lua_object *allocation; + void **obj; + uint32_t regid; + + allocation = xzalloc(size); + obj = lua_newuserdata(lua, sizeof(*obj)); + *obj = allocation; + luaL_getmetatable(lua, meta); + lua_setmetatable(lua, -2); + regid = luaL_ref(lua, LUA_REGISTRYINDEX); + allocation->lua_regid = regid; + + return allocation; +} + +static void +lfree(struct lua_State *lua, void *data) +{ + struct lua_object *obj = data; + int lua_regid = obj->lua_regid; + + if (obj->lua_private_regid) + luaL_unref(lua, LUA_REGISTRYINDEX, + obj->lua_private_regid); + obj->lua_private_regid = 0; + + obj->lua_regid = 0; + luaL_unref(lua, LUA_REGISTRYINDEX, lua_regid); + + /* DO NOT FREE! LUA objects are freed from the garbage + * collector later. + */ +} + +static int +lgc(struct lua_State *lua) +{ + struct lua_object **udata = lua_touserdata(lua, 1); + struct lua_object *obj = *udata; + + assert(obj->lua_regid == 0); + assert(obj->lua_private_regid == 0); + + free(obj); + + return 0; +} + +static bool +lua_shell_push_function(struct lua_shell *shell, enum lua_shell_cb_id id) +{ + struct lua_State *lua = shell->lua; + + if (!shell->callbacks[id].regid) + return false; + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shell->callbacks[id].regid); + + assert(lua_isfunction(lua, -1)); + + return true; +} + +static bool +lua_shell_call_function(struct lua_shell *shell, const char *func, + int args, int rets) +{ + weston_assert_true(shell->compositor, + lua_isfunction(shell->lua, -(args + 1))); + + if (lua_pcall(shell->lua, args, rets, 0) != 0) { + weston_log("error from function '%s': %s\n", + func, lua_tostring(shell->lua, -1)); + lua_dump_stack(shell->lua); + + return false; + } + return true; +} + +static struct lua_shell_surface * +get_lua_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 +lua_shell_seat_handle_destroy(struct wl_listener *listener, void *data); + +static struct lua_shell_seat * +get_lua_shell_seat(struct weston_seat *seat) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&seat->destroy_signal, + lua_shell_seat_handle_destroy); + assert(listener != NULL); + + return container_of(listener, + struct lua_shell_seat, seat_destroy_listener); +} + +static void +lua_shell_view_handle_destroy(struct wl_listener *listener, void *data) +{ + /* We don't really care. We use this to help get lua_shell_view + * from a weston_view + */ +} + +static struct lua_shell_view * +get_lua_shell_view(struct weston_view *view) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&view->destroy_signal, + lua_shell_view_handle_destroy); + if (!listener) + return NULL; + + return container_of(listener, + struct lua_shell_view, view_destroy_listener); +} + +/* + * lua_shell_surface + */ + +static void +lua_shell_surface_set_output(struct lua_shell_surface *shsurf, + struct lua_shell_output *shoutput); +static void +lua_shell_surface_set_parent(struct lua_shell_surface *shsurf, + struct lua_shell_surface *parent); + +static void +lua_shell_surface_notify_parent_destroy(struct wl_listener *listener, void *data) +{ + struct lua_shell_surface *shsurf = + container_of(listener, + struct lua_shell_surface, parent_destroy_listener); + + lua_shell_surface_set_parent(shsurf, shsurf->parent->parent); +} + +static void +lua_shell_surface_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct lua_shell_surface *shsurf = + container_of(listener, + struct lua_shell_surface, output_destroy_listener); + + lua_shell_surface_set_output(shsurf, NULL); +} + +static void +lua_shell_surface_set_output(struct lua_shell_surface *shsurf, + struct lua_shell_output *shoutput) +{ + shsurf->shoutput = shoutput; + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->shoutput) + return; + + shsurf->output_destroy_listener.notify = + lua_shell_surface_notify_output_destroy; + wl_signal_add(&shsurf->shoutput->output->destroy_signal, + &shsurf->output_destroy_listener); +} + +static void +lua_shell_surface_set_parent(struct lua_shell_surface *shsurf, + struct lua_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) + return; + + shsurf->parent_destroy_listener.notify = + lua_shell_surface_notify_parent_destroy; + wl_signal_add(&shsurf->parent->destroy_signal, + &shsurf->parent_destroy_listener); +} + +static void +lua_shell_view_dispose(struct lua_shell_view *shview) +{ + wl_list_remove(&shview->view_destroy_listener.link); + wl_list_remove(&shview->surface_link); + wl_list_remove(&shview->link); + + if (shview->is_desktop_surface) { + weston_desktop_surface_unlink_view(shview->view); + weston_view_destroy(shview->view); + } + + lfree(shview->shell->lua, shview); +} + +static void +lua_shell_layer_dispose(struct lua_shell_layer *shlayer) +{ + wl_list_remove(&shlayer->link); + + weston_layer_fini(&shlayer->layer); + + lfree(shlayer->shell->lua, shlayer); +} + +static void +lua_shell_curtain_dispose(struct lua_shell_curtain *shcurtain) +{ + wl_list_remove(&shcurtain->link); + + free(shcurtain->name); + weston_shell_utils_curtain_destroy(shcurtain->curtain); + + lfree(shcurtain->shell->lua, shcurtain); +} + +static void +lua_shell_surface_dispose(struct lua_shell_surface *shsurf) +{ + struct lua_shell_view *shview, *tmp; + + wl_list_remove(&shsurf->link); + + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); + shsurf->desktop_surface = NULL; + + 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; + } + + wl_list_for_each_safe(shview, tmp, &shsurf->view_list, surface_link) + lua_shell_view_dispose(shview); + + lfree(shsurf->shell->lua, shsurf); +} + +static struct lua_shell_surface * +lua_shell_surface_added(struct lua_shell *shell, + struct weston_desktop_surface *desktop_surface) +{ + struct lua_shell_surface *shsurf; + + shsurf = lxzalloc(shell->lua, sizeof *shsurf, "weston.surface"); + + shsurf->shell = shell; + shsurf->desktop_surface = desktop_surface; + wl_list_init(&shsurf->view_list); + + weston_desktop_surface_set_user_data(desktop_surface, shsurf); + + wl_signal_init(&shsurf->destroy_signal); + + wl_list_insert(shell->surface_list.prev, &shsurf->link); + + if (!lua_shell_push_function(shell, LUA_SHELL_CB_SURFACE_ADDED)) + return shsurf; + + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_shell_call_function(shell, "surface_added", 1, 0); + + return shsurf; +} + +/* + * lua_shell_seat + */ + +static void +lua_shell_seat_handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct lua_shell_seat *shseat = get_lua_shell_seat(keyboard->seat); + struct lua_shell *shell = shseat->shell; + + if (!lua_shell_push_function(shell, LUA_SHELL_CB_KEYBOARD_FOCUS)) + return; + + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_shell_call_function(shell, "keyboard_focus", 1, 0); +} + +static void +lua_shell_seat_destroy(struct lua_shell_seat *shseat) +{ + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_remove(&shseat->caps_changed_listener.link); + wl_list_remove(&shseat->seat_destroy_listener.link); + + wl_list_remove(&shseat->link); + + lfree(shseat->shell->lua, shseat); +} + +static void +lua_shell_seat_handle_destroy(struct wl_listener *listener, void *data) +{ + struct lua_shell_seat *shseat = + container_of(listener, + struct lua_shell_seat, seat_destroy_listener); + + lua_shell_seat_destroy(shseat); +} + +static void +lua_shell_seat_handle_caps_changed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard; + struct lua_shell_seat *shseat; + + shseat = container_of(listener, struct lua_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 lua_shell_seat * +lua_shell_seat_create(struct lua_shell *shell, struct weston_seat *seat) +{ + struct lua_shell_seat *shseat; + + shseat = lxzalloc(shell->lua, sizeof *shseat, "weston.seat"); + + shseat->seat = seat; + shseat->shell = shell; + + shseat->seat_destroy_listener.notify = lua_shell_seat_handle_destroy; + wl_signal_add(&seat->destroy_signal, &shseat->seat_destroy_listener); + + shseat->keyboard_focus_listener.notify = lua_shell_seat_handle_keyboard_focus; + wl_list_init(&shseat->keyboard_focus_listener.link); + + shseat->caps_changed_listener.notify = lua_shell_seat_handle_caps_changed; + wl_signal_add(&seat->updated_caps_signal, + &shseat->caps_changed_listener); + lua_shell_seat_handle_caps_changed(&shseat->caps_changed_listener, NULL); + + wl_list_insert(&shell->seat_list, &shseat->link); + + if (!lua_shell_push_function(shell, LUA_SHELL_CB_SEAT_CREATE)) + return shseat; + + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_shell_call_function(shell, "seat_create", 1, 0); + return shseat; +} + +/* + * lua_shell_output + */ + +static void +lua_shell_output_destroy(struct lua_shell_output *shoutput) +{ + shoutput->output = NULL; + shoutput->output_destroy_listener.notify = NULL; + + wl_list_remove(&shoutput->output_destroy_listener.link); + wl_list_remove(&shoutput->link); + + lfree(shoutput->shell->lua, shoutput); +} + +static void +lua_shell_output_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct lua_shell_output *shoutput = + container_of(listener, + struct lua_shell_output, output_destroy_listener); + + lua_shell_output_destroy(shoutput); +} + +static struct lua_shell_output * +lua_shell_output_create(struct lua_shell *shell, struct weston_output *output) +{ + struct lua_shell_output *shoutput; + + shoutput = lxzalloc(shell->lua, sizeof *shoutput, "weston.output"); + + shoutput->output = output; + shoutput->shell = shell; + + shoutput->output_destroy_listener.notify = + lua_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); + + weston_output_set_shell_private(output, shoutput); + if (!lua_shell_push_function(shell, LUA_SHELL_CB_OUTPUT_CREATE)) + return shoutput; + + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shoutput->lua_regid); + lua_shell_call_function(shell, "output_create", 1, 0); + + return shoutput; +} + +/* + * libweston-desktop + */ + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct lua_shell *shell = data; + struct lua_shell_surface *shsurf; + + shsurf = lua_shell_surface_added(shell, desktop_surface); + if (!shsurf) + return; + + weston_surface_set_label_func(surface, + weston_shell_utils_surface_get_label); +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct lua_shell *shell = data; + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + if (!shsurf) + return; + + if (!lua_shell_push_function(shell, LUA_SHELL_CB_SURFACE_REMOVED)) + return; + + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_shell_call_function(shell, "surface_removed", 1, 0); + + lua_shell_surface_dispose(shsurf); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + struct weston_coord_surface buf_offset, void *data) +{ + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct lua_State *lua = shsurf->shell->lua; + + if (!lua_shell_push_function(shsurf->shell, LUA_SHELL_CB_SURFACE_COMMITTED)) + return; + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_shell_call_function(shsurf->shell, "surface_committed", 1, 0); +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ + struct lua_shell *ls = shell; + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct lua_shell_seat *shseat = + get_lua_shell_seat(seat); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_SURFACE_MOVE)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_pushnumber(ls->lua, serial); + lua_shell_call_function(ls, "surface_move", 3, 0); +} + +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) +{ + struct lua_shell *ls = shell; + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct lua_shell_seat *shseat = + get_lua_shell_seat(seat); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_SURFACE_RESIZE)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_pushnumber(ls->lua, edges); + lua_shell_call_function(ls, "surface_resize", 3, 0); +} + +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct lua_shell_surface *shsurf_parent = + parent ? weston_desktop_surface_get_user_data(parent) : NULL; + + lua_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 lua_shell *ls = shell; + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_SURFACE_FULLSCREEN)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + if (output) { + struct lua_shell_output *shoutput = + weston_output_get_shell_private(output); + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shoutput->lua_regid); + } else + lua_pushnil(ls->lua); + lua_pushboolean(ls->lua, fullscreen); + lua_shell_call_function(ls, "surface_fullscreen", 3, 0); +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ + struct lua_shell *ls = shell; + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_SURFACE_MAXIMIZE)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_pushboolean(ls->lua, maximized); + lua_shell_call_function(ls, "surface_maximize", 2, 0); +} + +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, + struct weston_coord_global pos, void *shell) +{ + struct lua_shell *ls = shell; + struct lua_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_SET_XWAYLAND_POSITION)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + lua_pushnumber(ls->lua, pos.c.x); + lua_pushnumber(ls->lua, pos.c.y); + lua_shell_call_function(ls, "set_xwayland_position", 3, 0); +} + +static const struct weston_desktop_api lua_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, +}; + +/* + * lua_shell + */ + +static void +lua_shell_binding_destroy(struct lua_shell_binding *shbinding) +{ + luaL_unref(shbinding->shell->lua, LUA_REGISTRYINDEX, + shbinding->callback_regid); + free(shbinding); +} + +static void +button_binding_cb(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + struct lua_shell_binding *shbinding = data; + struct lua_shell *ls = shbinding->shell; + struct lua_shell_view *shview = NULL; + struct lua_shell_seat *shseat = get_lua_shell_seat(pointer->seat); + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shbinding->callback_regid); + + if (pointer->focus) { + shview = get_lua_shell_view(pointer->focus); + if (!shview) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shview->lua_regid); + } else { + lua_pushnil(ls->lua); + } + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_pushnumber(ls->lua, button); + lua_shell_call_function(ls, "[button callback]", 3, 0); +} + +static int +lua_shell_env_add_button_binding(struct lua_State *lua) +{ + struct lua_shell_binding *shbinding; + struct lua_shell *shell = get_shell_from_arg(lua, 1); + int button = luaL_checkinteger(lua, 2); + int mods = luaL_checkinteger(lua, 3); + + luaL_checktype(lua, 4, LUA_TFUNCTION); + + shbinding = xzalloc(sizeof *shbinding); + shbinding->callback_regid = luaL_ref(lua, LUA_REGISTRYINDEX); + shbinding->shell = shell; + + shbinding->binding = + weston_compositor_add_button_binding(shell->compositor, + button, mods, + button_binding_cb, + shbinding); + wl_list_insert(shell->binding_list.prev, &shbinding->link); + return 0; +} + +static void +touch_binding_cb(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + struct lua_shell_binding *shbinding = data; + struct lua_shell *ls = shbinding->shell; + struct lua_shell_view *shview = NULL; + struct lua_shell_seat *shseat = get_lua_shell_seat(touch->seat); + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shbinding->callback_regid); + + if (touch->focus) { + shview = get_lua_shell_view(touch->focus); + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shview->lua_regid); + } else { + lua_pushnil(ls->lua); + } + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_shell_call_function(ls, "[touch callback]", 2, 0); +} + +static int +lua_shell_env_add_touch_binding(struct lua_State *lua) +{ + struct lua_shell_binding *shbinding; + struct lua_shell *shell = get_shell_from_arg(lua, 1); + int mods = luaL_checkinteger(lua, 2); + + luaL_checktype(lua, 3, LUA_TFUNCTION); + + shbinding = xzalloc(sizeof *shbinding); + shbinding->callback_regid = luaL_ref(lua, LUA_REGISTRYINDEX); + shbinding->shell = shell; + + shbinding->binding = + weston_compositor_add_touch_binding(shell->compositor, + mods, + touch_binding_cb, + shbinding); + wl_list_insert(shell->binding_list.prev, &shbinding->link); + return 0; +} + +static void +lua_shell_handle_output_created(struct wl_listener *listener, void *data) +{ + struct lua_shell *shell = + container_of(listener, struct lua_shell, output_created_listener); + struct weston_output *output = data; + + lua_shell_output_create(shell, output); +} + +static void +lua_shell_handle_output_resized(struct wl_listener *listener, void *data) +{ + struct lua_shell *ls = + container_of(listener, struct lua_shell, output_resized_listener); + struct weston_output *output = data; + struct lua_shell_output *shoutput = + weston_output_get_shell_private(output); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_OUTPUT_RESIZED)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shoutput->lua_regid); + lua_shell_call_function(ls, "output_resized", 1, 0); +} + +static void +lua_shell_handle_output_moved(struct wl_listener *listener, void *data) +{ + struct lua_shell *ls = + container_of(listener, struct lua_shell, output_moved_listener); + struct weston_output *output = data; + struct lua_shell_output *shoutput = weston_output_get_shell_private(output); + + if (!lua_shell_push_function(ls, LUA_SHELL_CB_OUTPUT_MOVED)) + return; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shoutput->lua_regid); + lua_pushnumber(ls->lua, output->move.c.x); + lua_pushnumber(ls->lua, output->move.c.y); + lua_shell_call_function(ls, "output_moved", 3, 0); +} + +static void +lua_shell_handle_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + struct lua_shell *shell = + container_of(listener, struct lua_shell, seat_created_listener); + lua_shell_seat_create(shell, seat); +} + +static void +transform_handler(struct wl_listener *listener, void *data) +{ +} + +static int +lua_shell_env_output_get_dimensions(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + lua_pushinteger(lua, shoutput->output->width); + lua_pushinteger(lua, shoutput->output->height); + return 2; +} + +static int +lua_shell_env_output_get_position(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + lua_pushinteger(lua, shoutput->output->pos.c.x); + lua_pushinteger(lua, shoutput->output->pos.c.y); + return 2; +} + +static int +lua_shell_env_output_get_name(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + lua_pushstring(lua, shoutput->output->name); + return 1; +} + +static int +lua_shell_env_output_get_scale(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + lua_pushinteger(lua, shoutput->output->current_scale); + return 1; +} + +static int +lua_shell_env_output_is_enabled(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + lua_pushinteger(lua, shoutput->output->enabled); + return 1; +} + +static int +lua_shell_env_output_set_private(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + assert(shoutput->lua_private_regid == 0); + shoutput->lua_private_regid = luaL_ref(lua, LUA_REGISTRYINDEX); + + return 0; +} + +static int +lua_shell_env_output_get_private(struct lua_State *lua) +{ + struct lua_shell_output *shoutput = get_output_from_arg(lua, 1); + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shoutput->lua_private_regid); + + return 1; +} + +static void +lua_shell_env_init_output(struct lua_shell *shell) +{ + static const luaL_Reg lua_output_obj_funcs[] = { + { "__gc", lgc }, + { "get_dimensions", lua_shell_env_output_get_dimensions }, + { "get_position", lua_shell_env_output_get_position }, + { "get_name", lua_shell_env_output_get_name }, + { "get_scale", lua_shell_env_output_get_scale }, + { "is_enabled", lua_shell_env_output_is_enabled }, + { "set_private", lua_shell_env_output_set_private }, + { "get_private", lua_shell_env_output_get_private }, + { NULL, NULL } + }; + + luaL_newmetatable(shell->lua, "weston.output"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_output_obj_funcs, 0); +} + +static int +lua_shell_env_seat_get_capabilities(struct lua_State *lua) +{ + struct lua_shell_seat *shseat = get_seat_from_arg(lua, 1); + struct weston_seat *seat = shseat->seat; + + lua_pushinteger(lua, seat->pointer_device_count); + lua_pushinteger(lua, seat->keyboard_device_count); + lua_pushinteger(lua, seat->touch_device_count); + + return 3; +} + +static int +lua_shell_env_seat_get_name(struct lua_State *lua) +{ + struct lua_shell_seat *shseat = get_seat_from_arg(lua, 1); + + lua_pushstring(lua, shseat->seat->seat_name); + return 1; +} + +static void +lua_shell_env_init_seat(struct lua_shell *shell) +{ + static const luaL_Reg lua_seat_obj_funcs[] = { + { "__gc", lgc }, + { "get_capabilities", lua_shell_env_seat_get_capabilities }, + { "get_name", lua_shell_env_seat_get_name }, + { NULL, NULL } + }; + + luaL_newmetatable(shell->lua, "weston.seat"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_seat_obj_funcs, 0); +} + +static int +lua_shell_env_surface_get_role(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_surface *surface; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + lua_pushstring(lua, weston_surface_get_role(surface)); + return 1; +} + +static int +lua_shell_env_surface_get_app_id(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + const char *app_id = + weston_desktop_surface_get_app_id(shsurf->desktop_surface); + + lua_pushstring(lua, app_id); + return 1; +} + +static int +lua_shell_env_surface_get_title(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + const char *title = + weston_desktop_surface_get_title(shsurf->desktop_surface); + + lua_pushstring(lua, title); + return 1; +} + +static int +lua_shell_env_surface_get_dimensions(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_surface *surface; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + lua_pushinteger(lua, surface->width); + lua_pushinteger(lua, surface->height); + return 2; +} + +static int +lua_shell_env_surface_get_geometry(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_geometry geometry; + + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + lua_pushinteger(lua, geometry.x); + lua_pushinteger(lua, geometry.y); + return 2; +} + +static int +lua_shell_env_surface_get_output(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shsurf->shoutput->lua_regid); + return 1; +} + +static int +lua_shell_env_surface_get_private(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shsurf->lua_private_regid); + + return 1; +} + +static int +lua_shell_env_surface_set_output(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct lua_shell_output *shoutput = get_output_from_arg(lua, 2); + + lua_shell_surface_set_output(shsurf, shoutput); + return 0; +} + +static int +lua_shell_env_surface_set_private(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + + assert(shsurf->lua_private_regid == 0); + shsurf->lua_private_regid = luaL_ref(lua, LUA_REGISTRYINDEX); + + return 0; +} + +static int +lua_shell_env_surface_set_state_fullscreen(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct lua_shell_output *shoutput = get_output_from_arg(lua, 2); + + lua_shell_surface_set_output(shsurf, shoutput); + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, true); + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->shoutput->output->width, + shsurf->shoutput->output->height); + + return 0; +} + +static int +lua_shell_env_surface_get_state_fullscreen(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_desktop_surface *dsurface = shsurf->desktop_surface; + + lua_pushinteger(lua, weston_desktop_surface_get_fullscreen(dsurface)); + + return 1; +} + +static int +lua_shell_env_surface_set_state_maximized(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct lua_shell_output *shoutput = get_output_from_arg(lua, 2); + + lua_shell_surface_set_output(shsurf, shoutput); + weston_desktop_surface_set_maximized(shsurf->desktop_surface, true); + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->shoutput->output->width, + shsurf->shoutput->output->height); + + return 0; +} + +static int +lua_shell_env_surface_get_state_maximized(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_desktop_surface *dsurface = shsurf->desktop_surface; + + lua_pushinteger(lua, weston_desktop_surface_get_maximized(dsurface)); + return 1; +} + +static int +lua_shell_env_surface_set_state_normal(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + int width = luaL_checkinteger(lua, 2); + int height = luaL_checkinteger(lua, 3); + + 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, width, height); + + return 0; +} + +static int +lua_shell_env_surface_get_parent(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct lua_shell_surface *parent = shsurf->parent; + + if (!parent) + lua_pushnil(lua); + else + lua_rawgeti(lua, LUA_REGISTRYINDEX, parent->lua_regid); + + return 1; +} + +static int +lua_shell_env_surface_get_views(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct lua_shell *ls = shsurf->shell; + struct lua_shell_view *shview; + + lua_newtable(ls->lua); + wl_list_for_each(shview, &shsurf->view_list, surface_link) { + char buf[32]; + + lua_rawgeti(ls->lua, LUA_REGISTRYINDEX, shview->lua_regid); + snprintf(buf, sizeof(buf), "view-%"PRIu32, shview->lua_regid); + lua_setfield(ls->lua, -2, buf); + } + + return 1; +} + +static int +lua_shell_env_surface_create_view(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct lua_shell_view *shview; + struct weston_view *view; + + shview = lxzalloc(lua, sizeof(*shview), "weston.view"); + view = weston_desktop_surface_create_view(shsurf->desktop_surface); + shview->view = view; + shview->shell = shsurf->shell; + shview->surface = shsurf; + shview->is_desktop_surface = true; + + wl_list_insert(shview->shell->view_list.prev, &shview->link); + wl_list_insert(shsurf->view_list.prev, &shview->surface_link); + + shview->view_destroy_listener.notify = lua_shell_view_handle_destroy; + wl_signal_add(&view->destroy_signal, &shview->view_destroy_listener); + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shview->lua_regid); + + return 1; +} + +static int +lua_shell_env_surface_map(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + weston_surface_map(surface); + return 0; +} + +static int +lua_shell_env_surface_is_mapped(struct lua_State *lua) +{ + struct lua_shell_surface *shsurf = get_surface_from_arg(lua, 1); + struct weston_surface *wsurf; + bool mapped; + + wsurf = weston_desktop_surface_get_surface(shsurf->desktop_surface); + mapped = weston_surface_is_mapped(wsurf); + lua_pushboolean(lua, mapped); + + return 1; +} + +static int +lua_shell_env_view_get_surface(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + + if (!shview->surface) + lua_pushnil(lua); + else + lua_rawgeti(lua, LUA_REGISTRYINDEX, shview->surface->lua_regid); + + return 1; +} + +static int +lua_shell_env_view_get_private_surface(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + + if (!shview->surface) + lua_pushnil(lua); + else + lua_rawgeti(lua, LUA_REGISTRYINDEX, shview->surface->lua_private_regid); + + return 1; +} + +static int +lua_shell_env_view_get_layer(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + + if (!shview->layer) + lua_pushnil(lua); + else + lua_rawgeti(lua, LUA_REGISTRYINDEX, shview->layer->lua_regid); + + return 1; +} + +static int +lua_shell_env_view_set_layer(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct lua_shell_layer *shlayer = get_layer_from_arg(lua, 2); + + shview->layer = shlayer; + weston_view_move_to_layer(shview->view, &shlayer->layer.view_list); + return 0; +} + +static int +lua_shell_env_view_unset_layer(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + + shview->layer = NULL; + weston_view_move_to_layer(shview->view, NULL); + + return 0; +} + +static int +lua_shell_env_view_get_position(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + lua_pushinteger(lua, shview->view->geometry.pos_offset.x); + lua_pushinteger(lua, shview->view->geometry.pos_offset.y); + return 2; +} + +static int +lua_shell_env_view_set_position(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + int x = luaL_checkinteger(lua, 2); + int y = luaL_checkinteger(lua, 3); + struct weston_coord_global pos; + + pos.c = weston_coord(x, y); + weston_view_set_position(shview->view, pos); + weston_view_update_transform(shview->view); + + return 0; +} + +static int +lua_shell_env_view_get_dimensions(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + int width, height; + + if (shview->view->geometry.scissor_enabled) { + pixman_box32_t *extents = + pixman_region32_extents(&shview->view->geometry.scissor); + width = extents->x2 - extents->x1; + height = extents->y2 - extents->y1; + } else { + struct weston_output *output; + struct weston_compositor *ec = + shview->view->surface->compositor; + + width = 0; + height = 0; + wl_list_for_each(output, &ec->output_list, link) { + width = MAX(width, output->pos.c.x + output->width); + height = MAX(height, output->pos.c.y + output->height); + } + } + + lua_pushinteger(lua, width); + lua_pushinteger(lua, height); + return 2; +} + +static int +lua_shell_env_view_set_dimensions(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct lua_shell_surface *shsurf = shview->surface; + int width = luaL_checkinteger(lua, 2); + int height = luaL_checkinteger(lua, 3); + + if (!shsurf) + return 0; + + weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); + + return 0; +} + +static int +lua_shell_env_view_set_output(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct lua_shell_output *shoutput = get_output_from_arg(lua, 2); + + weston_view_set_output(shview->view, shoutput->output); + return 0; +} + +static int +lua_shell_env_view_get_output(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct weston_output *output = shview->view->output; + uint32_t regid = LUA_REFNIL; + + if (output) { + struct lua_shell_output *shoutput; + + shoutput = weston_output_get_shell_private(output); + regid = shoutput->lua_regid; + } + lua_rawgeti(lua, LUA_REGISTRYINDEX, regid); + return 1; +} + +static int +lua_shell_env_view_get_alpha(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + + lua_pushnumber(lua, shview->view->alpha); + return 1; +} + +static int +lua_shell_env_view_set_alpha(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + float alpha = luaL_checknumber(lua, 2); + + weston_view_set_alpha(shview->view, alpha); + + return 0; +} + +static int +lua_shell_env_view_activate(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct lua_shell_seat *shseat = get_seat_from_arg(lua, 2); + struct weston_surface *main_surface = + weston_surface_get_main_surface(shview->view->surface); + struct lua_shell_surface *shsurf = get_lua_shell_surface(main_surface); + + if (!shsurf) + return 0; + + weston_view_activate_input(shview->view, shseat->seat, WESTON_ACTIVATE_FLAG_NONE); + weston_desktop_surface_set_activated(shsurf->desktop_surface, true); + + return 0; +} + +static int +lua_shell_env_view_deactivate(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct weston_surface *main_surface = + weston_surface_get_main_surface(shview->view->surface); + struct lua_shell_surface *shsurf = get_lua_shell_surface(main_surface); + + if (!shsurf) + return 0; + + weston_desktop_surface_set_activated(shsurf->desktop_surface, false); + return 0; +} + +static int +lua_shell_env_view_move_behind_other_view(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct lua_shell_view *other_shview = get_view_from_arg(lua, 2); + struct lua_shell *shell = shview->shell; + struct weston_compositor *wc = shell->compositor; + + weston_assert_true(wc, &other_shview->view->layer_link.layer); + + weston_view_move_to_layer(shview->view, + &other_shview->view->layer_link); + + return 0; +} + +static int +lua_shell_env_view_move_in_front_of_other_view(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + struct lua_shell_view *other_shview = get_view_from_arg(lua, 2); + struct lua_shell *shell = shview->shell; + struct weston_compositor *wc = shell->compositor; + + weston_assert_true(wc, &other_shview->view->layer_link.layer); + + weston_view_move_before_layer_entry(shview->view, + &other_shview->view->layer_link); + + return 0; +} + +static int +lua_shell_env_view_dispose(struct lua_State *lua) +{ + struct lua_shell_view *shview = get_view_from_arg(lua, 1); + + lua_shell_view_dispose(shview); + + return 0; +} + +static void +lua_shell_env_init_surface_view(struct lua_shell *shell) +{ + static const luaL_Reg lua_surface_obj_funcs[] = { + { "__gc", lgc }, + { "get_role", lua_shell_env_surface_get_role }, + { "get_app_id", lua_shell_env_surface_get_app_id }, + { "get_title", lua_shell_env_surface_get_title }, + { "get_geometry", lua_shell_env_surface_get_geometry}, + { "get_dimensions", lua_shell_env_surface_get_dimensions }, + { "get_output", lua_shell_env_surface_get_output }, + { "get_private", lua_shell_env_surface_get_private }, + { "set_output", lua_shell_env_surface_set_output }, + { "set_private", lua_shell_env_surface_set_private }, + { "set_state_fullscreen", lua_shell_env_surface_set_state_fullscreen }, + { "get_state_fullscreen", lua_shell_env_surface_get_state_fullscreen }, + { "set_state_maximized", lua_shell_env_surface_set_state_maximized }, + { "get_state_maximized", lua_shell_env_surface_get_state_maximized }, + { "set_state_normal", lua_shell_env_surface_set_state_normal }, + { "get_parent", lua_shell_env_surface_get_parent }, + { "get_views", lua_shell_env_surface_get_views }, + { "create_view", lua_shell_env_surface_create_view }, + { "map", lua_shell_env_surface_map }, + { "is_mapped", lua_shell_env_surface_is_mapped }, + { NULL, NULL } + }; + static const luaL_Reg lua_view_obj_funcs[] = { + { "__gc", lgc }, + { "get_surface", lua_shell_env_view_get_surface }, + { "get_private_surface", lua_shell_env_view_get_private_surface }, + { "get_layer", lua_shell_env_view_get_layer }, + { "set_layer", lua_shell_env_view_set_layer }, + { "unset_layer", lua_shell_env_view_unset_layer }, + { "get_position", lua_shell_env_view_get_position }, + { "set_position", lua_shell_env_view_set_position }, + { "get_dimensions", lua_shell_env_view_get_dimensions }, + { "set_dimensions", lua_shell_env_view_set_dimensions }, + { "get_output", lua_shell_env_view_get_output }, + { "set_output", lua_shell_env_view_set_output }, + { "get_alpha", lua_shell_env_view_get_alpha }, + { "set_alpha", lua_shell_env_view_set_alpha }, + { "activate", lua_shell_env_view_activate }, + { "deactivate", lua_shell_env_view_deactivate }, + { "move_behind_other_view", lua_shell_env_view_move_behind_other_view }, + { "move_in_front_of_other_view", lua_shell_env_view_move_in_front_of_other_view }, + { "dispose", lua_shell_env_view_dispose }, + { NULL, NULL } + }; + + luaL_newmetatable(shell->lua, "weston.surface"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_surface_obj_funcs, 0); + + luaL_newmetatable(shell->lua, "weston.view"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_view_obj_funcs, 0); +} + +static int +lua_shell_env_layer_get_position(struct lua_State *lua) +{ + struct lua_shell_layer *shlayer = get_layer_from_arg(lua, 1); + + lua_pushinteger(lua, shlayer->layer.position); + return 1; +} + +static int +lua_shell_env_layer_set_position(struct lua_State *lua) +{ + struct lua_shell_layer *shlayer = get_layer_from_arg(lua, 1); + int64_t position = luaL_checkinteger(lua, 2); + + weston_layer_set_position(&shlayer->layer, position); + return 0; +} + +static int +lua_shell_env_layer_get_views(struct lua_State *lua) +{ + struct lua_shell_layer *shlayer = get_layer_from_arg(lua, 1); + struct lua_shell_view *shview; + struct lua_shell *shell = shlayer->shell; + struct weston_view *view; + + lua_newtable(shell->lua); + + wl_list_for_each(view, &shlayer->layer.view_list.link, layer_link.link) { + char buf[32]; + + shview = get_lua_shell_view(view); + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shview->lua_regid); + + snprintf(buf, sizeof(buf), "view-%"PRIu32, shview->lua_regid); + lua_setfield(shell->lua, -2, buf); + } + return 1; +} + +static void +lua_shell_env_init_enums(struct lua_shell *shell) +{ + /* input-event-codes */ + LUA_ENUM(BTN_LEFT); + LUA_ENUM(BTN_RIGHT); + + /* enum weston_layer_position */ + LUA_ENUM(WESTON_LAYER_POSITION_NONE); + LUA_ENUM(WESTON_LAYER_POSITION_HIDDEN); + LUA_ENUM(WESTON_LAYER_POSITION_BACKGROUND); + LUA_ENUM(WESTON_LAYER_POSITION_BOTTOM_UI); + LUA_ENUM(WESTON_LAYER_POSITION_NORMAL); + LUA_ENUM(WESTON_LAYER_POSITION_UI); + LUA_ENUM(WESTON_LAYER_POSITION_FULLSCREEN); + LUA_ENUM(WESTON_LAYER_POSITION_TOP_UI); + LUA_ENUM(WESTON_LAYER_POSITION_LOCK); + LUA_ENUM(WESTON_LAYER_POSITION_CURSOR); + LUA_ENUM(WESTON_LAYER_POSITION_FADE); +} + +static void +lua_shell_env_init_layer(struct lua_shell *shell) +{ + static const luaL_Reg lua_layer_obj_funcs[] = { + { "__gc", lgc }, + { "get_position", lua_shell_env_layer_get_position }, + { "set_position", lua_shell_env_layer_set_position }, + { "get_views", lua_shell_env_layer_get_views }, + { NULL, NULL } + }; + + luaL_newmetatable(shell->lua, "weston.layer"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_layer_obj_funcs, 0); +} + +static int +lua_shell_env_curtain_set_color(struct lua_State *lua) +{ + struct lua_shell_curtain *shcurtain = get_curtain_from_arg(lua, 1); + unsigned int color = luaL_checkinteger(lua, 2); + + assert(!shcurtain->view); + + shcurtain->params.r = ((color >> 16) & 0xff) / 255.0; + shcurtain->params.g = ((color >> 8) & 0xff) / 255.0; + shcurtain->params.b = ((color >> 0) & 0xff) / 255.0; + shcurtain->params.a = ((color >> 24) & 0xff) / 255.0; + + return 0; +} + +static int +lua_shell_env_curtain_set_position(struct lua_State *lua) +{ + struct lua_shell_curtain *shcurtain = get_curtain_from_arg(lua, 1); + unsigned int x = luaL_checkinteger(lua, 2); + unsigned int y = luaL_checkinteger(lua, 3); + + assert(!shcurtain->view); + + shcurtain->params.pos.c = weston_coord(x, y); + + return 0; +} + +static int +lua_shell_env_curtain_set_dimensions(struct lua_State *lua) +{ + struct lua_shell_curtain *shcurtain = get_curtain_from_arg(lua, 1); + unsigned int w = luaL_checkinteger(lua, 2); + unsigned int h = luaL_checkinteger(lua, 3); + + assert(!shcurtain->view); + + shcurtain->params.width = w; + shcurtain->params.height = h; + + return 0; +} + +static int +lua_shell_env_curtain_set_capture_input(struct lua_State *lua) +{ + struct lua_shell_curtain *shcurtain = get_curtain_from_arg(lua, 1); + bool capture_input = lua_toboolean(lua, 2); + + assert(!shcurtain->view); + + shcurtain->params.capture_input = capture_input; + return 0; +} + +static int +lua_shell_curtain_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + struct lua_shell_curtain *shcurtain = surface->committed_private; + const char *name = "unnamed"; + + if (shcurtain->name) + name = shcurtain->name; + + return snprintf(buf, len, "%s (curtain)", name); +} + +static int +lua_shell_env_curtain_get_view(struct lua_State *lua) +{ + struct lua_shell_curtain *shcurtain = get_curtain_from_arg(lua, 1); + struct lua_shell *shell = shcurtain->shell; + struct lua_shell_view *shview = shcurtain->view; + struct weston_view *view; + + if (shview) + goto done; + + shcurtain->params.get_label = lua_shell_curtain_get_label; + shcurtain->params.surface_private = shcurtain; + shcurtain->curtain = weston_shell_utils_curtain_create(shell->compositor, + &shcurtain->params); + + shview = lxzalloc(lua, sizeof(*shview), "weston.view"); + shview->view = shcurtain->curtain->view; + shcurtain->view = shview; + shview->shell = shell; + view = shview->view; + + shview->view_destroy_listener.notify = lua_shell_view_handle_destroy; + wl_signal_add(&view->destroy_signal, &shview->view_destroy_listener); + + wl_list_insert(shview->shell->view_list.prev, &shview->link); + wl_list_init(&shview->surface_link); + +done: + lua_rawgeti(lua, LUA_REGISTRYINDEX, shview->lua_regid); + + return 1; +} + +static int +lua_shell_env_curtain_dispose(struct lua_State *lua) +{ + struct lua_shell_curtain *shcurtain = get_curtain_from_arg(lua, 1); + + lua_shell_curtain_dispose(shcurtain); + + return 0; +} + +static void +lua_shell_env_init_curtain(struct lua_shell *shell) +{ + static const luaL_Reg lua_layer_obj_funcs[] = { + { "__gc", lgc }, + { "set_color", lua_shell_env_curtain_set_color }, + { "set_position", lua_shell_env_curtain_set_position }, + { "set_dimensions", lua_shell_env_curtain_set_dimensions }, + { "set_capture_input", lua_shell_env_curtain_set_capture_input }, + { "get_view", lua_shell_env_curtain_get_view }, + { "dispose", lua_shell_env_curtain_dispose }, + { NULL, NULL } + }; + + luaL_newmetatable(shell->lua, "weston.curtain"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_layer_obj_funcs, 0); +} + +static void +lua_shell_env_destroy_callbacks(struct lua_shell *shell) +{ + struct lua_shell_callback *cb = shell->callbacks; + unsigned int i; + + for (i = 0; i < LUA_SHELL_NUM_CB; i++) { + if (!cb[i].regid) + continue; + + luaL_unref(shell->lua, LUA_REGISTRYINDEX, cb[i].regid); + cb[i].regid = 0; + } +} + +static bool +lua_shell_env_init_callbacks(struct lua_shell *shell) +{ + struct lua_State *lua = shell->lua; + int objtype; + unsigned int i; + struct lua_shell_callback *cb = shell->callbacks; + + cb[LUA_SHELL_CB_INIT].name = "init"; + cb[LUA_SHELL_CB_SURFACE_ADDED].name = "surface_added"; + cb[LUA_SHELL_CB_KEYBOARD_FOCUS].name = "keyboard_focus"; + cb[LUA_SHELL_CB_SEAT_CREATE].name = "seat_create"; + cb[LUA_SHELL_CB_SURFACE_ADDED].name = "surface_added"; + cb[LUA_SHELL_CB_SURFACE_COMMITTED].name = "surface_committed"; + cb[LUA_SHELL_CB_SURFACE_MOVE].name = "surface_move"; + cb[LUA_SHELL_CB_SURFACE_REMOVED].name = "surface_removed"; + cb[LUA_SHELL_CB_SURFACE_RESIZE].name = "surface_resize"; + cb[LUA_SHELL_CB_SURFACE_FULLSCREEN].name = "surface_fullscreen"; + cb[LUA_SHELL_CB_SURFACE_MAXIMIZE].name = "surface_maximize"; + cb[LUA_SHELL_CB_SET_XWAYLAND_POSITION].name = "set_xwayland_position"; + cb[LUA_SHELL_CB_OUTPUT_CREATE].name = "output_create"; + cb[LUA_SHELL_CB_OUTPUT_RESIZED].name = "output_resized"; + cb[LUA_SHELL_CB_OUTPUT_MOVED].name = "output_moved"; + + lua_getglobal(lua, "lua_shell_callbacks"); + if (!lua_istable(lua, -1)) { + weston_log("lua_shell_callbacks table missing\n"); + return false; + } + + for (i = 0; i < LUA_SHELL_NUM_CB; i++) { + assert(shell->callbacks[i].name); + + objtype = lua_getfield(lua, -1, shell->callbacks[i].name); + /* No callback provided for this one. */ + if (objtype == LUA_TNIL) { + lua_pop(lua, 1); + continue; + } + + if (objtype != LUA_TFUNCTION) { + weston_log("LUA callback for '%s' was not a function!\n", + shell->callbacks[i].name); + return false; + } + + shell->callbacks[i].regid = luaL_ref(lua, LUA_REGISTRYINDEX); + } + + return true; +} + +static int +lua_shell_env_get_outputs(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_output *shoutput; + + lua_newtable(shell->lua); /* outputs[] = { */ + wl_list_for_each(shoutput, &shell->output_list, link) { + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shoutput->lua_regid); + lua_setfield(shell->lua, -2, shoutput->output->name); + } + + return 1; +} + +static int +lua_shell_env_get_seats(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_seat *shseat; + + lua_newtable(shell->lua); /* seats[] = { */ + wl_list_for_each(shseat, &shell->seat_list, link) { + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shseat->lua_regid); + lua_setfield(shell->lua, -2, shseat->seat->seat_name); + } + + return 1; +} + +static int +lua_shell_env_get_surfaces(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_surface *shsurf; + + lua_newtable(shell->lua); /* surfaces[] = { */ + wl_list_for_each(shsurf, &shell->surface_list, link) { + char buf[32]; + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shsurf->lua_regid); + snprintf(buf, sizeof(buf), "surf-%" PRIu32, shsurf->lua_regid); + lua_setfield(shell->lua, -2, buf); + } + + return 1; +} + +static int +lua_shell_env_get_views(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_view *shview; + + lua_newtable(shell->lua); /* views[] = { */ + wl_list_for_each(shview, &shell->view_list, link) { + char buf[32]; + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shview->lua_regid); + snprintf(buf, sizeof(buf), "view-%" PRIu32, shview->lua_regid); + lua_setfield(shell->lua, -2, buf); + } + + return 1; +} + +static int +lua_shell_env_get_layers(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_layer *shlayer; + + lua_newtable(shell->lua); /* layers[] = { */ + wl_list_for_each(shlayer, &shell->layer_list, link) { + char buf[32]; + + lua_rawgeti(shell->lua, LUA_REGISTRYINDEX, shlayer->lua_regid); + snprintf(buf, sizeof(buf), "layer-0x%" PRIx64, shlayer->layer.position); + lua_setfield(shell->lua, -2, buf); + } + return 1; +} + +static int +lua_shell_env_create_layer(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_layer *shlayer; + + shlayer = lxzalloc(shell->lua, sizeof *shlayer, "weston.layer"); + + shlayer->shell = shell; + weston_layer_init(&shlayer->layer, shell->compositor); + wl_list_insert(&shell->layer_list, &shlayer->link); + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shlayer->lua_regid); + return 1; +} + +static int +lua_shell_env_create_curtain(struct lua_State *lua) +{ + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_curtain *shcurtain; + const char *name = lua_tostring(lua, -1); + + shcurtain = lxzalloc(shell->lua, sizeof *shcurtain, "weston.curtain"); + shcurtain->shell = shell; + wl_list_insert(&shell->curtain_list, &shcurtain->link); + + lua_rawgeti(lua, LUA_REGISTRYINDEX, shcurtain->lua_regid); + if (name) + shcurtain->name = strdup(name); + + return 1; +} + +static int +timer_wrapper(void *data) +{ + struct lua_shell_timer *timer = data; + struct lua_shell *shell = timer->shell; + struct lua_State *lua = shell->lua; + + lua_rawgeti(lua, LUA_REGISTRYINDEX, timer->cb_regid); + lua_rawgeti(lua, LUA_REGISTRYINDEX, timer->lua_private_regid); + + lua_shell_call_function(shell, "[timer callback]", 1, 0); + + luaL_unref(lua, LUA_REGISTRYINDEX, timer->cb_regid); + luaL_unref(lua, LUA_REGISTRYINDEX, timer->lua_private_regid); + wl_event_source_remove(timer->event_source); + free(data); + return 0; +} + +static int +lua_shell_env_set_timer(struct lua_State *lua) +{ + struct wl_event_loop *loop; + struct lua_shell *shell = get_shell_from_arg(lua, 1); + struct lua_shell_timer *timer; + int timeout = luaL_checkinteger(lua, 4); + + luaL_checktype(lua, 2, LUA_TFUNCTION); + + lua_pop(lua, 1); + + timer = xzalloc(sizeof *timer); + timer->shell = shell; + + loop = wl_display_get_event_loop(shell->compositor->wl_display); + timer->event_source = wl_event_loop_add_timer(loop, timer_wrapper, timer); + timer->lua_private_regid = luaL_ref(lua, LUA_REGISTRYINDEX); + timer->cb_regid = luaL_ref(lua, LUA_REGISTRYINDEX); + wl_event_source_timer_update(timer->event_source, timeout); + + return 0; +} + +static void +lua_shell_env_init_weston(struct lua_shell *shell) +{ + static const luaL_Reg lua_global_obj_funcs[] = { + { "get_outputs", lua_shell_env_get_outputs, }, + { "get_seats", lua_shell_env_get_seats, }, + { "get_surfaces", lua_shell_env_get_surfaces, }, + { "get_layers", lua_shell_env_get_layers, }, + { "get_views", lua_shell_env_get_views, }, + { "create_layer", lua_shell_env_create_layer, }, + { "create_curtain", lua_shell_env_create_curtain, }, + { "set_timer", lua_shell_env_set_timer, }, + { "add_touch_binding", lua_shell_env_add_touch_binding, }, + { "add_button_binding", lua_shell_env_add_button_binding, }, + { NULL, NULL } + }; + + /* instantiate the type */ + luaL_newmetatable(shell->lua, "weston.global"); + lua_pushvalue(shell->lua, -1); + lua_setfield(shell->lua, -2, "__index"); + luaL_setfuncs(shell->lua, lua_global_obj_funcs, 0); + + /* create a new typed variable */ + struct lua_shell **out = lua_newuserdata(shell->lua, sizeof(*out)); + *out = shell; + luaL_getmetatable(shell->lua, "weston.global"); + lua_setmetatable(shell->lua, -2); + lua_setglobal(shell->lua, "weston"); +} + +static int +lua_shell_init_env(struct lua_shell *shell, const char *script) +{ + /* set up the core Lua interpreter */ + shell->lua = luaL_newstate(); + if (!shell->lua) { + weston_log("Couldn't initialize Lua environment\n"); + return -1; + } + + /* add Lua standard libraries */ + luaL_openlibs(shell->lua); + + /* initialise our types and global singleton */ + lua_shell_env_init_enums(shell); + lua_shell_env_init_curtain(shell); + lua_shell_env_init_output(shell); + lua_shell_env_init_seat(shell); + lua_shell_env_init_surface_view(shell); + lua_shell_env_init_layer(shell); + lua_shell_env_init_weston(shell); + + /* Read the initial lua setup script */ + if (luaL_dofile(shell->lua, script) != LUA_OK) { + const char *error; + + error = lua_tostring(shell->lua, -1); + weston_log("Lua script '%s' is not ok: %s\n", script, error); + return -1; + } + + if (!lua_shell_env_init_callbacks(shell)) + return -1; + + if (!lua_shell_push_function(shell, LUA_SHELL_CB_INIT)) { + weston_log("Lua init-script missing init function\n"); + return -1; + } + + if (!lua_shell_call_function(shell, "init", 0, 0)) + return -1; + + return 0; +} + +static void +lua_shell_destroy(struct wl_listener *listener, void *data) +{ + struct lua_shell *shell = + container_of(listener, struct lua_shell, destroy_listener); + struct lua_shell_output *shoutput, *shoutput_next; + struct lua_shell_seat *shseat, *shseat_next; + struct lua_shell_view *shview, *shview_next; + struct lua_shell_surface *shsurf, *shsurf_next; + struct lua_shell_layer *shlayer, *shlayer_next; + struct lua_shell_curtain *shcurtain, *shcurtain_next; + struct lua_shell_binding *shbinding, *shbinding_next; + + 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_remove(&shell->transform_listener.link); + + wl_list_for_each_safe(shcurtain, shcurtain_next, &shell->curtain_list, link) + lua_shell_curtain_dispose(shcurtain); + + wl_list_for_each_safe(shoutput, shoutput_next, &shell->output_list, link) + lua_shell_output_destroy(shoutput); + + wl_list_for_each_safe(shseat, shseat_next, &shell->seat_list, link) + lua_shell_seat_destroy(shseat); + + wl_list_for_each_safe(shsurf, shsurf_next, &shell->surface_list, link) + lua_shell_surface_dispose(shsurf); + + wl_list_for_each_safe(shview, shview_next, &shell->view_list, link) + lua_shell_view_dispose(shview); + + wl_list_for_each_safe(shlayer, shlayer_next, &shell->layer_list, link) + lua_shell_layer_dispose(shlayer); + + wl_list_for_each_safe(shbinding, shbinding_next, &shell->binding_list, link) + lua_shell_binding_destroy(shbinding); + + weston_desktop_destroy(shell->desktop); + + if (shell->lua) { + lua_shell_env_destroy_callbacks(shell); + lua_close(shell->lua); + } + + if (shell->config) + weston_config_destroy(shell->config); + + free(shell); +} + +WL_EXPORT int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct weston_config_section *shell_section = NULL; + char *script = NULL; + struct lua_shell *shell; + struct weston_seat *seat; + struct weston_output *output; + const char *config_file; + const struct weston_option options[] = { + { WESTON_OPTION_STRING, "lua-script", 0, &script }, + }; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = ec; + + wl_list_init(&shell->surface_list); + wl_list_init(&shell->layer_list); + wl_list_init(&shell->view_list); + wl_list_init(&shell->timer_list); + wl_list_init(&shell->seat_list); + wl_list_init(&shell->output_list); + wl_list_init(&shell->curtain_list); + wl_list_init(&shell->binding_list); + + /* Init these because it makes cleanup nicer if + * lua_shell_init_env() fails */ + wl_list_init(&shell->seat_created_listener.link); + wl_list_init(&shell->output_created_listener.link); + wl_list_init(&shell->output_resized_listener.link); + wl_list_init(&shell->output_moved_listener.link); + + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + lua_shell_destroy)) { + free(shell); + return 0; + } + + shell->transform_listener.notify = transform_handler; + wl_signal_add(&ec->transform_signal, &shell->transform_listener); + + config_file = weston_config_get_name_from_env(); + shell->config = weston_config_parse(config_file); + + parse_options(options, ARRAY_LENGTH(options), argc, argv); + + shell->desktop = weston_desktop_create(ec, &lua_shell_desktop_api, + shell); + if (!shell->desktop) + return -1; + + if (shell->config) + shell_section = weston_config_get_section(shell->config, "shell", NULL, NULL); + if (!script && shell_section) + weston_config_section_get_string(shell_section, "lua-script", &script, NULL); + + if (!script) { + weston_log("No LUA script\n"); + return -1; + } + + if (lua_shell_init_env(shell, script)) { + free(script); + return -1; + } + free(script); + + wl_list_for_each(seat, &ec->seat_list, link) + lua_shell_seat_create(shell, seat); + shell->seat_created_listener.notify = lua_shell_handle_seat_created; + wl_signal_add(&ec->seat_created_signal, &shell->seat_created_listener); + + wl_list_for_each(output, &ec->output_list, link) + lua_shell_output_create(shell, output); + + shell->output_created_listener.notify = lua_shell_handle_output_created; + wl_signal_add(&ec->output_created_signal, &shell->output_created_listener); + + shell->output_resized_listener.notify = lua_shell_handle_output_resized; + wl_signal_add(&ec->output_resized_signal, &shell->output_resized_listener); + + shell->output_moved_listener.notify = lua_shell_handle_output_moved; + wl_signal_add(&ec->output_moved_signal, &shell->output_moved_listener); + + screenshooter_create(ec); + + return 0; +} diff --git a/lua-shell/lua-shell.h b/lua-shell/lua-shell.h new file mode 100644 index 000000000..3e94beb06 --- /dev/null +++ b/lua-shell/lua-shell.h @@ -0,0 +1,259 @@ +/* + * Copyright 2020-2025 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_LUA_SHELL_H +#define WESTON_LUA_SHELL_H + +#include +#include +#include +#include "libweston/shell-utils.h" + +#include +#include +#include + +#define LUA_ENUM(x) do {\ + lua_pushnumber(shell->lua, x); \ + lua_setglobal(shell->lua, #x); \ +} while (0) + +enum lua_shell_cb_id { + LUA_SHELL_CB_INIT = 0, + LUA_SHELL_CB_KEYBOARD_FOCUS, + LUA_SHELL_CB_OUTPUT_CREATE, + LUA_SHELL_CB_OUTPUT_RESIZED, + LUA_SHELL_CB_OUTPUT_MOVED, + LUA_SHELL_CB_SEAT_CREATE, + LUA_SHELL_CB_SET_XWAYLAND_POSITION, + LUA_SHELL_CB_SURFACE_ADDED, + LUA_SHELL_CB_SURFACE_COMMITTED, + LUA_SHELL_CB_SURFACE_MOVE, + LUA_SHELL_CB_SURFACE_REMOVED, + LUA_SHELL_CB_SURFACE_RESIZE, + LUA_SHELL_CB_SURFACE_FULLSCREEN, + LUA_SHELL_CB_SURFACE_MAXIMIZE, + LUA_SHELL_NUM_CB, +}; + +struct lua_shell_callback { + const char *name; + uint32_t regid; +}; + +struct lua_shell { + struct weston_compositor *compositor; + struct weston_desktop *desktop; + + struct lua_shell_callback callbacks[LUA_SHELL_NUM_CB]; + + 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 wl_listener transform_listener; + + struct wl_list output_list; + struct wl_list seat_list; + struct wl_list layer_list; + struct wl_list surface_list; + struct wl_list view_list; + struct wl_list timer_list; + struct wl_list curtain_list; + struct wl_list binding_list; + + const struct weston_xwayland_surface_api *xwayland_surface_api; + struct weston_config *config; + + struct lua_State *lua; +}; + +struct lua_object { + uint32_t lua_regid; + uint32_t lua_private_regid; +}; + +struct lua_shell_output { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct wl_list link; /** lua_shell::output_list */ +}; + +struct lua_shell_curtain { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + struct lua_shell_view *view; + struct weston_curtain_params params; + struct weston_curtain *curtain; + char *name; + + struct wl_list link; /** lua_shell:curtain_list */ +}; + +struct lua_shell_surface { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + struct weston_desktop_surface *desktop_surface; + + struct lua_shell_output *shoutput; + struct wl_listener output_destroy_listener; + + struct wl_signal destroy_signal; + struct wl_listener parent_destroy_listener; + struct lua_shell_surface *parent; + + struct wl_list view_list; + + struct wl_list link; /** lua_shell::surface_list */ +}; + +struct lua_shell_view { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + bool is_desktop_surface; + struct lua_shell_surface *surface; + struct weston_view *view; + struct lua_shell_layer *layer; + struct wl_listener view_destroy_listener; + struct wl_list surface_link; /** lua_shell_surface::view_list */ + + struct wl_list link; /** lua_shell::view_list */ +}; + +struct lua_shell_layer { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + struct weston_layer layer; + + struct wl_list link; /** lua_shell::layer_list */ +}; + +struct lua_shell_seat { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + 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 wl_list link; /** lua_shell::seat_list */ +}; + +struct lua_shell_timer { + uint32_t lua_regid; + uint32_t lua_private_regid; + + struct lua_shell *shell; + struct wl_event_source *event_source; + uint32_t cb_regid; + + struct wl_list link; /** lua_shell::timer_list */ +}; + +struct lua_shell_binding { + struct weston_binding *binding; + struct lua_shell *shell; + uint32_t callback_regid; + + struct wl_list link; /** lua_shell::binding_list */ +}; + +static inline struct lua_shell * +get_shell_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell **shell = luaL_checkudata(lua, arg, "weston.global"); + luaL_argcheck(lua, shell && *shell, arg, "`weston' expected"); + return *shell; +} + +static inline struct lua_shell_output * +get_output_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell_output **shoutput = + luaL_checkudata(lua, arg, "weston.output"); + luaL_argcheck(lua, shoutput && *shoutput, arg, + "`weston.output' expected"); + return *shoutput; +} + +static inline struct lua_shell_seat * +get_seat_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell_seat **shseat = luaL_checkudata(lua, arg, "weston.seat"); + luaL_argcheck(lua, shseat && *shseat, arg, "`weston.seat' expected"); + return *shseat; +} + +static inline struct lua_shell_surface * +get_surface_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell_surface **shsurf = + luaL_checkudata(lua, arg, "weston.surface"); + luaL_argcheck(lua, shsurf && *shsurf, arg, "`weston.surface' expected"); + return *shsurf; +} + +static inline struct lua_shell_view * +get_view_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell_view **shview = luaL_checkudata(lua, arg, "weston.view"); + luaL_argcheck(lua, shview && *shview, arg, "`weston.view' expected"); + return *shview; +} + +static inline struct lua_shell_layer * +get_layer_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell_layer **shlayer = luaL_checkudata(lua, arg, "weston.layer"); + luaL_argcheck(lua, shlayer && *shlayer, arg, "`weston.layer' expected"); + return *shlayer; +} + +static inline struct lua_shell_curtain * +get_curtain_from_arg(struct lua_State *lua, int arg) +{ + struct lua_shell_curtain **shcurtain = luaL_checkudata(lua, arg, "weston.curtain"); + luaL_argcheck(lua, shcurtain && *shcurtain, arg, "`weston.curtain` expected"); + return *shcurtain; +} + +#endif /* WESTON_LUA_SHELL_H */ diff --git a/lua-shell/meson.build b/lua-shell/meson.build new file mode 100644 index 000000000..fff53fbc6 --- /dev/null +++ b/lua-shell/meson.build @@ -0,0 +1,33 @@ +if not get_option('shell-lua') + subdir_done() +endif + +config_h.set('BUILD_LUA_SHELL', '1') + +dep_lua = dependency('lua', version: '>= 5.4', required: false) +if not dep_lua.found() + error('lua-shell requires Lua >= 5.4 which was not found. Or, you can use \'-Dshell-lua=false\'.') +endif + +srcs_shell_lua = [ + 'lua-shell.c', +] +deps_shell_lua = [ + dep_libm, + dep_libexec_weston, + dep_libshared, + dep_libweston_public, + dep_lua, +] +plugin_shell_lua = shared_library( + 'lua-shell', + srcs_shell_lua, + include_directories: common_inc, + dependencies: deps_shell_lua, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' +) +env_modmap += 'lua-shell.so=@0@;'.format(plugin_shell_lua.full_path()) +install_data('shell.lua', install_dir: get_option('libexecdir')) diff --git a/lua-shell/shell.lua b/lua-shell/shell.lua new file mode 100644 index 000000000..341a13786 --- /dev/null +++ b/lua-shell/shell.lua @@ -0,0 +1,338 @@ +background_layer = {} +normal_layer = {} +hidden_layer = {} +fullscreen_layer = {} +current_width = 0 +current_height = 0 + +function get_tile_row_col(count, width, height) + if (count == 0) then + return 1, 1 + end + + local cols = math.ceil(math.sqrt(count)) + local rows = math.ceil(count / cols) + + return cols, rows +end + +function sort_views(views) + local count = 0 + local sorted_views = {} + + for k, v in pairs(views) do + count = count + 1 + sorted_views[count] = k + end + table.sort(sorted_views) + + return count, sorted_views +end + +function relayout() + local views = normal_layer:get_views() + local count = 0 + local col = 0 + local x = 0 + local row = 0 + local y = 0 + local count, sorted_views = sort_views(views) + local output_width, output_height = primary_output:get_dimensions() + + local cols, rows = get_tile_row_col(count) + local width = math.floor(output_width / cols) + local height = math.floor(output_height / rows) + for k, v in ipairs(sorted_views) do + col = col + 1 + if (col > cols) then + row = row + 1 + col = 1 + x = 0 + y = y + height + end + local view = views[v] + local surface = view:get_surface() + local gx, gy = surface:get_geometry() + view:set_position(x - gx, y - gy) + surface:set_state_normal(width, height) + x = x + width + end +end + +function my_output_create(output) + local pd = { has_fullscreen_view = false } + + if (primary_output == nil) then + primary_output = output + end + + x, y = output:get_position() + w, h = output:get_dimensions() + background_curtain = weston:create_curtain("output curtain") + background_curtain:set_color(0xFF000000) + background_curtain:set_position(x, y) + background_curtain:set_dimensions(w, h) + background_curtain:set_capture_input(true) + bv = background_curtain:get_view() + bv:set_output(output) + bv:set_layer(background_layer) + + pd.background_view = bv + output:set_private(pd) +end + +function output_moved(output, move_x, move_y) + local views = background_layer:get_views() + for k, v in pairs(views) do + if v:get_output() == output then + x, y = v:get_position() + v:set_position(x + move_x, y + move_y) + end + end + + views = normal_layer:get_views() + for k, v in pairs(views) do + if v:get_output() == output then + x, y = v:get_position() + v:set_position(x + move_x, y + move_y) + end + end +end + +function surface_added(surface) + local pd = {last_width = 0, last_height = 0, maximized = false, + map_fullscreen = false, fullscreen_output = nil} + + pd.view = surface:create_view() + pd.width = 0 + pd.height = 0 + + local outputs = weston:get_outputs() + for n, o in pairs(outputs) do + surface:set_output(o) + pd.view:set_output(o) + if (current_width == 0 and current_height == 0) then + output_width, output_height = o:get_dimensions() + current_width = output_width + current_height = output_height + else + current_width = math.floor(current_width / 2) + current_height = math.floor(current_height / 2) + end + pd.view:set_dimensions(current_width, current_height) + end + + surface:set_private(pd) +end + +function surface_removed(surface) + local pd = surface:get_private() + + if (active_view == pd.view) then + pd.view:deactivate() + active_view = nil + end + + if (pd.fullscreen_output) then + unset_fullscreen(surface) + end + + pd.view:dispose() + + current_width = current_width * 2 + current_height = current_height * 2 + relayout() +end + +function surface_maximize(surface, maximized) + if (maximized) then + local pd = surface:get_private() + pd.view:set_position(0, 0) + + local output = pd.view:get_output() + if (output == nil) then + output = primary_output + end + surface:set_state_maximized(output) + + pd.maximized = true + else + pd.maximized = false + relayout() + end +end + +function set_fullscreen(surface, output) + local surf_pd = surface:get_private() + local output_pd = output:get_private() + + -- We only allow one fullscreen client + if (surf_pd.fullscreen_output ~= nil or output_pd.has_fullscreen_view) then + return + end + + surface:set_state_fullscreen(output) + output_pd.has_fullscreen_view = true + output_pd.background_view:set_layer(fullscreen_layer) + surf_pd.view:move_in_front_of_other_view(output_pd.background_view) + surf_pd.fullscreen_output = output + surf_pd.view:set_position(0, 0) +end + +function unset_fullscreen(surface) + local surf_pd = surface:get_private() + local output = surface:get_output() + local output_pd = output:get_private() + + if (surf_pd.fullscreen_output == nil) then + return + end + + output_pd.background_view:set_layer(background_layer) + surf_pd.view:set_layer(normal_layer) + output_pd.has_fullscreen_view = false + surf_pd.fullscreen_output = nil + relayout() +end + +function surface_fullscreen(surface, output, fullscreen) + if (fullscreen) then + local pd = surface:get_private() + if (output == nil) then + output = pd.view:get_output() + end + + if (output == nil) then + output = primary_output + end + + if (surface:is_mapped()) then + set_fullscreen(surface, output) + else + pd.map_fullscreen = true + end + else + unset_fullscreen(surface) + relayout() + end +end + +function lower_fullscreen_layer(surface, output) + local views_in_fs_layer = fullscreen_layer:get_views() + local count, sorted_views = sort_views(views_in_fs_layer) + + for k, v in ipairs(sorted_views) do + local view = views_in_fs_layer[v] + local pd = view:get_private_surface() + + -- no continue in lua, we have go the other way around + if (pd ~= nil) then + if (output ~= nil and pd.fullscreen_output == output) then + local output_pd = output:get_private() + + if (output_pd.background_view) then + output_pd.background_view:set_layer(background_layer) + end + + view:set_layer(normal_layer) + pd.map_fullscreen = false + pd.fullscreen_output = nil + output_pd.has_fullscreen_view = false + relayout() + end + end + end +end + +function surface_committed(surface) + local pd = surface:get_private() + local w, h = surface:get_dimensions() + local good_seat = nil + local output = nil + + if (w == 0) then + return + end + + local is_resized = w ~= pd.width or h ~= pd.height + + pd.width = w + pd.height = h + + if (is_resized) then + relayout() + end + + if surface:is_mapped() then + return + end + + surface:map() + + local seats = weston:get_seats() + for n, o in pairs(seats) do + good_seat = o + end + + output = pd.view:get_output() + lower_fullscreen_layer(surface, output) + + if (active_view ~= nil) then + active_view:deactivate() + end + + pd.view:activate(good_seat) + active_view = pd.view + pd.view:set_layer(normal_layer) + + if (pd.maximized) then + surface:set_state_maximized(output) + return + elseif (pd.map_fullscreen) then + set_fullscreen(surface, output) + return + end + + relayout() +end + +function click_to_activate(focus_view, seat, button) + if (active_view == focus_view) then + return + end + + if (active_view ~= nil) then + active_view:deactivate() + end + + focus_view:activate(seat) + active_view = focus_view +end + +function my_init() + background_layer = weston:create_layer() + background_layer:set_position(WESTON_LAYER_POSITION_BACKGROUND) + + normal_layer = weston:create_layer() + normal_layer:set_position(WESTON_LAYER_POSITION_NORMAL) + + hidden_layer = weston:create_layer() + hidden_layer:set_position(WESTON_LAYER_POSITION_HIDDEN) + + fullscreen_layer = weston:create_layer() + fullscreen_layer:set_position(WESTON_LAYER_POSITION_FULLSCREEN) + + weston:add_button_binding(BTN_LEFT, 0, click_to_activate) + weston:add_button_binding(BTN_RIGHT, 0, click_to_activate) +end + +lua_shell_callbacks = { + init = my_init, + surface_added = surface_added, + surface_committed = surface_committed, + surface_fullscreen = surface_fullscreen, + surface_maximize = surface_maximize, + surface_removed = surface_removed, + output_create = my_output_create, + output_moved = output_moved, +} diff --git a/man/weston.ini.man b/man/weston.ini.man index 439107e6c..cd615c259 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -109,6 +109,7 @@ directory are: .BR fullscreen (deprecated) .BR ivi .BR kiosk +.BR lua .fi .RE .TP 7 @@ -483,6 +484,9 @@ sets the cursor theme (string). .TP 7 .BI "cursor-size=" 24 sets the cursor size (unsigned integer). +.TP 7 +.BI "lua-script=" script +sets the script for the lua shell. .\"--------------------------------------------------------------------- .SH "LAUNCHER SECTION" There can be multiple launcher sections, one for each launcher. diff --git a/man/weston.man b/man/weston.man index 13e9f5dff..67b199054 100644 --- a/man/weston.man +++ b/man/weston.man @@ -90,6 +90,12 @@ The IVI shell is a special-purpose shell which exposes an API compatible with the GENIVI Layer Manager to user-provided HMI controller modules. It is intended for use in automotive environments. . +.TP +.I lua +The lua shell is a meta-shell that is user-scriptable and configurable. +A supplied Lua script will be interpreted and executed by Weston, +allowing full control over window management in response to events. +. .\" *************************************************************** .SH XWAYLAND Weston can support X11 clients running within a Weston session via an @@ -319,6 +325,12 @@ Give all outputs a refresh rate of Supported values range from 0 mHz to 1,000,000 mHz. 0 is a special value that repaints as soon as possible on capture requests only, not on damages. . +.SS lua shell options: +.TP +\fB\-\-lua\-script\fR=\fIscript\fR +Override weston.ini lua\-script= with +.IR script "." +. . .\" *************************************************************** .SH FILES diff --git a/meson.build b/meson.build index 4a7655f59..63ff77c4d 100644 --- a/meson.build +++ b/meson.build @@ -231,6 +231,7 @@ subdir('desktop-shell') subdir('fullscreen-shell') subdir('ivi-shell') subdir('kiosk-shell') +subdir('lua-shell') subdir('remoting') subdir('pipewire') subdir('clients') diff --git a/meson_options.txt b/meson_options.txt index 788997b6a..41eab349a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -136,6 +136,12 @@ option( value: true, description: 'Weston shell UI: kiosk (desktop apps)' ) +option( + 'shell-lua', + type: 'boolean', + value: true, + description: 'Weston shell UI: Lua (programmable)' +) option( 'desktop-shell-client-default', diff --git a/tests/lua-shell-test.c b/tests/lua-shell-test.c new file mode 100644 index 000000000..606c74a39 --- /dev/null +++ b/tests/lua-shell-test.c @@ -0,0 +1,173 @@ +/* + * Copyright © 2016-2023 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 "config.h" + +#include +#include +#include + +#include "libweston-internal.h" +#include "libweston/desktop.h" +#include "shared/xalloc.h" +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "weston-test-assert.h" +#include "tests/test-config.h" +#include "xdg-client-helper.h" + +#define NR_XDG_SURFACES 4 + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = WESTON_RENDERER_PIXMAN; + setup.width = 320; + setup.height = 240; + setup.shell = SHELL_LUA; + setup.logging_scopes = "log,test-harness-plugin"; + setup.refresh = HIGHEST_OUTPUT_REFRESH; + + weston_ini_setup(&setup, + cfgln("[shell]"), + cfgln("lua-script=%s", WESTON_LUA_SHELL_DIR "/shell.lua")); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +#define DECLARE_LIST_ITERATOR(name, parent, list, child, link) \ +static child * \ +next_##name(parent *from, child *pos) \ +{ \ + struct wl_list *entry = pos ? &pos->link : &from->list; \ + child *ret = wl_container_of(entry->next, ret, link); \ + return (&ret->link == &from->list) ? NULL : ret; \ +} + +DECLARE_LIST_ITERATOR(pnode_from_z, struct weston_output, paint_node_z_order_list, + struct weston_paint_node, z_order_link); + +TEST(four_apps_in_a_square) +{ + struct wet_testsuite_data *suite_data = TEST_GET_SUITE_DATA(); + struct app { + const char *title_id; + int width, height; + float x, y; + } apps[NR_XDG_SURFACES] = { + { "one", 320, 240, 0.0, 0.0 }, + { "two", 160, 120, 160.0, 0.0 }, + { "three", 80, 60, 0.0, 120.0 }, + { "four", 40, 30, 160.0, 120.0 }, + }; + int i = NR_XDG_SURFACES - 1; + + struct xdg_client *xdg_client = create_xdg_client(); + + struct xdg_surface_data *xdg_surface1 = create_xdg_surface(xdg_client); + struct xdg_surface_data *xdg_surface2 = create_xdg_surface(xdg_client); + struct xdg_surface_data *xdg_surface3 = create_xdg_surface(xdg_client); + struct xdg_surface_data *xdg_surface4 = create_xdg_surface(xdg_client); + + test_assert_ptr_not_null(xdg_client); + test_assert_ptr_not_null(xdg_surface1); + test_assert_ptr_not_null(xdg_surface2); + test_assert_ptr_not_null(xdg_surface3); + test_assert_ptr_not_null(xdg_surface4); + + xdg_surface_make_toplevel(xdg_surface1, "weston.test.lua.one", "one"); + xdg_surface_wait_configure(xdg_surface1); + + xdg_surface_make_toplevel(xdg_surface2, "weston.test.lua.two", "two"); + xdg_surface_wait_configure(xdg_surface2); + + xdg_surface_make_toplevel(xdg_surface3, "weston.test.lua.three", "three"); + xdg_surface_wait_configure(xdg_surface3); + + xdg_surface_make_toplevel(xdg_surface4, "weston.test.lua.four", "four"); + xdg_surface_wait_configure(xdg_surface4); + + client_push_breakpoint(xdg_client->client, suite_data, + WESTON_TEST_BREAKPOINT_POST_REPAINT, + (struct wl_proxy *) xdg_client->client->output->wl_output); + + xdg_surface_commit_solid(xdg_surface1, 255, 0, 0); + xdg_surface_commit_solid(xdg_surface2, 255, 0, 0); + xdg_surface_commit_solid(xdg_surface3, 255, 0, 0); + xdg_surface_commit_solid(xdg_surface4, 255, 0, 0); + + RUN_INSIDE_BREAKPOINT(xdg_client->client, suite_data) { + struct weston_head *head = breakpoint->resource; + struct weston_output *output = head->output; + struct weston_paint_node *pnode = NULL; + + test_assert_enum(breakpoint->template_->breakpoint, + WESTON_TEST_BREAKPOINT_POST_REPAINT); + + while ((pnode = next_pnode_from_z(output, pnode)) != NULL && i > -1) { + struct weston_view *view = pnode->view; + struct weston_surface *surface = view->surface; + struct weston_buffer *buffer = surface->buffer_ref.buffer; + struct weston_desktop_surface *wds = + weston_surface_get_desktop_surface(surface); + const char *wds_title = + weston_desktop_surface_get_title(wds); + struct weston_geometry geom = + weston_desktop_surface_get_geometry(wds); + struct weston_coord_global pos = + weston_view_get_pos_offset_global(view); + struct app app = apps[i--]; + + test_assert_ptr_not_null(pnode); + test_assert_ptr_not_null(surface); + test_assert_ptr_not_null(view); + test_assert_ptr_not_null(buffer); + + test_assert_true(weston_view_is_mapped(view)); + test_assert_true(weston_surface_is_mapped(surface)); + + test_assert_str_eq(wds_title, app.title_id); + + test_assert_int_eq(geom.width, app.width); + test_assert_int_eq(geom.height, app.height); + + test_assert_f32_eq(pos.c.x, app.x); + test_assert_f32_eq(pos.c.y, app.y); + } + } + + destroy_xdg_surface(xdg_surface1); + destroy_xdg_surface(xdg_surface2); + destroy_xdg_surface(xdg_surface3); + destroy_xdg_surface(xdg_surface4); + + xdg_client_destroy(xdg_client); + + return RESULT_OK; +} diff --git a/tests/meson.build b/tests/meson.build index f3084cddc..e9c88e2d2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -402,10 +402,24 @@ if get_option('shell-kiosk') ] endif +if get_option('shell-lua') + tests += [ + { + 'name': 'lua-shell', + 'sources': [ + 'lua-shell-test.c', + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + ], + }, + ] +endif + test_config_h = configuration_data() test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir() + '/reference') test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) test_config_h.set_quoted('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), '..', 'data')) +test_config_h.set_quoted('WESTON_LUA_SHELL_DIR', join_paths(meson.current_source_dir(), '..', 'lua-shell')) test_config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) test_config_h.set10('WESTON_TEST_SKIP_IS_FAILURE', get_option('test-skip-is-failure')) configure_file(output: 'test-config.h', configuration: test_config_h) diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index 6207e64bf..ed2638c33 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -242,6 +242,7 @@ shell_to_str(enum shell_type t) [SHELL_FULLSCREEN] = "fullscreen", [SHELL_IVI] = "ivi", [SHELL_KIOSK] = "kiosk", + [SHELL_LUA] = "lua", }; test_assert_true(t >= 0 && t < ARRAY_LENGTH(names)); return names[t]; diff --git a/tests/weston-test-fixture-compositor.h b/tests/weston-test-fixture-compositor.h index 8a9c5706a..117446f95 100644 --- a/tests/weston-test-fixture-compositor.h +++ b/tests/weston-test-fixture-compositor.h @@ -54,6 +54,8 @@ enum shell_type { SHELL_FULLSCREEN, /** The kiosk shell */ SHELL_KIOSK, + /** The lua shell */ + SHELL_LUA, }; /** Weston compositor configuration diff --git a/weston.ini.in b/weston.ini.in index 2da7c3618..2d4a88a85 100644 --- a/weston.ini.in +++ b/weston.ini.in @@ -14,6 +14,7 @@ panel-color=0x90ff0000 locking=true animation=zoom startup-animation=fade +lua-script=@libexecdir@/shell.lua #binding-modifier=ctrl #num-workspaces=6 #cursor-theme=whiteglass