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
28 changes: 27 additions & 1 deletion globalconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,37 @@ typedef struct InputSettings {
char *tap_button_map; /* "lrm", "lmr" */
char *clickfinger_button_map; /* "lrm", "lmr" */
bool accel_speed_set; /* true if accel_speed was explicitly set */

/* Tablet/touch rule properties (used by awful.input.rules). tool_mode
* is tablet tool-specific; map_to_output/map_to_region/map_from_region
* are shared by both "tablet" and "touch" rule types. */
char *tool_mode; /* "absolute", "relative" */
int emulate_pointer; /* touch-specific: synthesize wl_pointer
* clicks for clients that never bound
* wl_touch; 0=off, 1=on */
char **map_to_output_list; /* ordered candidates: "*", output name, or
* make/model/serial identifier; first
* connected candidate wins */
int map_to_output_count;
bool map_from_region_set; /* true if region is explicitly set */
double map_from_region_x1;
double map_from_region_y1;
double map_from_region_x2;
double map_from_region_y2;

/* Absolute output-layout-pixel target rectangle; takes precedence over
* map_to_output_list when set (mirrors sway: an input is mapped to
* either an output or a region, not both). */
bool map_to_region_set;
double map_to_region_x1;
double map_to_region_y1;
double map_to_region_x2;
double map_to_region_y2;
} InputSettings;

/** Per-device input rule (evaluated in order, last match wins per property) */
typedef struct InputRule {
char *type; /* "touchpad" or "pointer", NULL=match any */
char *type; /* e.g. "touchpad", "pointer", "tablet", "tablet-tool-pen", "tablet-tool", "touch"; NULL=match any */
char *name; /* device name substring, NULL=match any */
InputSettings properties;
} InputRule;
Expand Down
196 changes: 196 additions & 0 deletions luaa.c
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,10 @@ input_rules_free(void)
free(r->properties.send_events_mode);
free(r->properties.accel_profile);
free(r->properties.tap_button_map);
free(r->properties.tool_mode);
for (int j = 0; j < r->properties.map_to_output_count; j++)
free(r->properties.map_to_output_list[j]);
free(r->properties.map_to_output_list);
}
free(globalconf.input_rules);
globalconf.input_rules = NULL;
Expand Down Expand Up @@ -1062,6 +1066,20 @@ input_settings_init_unset(InputSettings *s)
s->accel_speed = 0.0;
s->tap_button_map = NULL;
s->accel_speed_set = false;
s->tool_mode = NULL;
s->emulate_pointer = -2;
s->map_to_output_list = NULL;
s->map_to_output_count = 0;
s->map_from_region_set = false;
s->map_from_region_x1 = 0.0;
s->map_from_region_y1 = 0.0;
s->map_from_region_x2 = 0.0;
s->map_from_region_y2 = 0.0;
s->map_to_region_set = false;
s->map_to_region_x1 = 0.0;
s->map_to_region_y1 = 0.0;
s->map_to_region_x2 = 0.0;
s->map_to_region_y2 = 0.0;
}

/** Read an optional int field from a Lua table on the stack.
Expand Down Expand Up @@ -1089,6 +1107,168 @@ input_rule_get_string(lua_State *L, int idx, const char *field)
return result;
}

/** Read and validate optional tool mode from a Lua table.
* Returns strdup'd value or NULL if field is absent. */
static char *
input_rule_get_tool_mode(lua_State *L, int idx, const char *field)
{
char *result = NULL;
const char *val;

lua_getfield(L, idx, field);
if (!lua_isnil(L, -1)) {
val = luaL_checkstring(L, -1);
if (strcmp(val, "absolute") != 0 && strcmp(val, "relative") != 0) {
lua_pop(L, 1);
luaL_error(L,
"awful.input.rules: invalid properties.%s '%s' (expected 'absolute' or 'relative')",
field, val);
return NULL;
}
result = strdup(val);
}
lua_pop(L, 1);
return result;
}

/** Read and parse an optional two-corner region field from a Lua table.
* Used for both map_from_region (tablet-space, normalized/mm) and
* map_to_region (output-layout pixels) - the field itself carries no
* semantics here, just two corner points. Accepts either:
* - string: "X1xY1 X2xY2"
* - table: { x1 = <number>, y1 = <number>, x2 = <number>, y2 = <number> }
*/
static void
input_rule_get_region(lua_State *L, int idx, const char *field,
bool *set, double *x1, double *y1, double *x2, double *y2)
{
*set = false;

lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return;
}

if (lua_isstring(L, -1)) {
const char *raw = lua_tostring(L, -1);
double sx1, sy1, sx2, sy2;
int parsed = sscanf(raw, "%lfx%lf %lfx%lf", &sx1, &sy1, &sx2, &sy2);
if (parsed != 4) {
lua_pop(L, 1);
luaL_error(L,
"awful.input.rules: invalid properties.%s '%s' (expected 'X1xY1 X2xY2')",
field, raw);
return;
}
*x1 = sx1;
*y1 = sy1;
*x2 = sx2;
*y2 = sy2;
*set = true;
lua_pop(L, 1);
return;
}

if (lua_istable(L, -1)) {
double sx1, sy1, sx2, sy2;

lua_getfield(L, -1, "x1");
sx1 = luaL_checknumber(L, -1);
lua_pop(L, 1);

lua_getfield(L, -1, "y1");
sy1 = luaL_checknumber(L, -1);
lua_pop(L, 1);

lua_getfield(L, -1, "x2");
sx2 = luaL_checknumber(L, -1);
lua_pop(L, 1);

lua_getfield(L, -1, "y2");
sy2 = luaL_checknumber(L, -1);
lua_pop(L, 1);

*x1 = sx1;
*y1 = sy1;
*x2 = sx2;
*y2 = sy2;
*set = true;
lua_pop(L, 1);
return;
}

lua_pop(L, 1);
luaL_error(L,
"awful.input.rules: invalid properties.%s type (expected string or table)",
field);
}

/** Read the optional map_to_output field from a Lua table.
* Accepts either a single string or an ordered array of strings, tried in
* order with the first connected candidate winning. Returns a newly
* allocated array of strdup'd strings via *out_list (NULL if the field is
* absent), with element count via *out_count. */
static void
input_rule_get_map_to_output(lua_State *L, int idx, const char *field,
char ***out_list, int *out_count)
{
*out_list = NULL;
*out_count = 0;

lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return;
}

if (lua_isstring(L, -1)) {
char **list = calloc(1, sizeof(char *));
list[0] = strdup(lua_tostring(L, -1));
*out_list = list;
*out_count = 1;
lua_pop(L, 1);
return;
}

if (lua_istable(L, -1)) {
int n = luaA_rawlen(L, -1);
char **list;

if (n == 0) {
lua_pop(L, 1);
return;
}

list = calloc(n, sizeof(char *));
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
if (!lua_isstring(L, -1)) {
lua_pop(L, 2);
for (int j = 0; j < i; j++)
free(list[j]);
free(list);
luaL_error(L,
"awful.input.rules: invalid properties.%s[%d] (expected string)",
field, i + 1);
return;
}
list[i] = strdup(lua_tostring(L, -1));
lua_pop(L, 1);
}

*out_list = list;
*out_count = n;
lua_pop(L, 1);
return;
}

lua_pop(L, 1);
luaL_error(L,
"awful.input.rules: invalid properties.%s type (expected string or array of strings)",
field);
}

/** Set input rules from Lua.
* Expects a table of { rule = { type=..., name=... }, properties = { ... } } entries.
* \param L Lua state. Stack: [1] = rules table (array).
Expand Down Expand Up @@ -1147,13 +1327,29 @@ luaA_awesome_set_input_rules(lua_State *L)
p->middle_button_emulation = input_rule_get_int(L, pidx, "middle_button_emulation", -2);
p->scroll_button = input_rule_get_int(L, pidx, "scroll_button", -2);
p->scroll_button_lock = input_rule_get_int(L, pidx, "scroll_button_lock", -2);
p->emulate_pointer = input_rule_get_int(L, pidx, "emulate_pointer", -2);

p->scroll_method = input_rule_get_string(L, pidx, "scroll_method");
p->click_method = input_rule_get_string(L, pidx, "click_method");
p->clickfinger_button_map = input_rule_get_string(L, pidx, "clickfinger_button_map");
p->send_events_mode = input_rule_get_string(L, pidx, "send_events_mode");
p->accel_profile = input_rule_get_string(L, pidx, "accel_profile");
p->tap_button_map = input_rule_get_string(L, pidx, "tap_button_map");
p->tool_mode = input_rule_get_tool_mode(L, pidx, "tool_mode");
input_rule_get_map_to_output(L, pidx, "map_to_output",
&p->map_to_output_list, &p->map_to_output_count);
input_rule_get_region(L, pidx, "map_from_region",
&p->map_from_region_set,
&p->map_from_region_x1,
&p->map_from_region_y1,
&p->map_from_region_x2,
&p->map_from_region_y2);
input_rule_get_region(L, pidx, "map_to_region",
&p->map_to_region_set,
&p->map_to_region_x1,
&p->map_to_region_y1,
&p->map_to_region_x2,
&p->map_to_region_y2);

lua_getfield(L, pidx, "accel_speed");
if (!lua_isnil(L, -1)) {
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ protocols = [
[wayland_protocols_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'enum-header'],
[wayland_protocols_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', 'enum-header'],
[wayland_protocols_dir / 'stable/xdg-shell/xdg-shell.xml', 'server-header'],
[wayland_protocols_dir / 'stable/tablet/tablet-v2.xml', 'server-header'],
# Local protocols
['protocols/wlr-layer-shell-unstable-v1.xml', 'enum-header'],
['protocols/wlr-output-power-management-unstable-v1.xml', 'server-header'],
Expand Down
Loading
Loading