/*
 * 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 <config.h>
#include <assert.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#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 <libweston/xwayland-api.h>

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_ready(struct lua_State *lua)
{
	struct lua_shell_output *shoutput = get_output_from_arg(lua, 1);

	weston_output_set_ready(shoutput->output);
	return 0;
}

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 },
		{ "set_ready", lua_shell_env_output_set_ready },
		{ 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_ptr_not_null(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_ptr_not_null(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;
}
