Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1835,8 +1835,10 @@ Place [this definition file](https://github.com/dawsers/scroll/blob/master/scrol
in one of your development Lua runtime directories for the Lua LSP server to
have access to the API information.

Using the command `lua` you can run Lua scripts that access window manager
properties, execute commands or add callbacks to window events.
Using the command `lua` (for script files) or `lua_eval` (for inline scripts)
you can run Lua scripts that access window manager properties, execute commands
or add callbacks to window events. All calls to `lua_eval` share the same
persistent state.

You can assign scripts to keyboard bindings, or add them to your
configuration for execution when the configuration loads.
Expand Down
2 changes: 2 additions & 0 deletions completions/bash/scrollmsg
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ _scrollmsg()
'get_config'
'send_tick'
'subscribe'
'lua'
'lua_eval'
)

short=(
Expand Down
2 changes: 2 additions & 0 deletions completions/fish/scrollmsg.fish
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ complete -c scrollmsg -s t -l type -fra 'get_config' --description "Gets a JSON-
complete -c scrollmsg -s t -l type -fra 'get_seats' --description "Gets a JSON-encoded list of all seats, its properties and all assigned devices."
complete -c scrollmsg -s t -l type -fra 'send_tick' --description "Sends a tick event to all subscribed clients."
complete -c scrollmsg -s t -l type -fra 'subscribe' --description "Subscribe to a list of event types."
complete -c scrollmsg -s t -l type -fra 'lua' --description "Execute a Lua script file with arguments."
complete -c scrollmsg -s t -l type -fra 'lua_eval' --description "Execute inline Lua code with arguments."
2 changes: 2 additions & 0 deletions completions/zsh/_scrollmsg
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ types=(
'get_config'
'send_tick'
'subscribe'
'lua'
'lua_eval'
)

_arguments -s \
Expand Down
27 changes: 14 additions & 13 deletions include/ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,26 @@ enum ipc_command_type {
IPC_GET_TRAILS = 121,
IPC_GET_SPACES = 122,
IPC_GET_BINDINGS = 123,
IPC_LUA_EXEC = 124,

// Events sent from sway to clients. Events have the highest bits set.
IPC_EVENT_WORKSPACE = ((1<<31) | 0),
IPC_EVENT_OUTPUT = ((1<<31) | 1),
IPC_EVENT_MODE = ((1<<31) | 2),
IPC_EVENT_WINDOW = ((1<<31) | 3),
IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4),
IPC_EVENT_BINDING = ((1<<31) | 5),
IPC_EVENT_SHUTDOWN = ((1<<31) | 6),
IPC_EVENT_TICK = ((1<<31) | 7),
IPC_EVENT_WORKSPACE = ((1 << 31) | 0),
IPC_EVENT_OUTPUT = ((1 << 31) | 1),
IPC_EVENT_MODE = ((1 << 31) | 2),
IPC_EVENT_WINDOW = ((1 << 31) | 3),
IPC_EVENT_BARCONFIG_UPDATE = ((1 << 31) | 4),
IPC_EVENT_BINDING = ((1 << 31) | 5),
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
IPC_EVENT_TICK = ((1 << 31) | 7),

// sway-specific event types
IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20),
IPC_EVENT_INPUT = ((1<<31) | 21),
IPC_EVENT_BAR_STATE_UPDATE = ((1 << 31) | 20),
IPC_EVENT_INPUT = ((1 << 31) | 21),

// scroll-specific event types
IPC_EVENT_LUA = ((1<<31) | 29),
IPC_EVENT_SCROLLER = ((1<<31) | 30),
IPC_EVENT_TRAILS = ((1<<31) | 31),
IPC_EVENT_LUA = ((1 << 31) | 29),
IPC_EVENT_SCROLLER = ((1 << 31) | 30),
IPC_EVENT_TRAILS = ((1 << 31) | 31),
};

#endif
1 change: 1 addition & 0 deletions include/sway/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ sway_cmd cmd_layout_transpose;
sway_cmd cmd_layout_widths;
sway_cmd cmd_log_colors;
sway_cmd cmd_lua;
sway_cmd cmd_lua_eval;
sway_cmd cmd_mark;
sway_cmd cmd_max_render_time;
sway_cmd cmd_maximize_if_single;
Expand Down
7 changes: 7 additions & 0 deletions include/sway/lua.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct sway_lua {
list_t *cbs_jump_end;
int command_data;
list_t *cbs_command_end;
struct sway_container *context_container;
};

int luaopen_scroll(lua_State *L);
Expand All @@ -54,5 +55,11 @@ void lua_execute_ipc_view_cbs(struct sway_view *view, const char *change);
void lua_execute_ipc_workspace_cbs(struct sway_workspace *old_ws,
struct sway_workspace *new_ws, const char *change);
void lua_execute_jump_end_cbs(struct sway_container *container);
struct sway_lua_script *sway_lua_get_or_create_script(const char *name);

struct json_object;
struct json_object *sway_lua_value_to_json(lua_State *L, int i);
struct json_object *sway_lua_table_to_json(lua_State *L, int index);
void sway_lua_push_json_to_lua(lua_State *L, struct json_object *obj);

#endif
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
addopts = -n 4
22 changes: 22 additions & 0 deletions scroll.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ function scroll.focused_view() end
--- @return integer|nil
function scroll.focused_container() end

---
--- Returns the container ID of the execution context if the command/script
--- was run via criteria (e.g. `[class="XTerm"] lua script.lua`), or nil if
--- there is no context (run globally).
---
--- @return integer|nil
function scroll.context_container() end

---
--- Returns the focused workspace ID or nil if none.
---
Expand Down Expand Up @@ -653,4 +661,18 @@ function scroll.add_callback(event, cb_func, cb_data) end
--- @return integer
function scroll.remove_callback(id) end

---
--- Returns true if there is an active animation running.
---
--- @return boolean
---
function scroll.animating() end

---
--- Returns true if there are pending transactions that haven't been applied yet.
---
--- @return boolean
---
function scroll.pending_transactions() end

return scroll
1 change: 1 addition & 0 deletions sway/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ static const struct cmd_handler handlers[] = {
{ "hide_edge_borders", cmd_hide_edge_borders },
{ "input", cmd_input },
{ "lua", cmd_lua },
{ "lua_eval", cmd_lua_eval },
{ "mode", cmd_mode },
{ "mouse_resize_tiling_limit", cmd_mouse_resize_tiling_limit },
{ "mouse_warping", cmd_mouse_warping },
Expand Down
114 changes: 91 additions & 23 deletions sway/commands/lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,7 @@
#include <wordexp.h>
#include "sway/commands.h"
#include "log.h"

static struct sway_lua_script *find_script(list_t *scripts, const char *name) {
for (int i = 0; i < scripts->length; ++i) {
struct sway_lua_script *script = scripts->items[i];
if (strcmp(script->name, name) == 0) {
return script;
}
}
return NULL;
}
#include "sway/lua.h"

struct cmd_results *cmd_lua(int argc, char **argv) {
struct cmd_results *error = NULL;
Expand Down Expand Up @@ -84,21 +75,33 @@ struct cmd_results *cmd_lua(int argc, char **argv) {
return res;
}

int top = lua_gettop(config->lua.state);

struct sway_container *old_context_container = config->lua.context_container;
if (config->handler_context.node_overridden) {
config->lua.context_container = config->handler_context.container;
} else {
config->lua.context_container = NULL;
}

int err = luaL_loadfile(config->lua.state, expanded_path);
if (err != LUA_OK) {
struct cmd_results *res = cmd_results_new(CMD_FAILURE, "Error %d loading lua script %s", err, expanded_path);
free(expanded_path);
return res;
const char *str = luaL_checkstring(config->lua.state, -1);
if (str) {
res = cmd_results_new(
CMD_FAILURE, "Error %s loading lua script %s", str, expanded_path);
} else {
res = cmd_results_new(
CMD_FAILURE, "Error %d loading lua script %s", err, expanded_path);
}
goto restore_context;
}

// Search if there is already a state for this script
struct sway_lua_script *script = find_script(config->lua.scripts, expanded_path);
struct sway_lua_script *script = sway_lua_get_or_create_script(expanded_path);
if (!script) {
script = malloc(sizeof(struct sway_lua_script));
script->name = strdup(expanded_path);
lua_createtable(config->lua.state, 0, 0);
script->state = luaL_ref(config->lua.state, LUA_REGISTRYINDEX);
list_add(config->lua.scripts, script);
res = cmd_results_new(CMD_FAILURE, "Failed to allocate memory");
goto restore_context;
}

// Create args table before running the script
Expand All @@ -112,16 +115,81 @@ struct cmd_results *cmd_lua(int argc, char **argv) {
err = lua_pcall(config->lua.state, 2, LUA_MULTRET, 0);
if (err != LUA_OK) {
const char *str = luaL_checkstring(config->lua.state, -1);
struct cmd_results *res;
if (str) {
res = cmd_results_new(CMD_FAILURE, "Error %s executing lua script %s", str, expanded_path);
} else {
res = cmd_results_new(CMD_FAILURE, "Error %d executing lua script %s", err, expanded_path);
}
free(expanded_path);
return res;
goto restore_context;
}

res = cmd_results_new(CMD_SUCCESS, NULL);

restore_context:
config->lua.context_container = old_context_container;
free(expanded_path);
return cmd_results_new(CMD_SUCCESS, NULL);
lua_settop(config->lua.state, top);
return res;
}

struct cmd_results *cmd_lua_eval(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "lua_eval", EXPECTED_AT_LEAST, 1))) {
return error;
}

int top = lua_gettop(config->lua.state);

struct sway_container *old_context_container = config->lua.context_container;
if (config->handler_context.node_overridden) {
config->lua.context_container = config->handler_context.container;
} else {
config->lua.context_container = NULL;
}

struct cmd_results *res = NULL;

int err = luaL_loadstring(config->lua.state, argv[0]);
if (err != LUA_OK) {
const char *str = luaL_checkstring(config->lua.state, -1);
if (str) {
res = cmd_results_new(CMD_FAILURE, "Error %s loading lua string", str);
} else {
res = cmd_results_new(CMD_FAILURE, "Error %d loading lua string", err);
}
goto restore_context;
}

// Search if there is already a state for this script
struct sway_lua_script *script = sway_lua_get_or_create_script("");
if (!script) {
res = cmd_results_new(CMD_FAILURE, "Failed to allocate memory");
goto restore_context;
}

// Create args table before running the script
lua_createtable(config->lua.state, argc - 1, 0);
for (int i = 1; i < argc; ++i) {
lua_pushstring(config->lua.state, argv[i]);
lua_rawseti(config->lua.state, -2, i);
}
lua_pushlightuserdata(config->lua.state, script);

err = lua_pcall(config->lua.state, 2, LUA_MULTRET, 0);
if (err != LUA_OK) {
const char *str = luaL_checkstring(config->lua.state, -1);
if (str) {
res = cmd_results_new(CMD_FAILURE, "Error %s executing lua string", str);
} else {
res = cmd_results_new(CMD_FAILURE, "Error %d executing lua string", err);
}
goto restore_context;
}

res = cmd_results_new(CMD_SUCCESS, NULL);

restore_context:
config->lua.context_container = old_context_container;
lua_settop(config->lua.state, top);
return res;
}
Loading