diff --git a/CMake/FindLibInput.cmake b/CMake/FindLibInput.cmake new file mode 100644 index 000000000..877219981 --- /dev/null +++ b/CMake/FindLibInput.cmake @@ -0,0 +1,66 @@ +#.rst: +# FindLibInput +# ------- +# +# Find LibInput library +# +# Try to find LibInpu library. The following values are defined +# +# :: +# +# LIBINPUT_FOUND - True if libinput is available +# LIBINPUT_INCLUDE_DIRS - Include directories for libinput +# LIBINPUT_LIBRARIES - List of libraries for libinput +# LIBINPUT_DEFINITIONS - List of definitions for libinput +# +# and also the following more fine grained variables +# +# :: +# +# LIBINPUT_VERSION +# LIBINPUT_VERSION_MAJOR +# LIBINPUT_VERSION_MINOR +# LIBINPUT_VERSION_MICRO +# +#============================================================================= +# Copyright (c) 2015 Jari Vetoniemi +# +# Distributed under the OSI-approved BSD License (the "License"); +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= + +include(FeatureSummary) +set_package_properties(LibInput PROPERTIES + URL "http://freedesktop.org/wiki/Software/libinput/" + DESCRIPTION "Library to handle input devices") + +find_package(PkgConfig) +pkg_check_modules(PC_INPUT QUIET libinput) +find_library(LIBINPUT_LIBRARIES NAMES input HINTS ${PC_INPUT_LIBRARY_DIRS}) +find_path(LIBINPUT_INCLUDE_DIRS libinput.h HINTS ${PC_INPUT_INCLUDE_DIRS}) + +set(LIBINPUT_VERSION ${PC_INPUT_VERSION}) +string(REPLACE "." ";" VERSION_LIST "${PC_INPUT_VERSION}") + +LIST(LENGTH VERSION_LIST n) +if (n EQUAL 3) + list(GET VERSION_LIST 0 LIBINPUT_VERSION_MAJOR) + list(GET VERSION_LIST 1 LIBINPUT_VERSION_MINOR) + list(GET VERSION_LIST 2 LIBINPUT_VERSION_MICRO) +endif () + +# This is compatible with libinput-version.h that exists in upstream +# but isn't in distribution (probably forgotten) +set(LIBINPUT_DEFINITIONS ${PC_INPUT_CFLAGS_OTHER} + -DLIBINPUT_VERSION=\"${LIBINPUT_VERSION}\" + -DLIBINPUT_VERSION_MAJOR=${LIBINPUT_VERSION_MAJOR} + -DLIBINPUT_VERSION_MINOR=${LIBINPUT_VERSION_MINOR} + -DLIBINPUT_VERSION_MICRO=${LIBINPUT_VERSION_MICRO}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBINPUT DEFAULT_MSG LIBINPUT_INCLUDE_DIRS LIBINPUT_LIBRARIES) +mark_as_advanced(LIBINPUT_INCLUDE_DIRS LIBINPUT_LIBRARIES LIBINPUT_DEFINITIONS + LIBINPUT_VERSION LIBINPUT_VERSION_MAJOR LIBINPUT_VERSION_MICRO LIBINPUT_VERSION_MINOR) diff --git a/CMakeLists.txt b/CMakeLists.txt index 587c3dacd..4620ff54a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,9 @@ find_package(Pango) find_package(GdkPixbuf) find_package(PAM) +find_package(LibInput REQUIRED) +include_directories(${LIBINPUT_INClUDE_DIRS}) + find_package(Backtrace) if(Backtrace_FOUND) include_directories("${Backtrace_INCLUDE_DIRS}") diff --git a/include/commands.h b/include/commands.h index 52d56e4ad..5fa66bb67 100644 --- a/include/commands.h +++ b/include/commands.h @@ -17,7 +17,8 @@ enum cmd_status { CMD_BLOCK_END, CMD_BLOCK_MODE, CMD_BLOCK_BAR, - CMD_BLOCK_BAR_COLORS + CMD_BLOCK_BAR_COLORS, + CMD_BLOCK_INPUT }; /** diff --git a/include/config.h b/include/config.h index e6a85b292..c2b67aa60 100644 --- a/include/config.h +++ b/include/config.h @@ -1,7 +1,9 @@ #ifndef _SWAY_CONFIG_H #define _SWAY_CONFIG_H +#include #include +#include #include #include #include "wayland-desktop-shell-server-protocol.h" @@ -45,6 +47,25 @@ struct sway_mode { list_t *bindings; }; +/** + * libinput options for input devices + */ +struct input_config { + char *identifier; + int click_method; + int drag_lock; + int dwt; + int middle_emulation; + int natural_scroll; + float pointer_accel; + int scroll_method; + int send_events; + int tap; + + bool capturable; + struct wlc_geometry region; +}; + /** * Size and position configuration for a particular output. * @@ -136,6 +157,7 @@ struct sway_config { list_t *cmd_queue; list_t *workspace_outputs; list_t *output_configs; + list_t *input_configs; list_t *criteria; list_t *active_bar_modifiers; struct sway_mode *current_mode; @@ -172,6 +194,12 @@ bool read_config(FILE *file, bool is_active); * Does variable replacement for a string based on the config's currently loaded variables. */ char *do_var_replacement(char *str); + +int input_identifier_cmp(const void *item, const void *data); +void merge_input_config(struct input_config *dst, struct input_config *src); +void apply_input_config(struct input_config *ic, struct libinput_device *dev); +void free_input_config(struct input_config *ic); + int output_name_cmp(const void *item, const void *data); void merge_output_config(struct output_config *dst, struct output_config *src); /** Sets up a WLC output handle based on a given output_config. diff --git a/include/input.h b/include/input.h new file mode 100644 index 000000000..4ed9bffe7 --- /dev/null +++ b/include/input.h @@ -0,0 +1,23 @@ +#ifndef _SWAY_INPUT_H +#define _SWAY_INPUT_H + +#include +#include "config.h" +#include "list.h" + +struct input_config *new_input_config(const char* identifier); + +char* libinput_dev_unique_id(struct libinput_device *dev); + +/** + * Global input device list. + */ +extern list_t *input_devices; + +/** + * Pointer used when reading input blocked. + * Shared so that it can be cleared from commands.c when closing the block + */ +extern struct input_config *current_input_config; + +#endif diff --git a/include/ipc.h b/include/ipc.h index 565935295..934049c2c 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -5,11 +5,12 @@ enum ipc_command_type { IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_SUBSCRIBE = 2, - IPC_GET_OUTPUTS = 3, + IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4, IPC_GET_MARKS = 5, IPC_GET_BAR_CONFIG = 6, - IPC_GET_VERSION = 7, + IPC_GET_VERSION = 7, + IPC_GET_INPUTS = 8, // Events send from sway to clients. Events have the higest bit set. IPC_EVENT_WORKSPACE = (1 << 31 | 0), IPC_EVENT_OUTPUT = (1 << 31 | 1), @@ -18,6 +19,7 @@ enum ipc_command_type { IPC_EVENT_BARCONFIG_UPDATE = (1 << 31 | 4), IPC_EVENT_BINDING = (1 << 31 | 5), IPC_EVENT_MODIFIER = (1 << 31 | 6), + IPC_EVENT_INPUT = (1 << 31 | 7), IPC_SWAY_GET_PIXELS = 0x81 }; diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt index 23829dc35..f7cc88ab8 100644 --- a/sway/CMakeLists.txt +++ b/sway/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories( ${PCRE_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${XKBCOMMON_INCLUDE_DIRS} + ${LIBINPUT_LIBRARIES} ) add_executable(sway @@ -15,6 +16,7 @@ add_executable(sway extensions.c focus.c handlers.c + input.c input_state.c ipc-server.c layout.c @@ -32,6 +34,7 @@ target_link_libraries(sway ${PCRE_LIBRARIES} ${JSONC_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} + ${LIBINPUT_LIBRARIES} m ) diff --git a/sway/commands.c b/sway/commands.c index 1c7eb9629..5c8653edb 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include #include "stringop.h" #include "layout.h" #include "focus.h" @@ -26,6 +29,8 @@ #include "input_state.h" #include "criteria.h" #include "ipc-server.h" +#include "list.h" +#include "input.h" typedef struct cmd_results *sway_cmd(int argc, char **argv); @@ -49,6 +54,7 @@ static sway_cmd cmd_focus_follows_mouse; static sway_cmd cmd_for_window; static sway_cmd cmd_fullscreen; static sway_cmd cmd_gaps; +static sway_cmd cmd_input; static sway_cmd cmd_kill; static sway_cmd cmd_layout; static sway_cmd cmd_log_colors; @@ -866,6 +872,293 @@ static struct cmd_results *cmd_orientation(int argc, char **argv) { return cmd_results_new(CMD_SUCCESS, NULL, NULL); } +static void input_cmd_apply(struct input_config *input) { + int i; + i = list_seq_find(config->input_configs, input_identifier_cmp, input->identifier); + if (i >= 0) { + // merge existing config + struct input_config *ic = config->input_configs->items[i]; + merge_input_config(ic, input); + free_input_config(input); + input = ic; + } else { + list_add(config->input_configs, input); + } + + current_input_config = input; + + if (input->identifier) { + // Try to find the input device and apply configuration now. If + // this is during startup then there will be no container and config + // will be applied during normal "new input" event from wlc. + struct libinput_device *device = NULL; + for (int i = 0; i < input_devices->length; ++i) { + device = input_devices->items[i]; + char* dev_identifier = libinput_dev_unique_id(device); + int match = dev_identifier && strcmp(dev_identifier, input->identifier) == 0; + free(dev_identifier); + if (match) { + apply_input_config(input, device); + break; + } + } + } +} + +static struct cmd_results *input_cmd_click_method(int argc, char **argv) { + sway_log(L_DEBUG, "click_method for device: %d %s", current_input_config==NULL, current_input_config->identifier); + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "click_method", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "click_method", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "none") == 0) { + new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_NONE; + } else if (strcasecmp(argv[0], "button_areas") == 0) { + new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + } else if (strcasecmp(argv[0], "clickfinger") == 0) { + new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; + } else { + return cmd_results_new(CMD_INVALID, "click_method", "Expected 'click_method identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; + } else { + return cmd_results_new(CMD_INVALID, "drag_lock", "Expected 'drag_lock '"); + } + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_dwt(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "dwt", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "dwt", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->dwt = LIBINPUT_CONFIG_DWT_ENABLED; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED; + } else { + return cmd_results_new(CMD_INVALID, "dwt", "Expected 'dwt '"); + } + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_events(int argc, char **argv) { + sway_log(L_DEBUG, "events for device: %s", current_input_config->identifier); + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "events", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "events", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; + } else if (strcasecmp(argv[0], "disabled_on_external_mouse") == 0) { + new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + } else { + return cmd_results_new(CMD_INVALID, "events", "Expected 'events '"); + } + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "middle_emulation", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "middle_emulation", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; + } else { + return cmd_results_new(CMD_INVALID, "middle_emulation", "Expected 'middle_emulation '"); + } + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "natural_scroll", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "natural_scoll", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->natural_scroll = 1; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->natural_scroll = 0; + } else { + return cmd_results_new(CMD_INVALID, "natural_scroll", "Expected 'natural_scroll '"); + } + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_pointer_accel(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "pointer_accel", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "pointer_accel", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + float pointer_accel = atof(argv[0]); + if (pointer_accel < -1 || pointer_accel > 1) { + return cmd_results_new(CMD_INVALID, "pointer_accel", "Input out of range [-1, 1]"); + } + new_config->pointer_accel = pointer_accel; + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_scroll_method(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "scroll_method", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "scroll_method", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "none") == 0) { + new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + } else if (strcasecmp(argv[0], "two_finger") == 0) { + new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + } else if (strcasecmp(argv[0], "edge") == 0) { + new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_EDGE; + } else if (strcasecmp(argv[0], "on_button_down") == 0) { + new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + } else { + return cmd_results_new(CMD_INVALID, "scroll_method", "Expected 'scroll_method '"); + } + + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *input_cmd_tap(int argc, char **argv) { + sway_log(L_DEBUG, "tap for device: %s", current_input_config->identifier); + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "tap", EXPECTED_AT_LEAST, 1))) { + return error; + } + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "tap", "No input device defined."); + } + struct input_config *new_config = new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->tap = LIBINPUT_CONFIG_TAP_ENABLED; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED; + } else { + return cmd_results_new(CMD_INVALID, "tap", "Expected 'tap '"); + } + + sway_log(L_DEBUG, "apply-tap for device: %s", current_input_config->identifier); + input_cmd_apply(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +static struct cmd_results *cmd_input(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) { + return error; + } + + if (config->reading && strcmp("{", argv[1]) == 0) { + current_input_config = new_input_config(argv[0]); + sway_log(L_DEBUG, "entering input block: %s", current_input_config->identifier); + return cmd_results_new(CMD_BLOCK_INPUT, NULL, NULL); + } + + if (argc > 2) { + int argc_new = argc-2; + char **argv_new = argv+2; + + struct cmd_results *res; + current_input_config = new_input_config(argv[0]); + if (strcasecmp("click_method", argv[1]) == 0) { + res = input_cmd_click_method(argc_new, argv_new); + } else if (strcasecmp("drag_lock", argv[1]) == 0) { + res = input_cmd_drag_lock(argc_new, argv_new); + } else if (strcasecmp("dwt", argv[1]) == 0) { + res = input_cmd_dwt(argc_new, argv_new); + } else if (strcasecmp("events", argv[1]) == 0) { + res = input_cmd_events(argc_new, argv_new); + } else if (strcasecmp("middle_emulation", argv[1]) == 0) { + res = input_cmd_middle_emulation(argc_new, argv_new); + } else if (strcasecmp("natural_scroll", argv[1]) == 0) { + res = input_cmd_natural_scroll(argc_new, argv_new); + } else if (strcasecmp("pointer_accel", argv[1]) == 0) { + res = input_cmd_pointer_accel(argc_new, argv_new); + } else if (strcasecmp("scroll_method", argv[1]) == 0) { + res = input_cmd_scroll_method(argc_new, argv_new); + } else if (strcasecmp("tap", argv[1]) == 0) { + res = input_cmd_tap(argc_new, argv_new); + } else { + res = cmd_results_new(CMD_INVALID, "input ", "Unknonwn command %s", argv[1]); + } + current_input_config = NULL; + return res; + } + + return cmd_results_new(CMD_BLOCK_INPUT, NULL, NULL); +} + static struct cmd_results *cmd_output(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1))) { @@ -1683,6 +1976,7 @@ static struct cmd_handler handlers[] = { { "for_window", cmd_for_window }, { "fullscreen", cmd_fullscreen }, { "gaps", cmd_gaps }, + { "input", cmd_input }, { "kill", cmd_kill }, { "layout", cmd_layout }, { "log_colors", cmd_log_colors }, @@ -2422,6 +2716,18 @@ static struct cmd_results *bar_colors_cmd_urgent_workspace(int argc, char **argv return cmd_results_new(CMD_SUCCESS, NULL, NULL); } +static struct cmd_handler input_handlers[] = { + { "click_method", input_cmd_click_method }, + { "drag_lock", input_cmd_drag_lock }, + { "dwt", input_cmd_dwt }, + { "events", input_cmd_events }, + { "middle_emulation", input_cmd_middle_emulation }, + { "natural_scroll", input_cmd_natural_scroll }, + { "pointer_accel", input_cmd_pointer_accel }, + { "scroll_method", input_cmd_scroll_method }, + { "tap", input_cmd_tap }, +}; + static struct cmd_handler bar_colors_handlers[] = { { "active_workspace", bar_colors_cmd_active_workspace }, { "background", bar_colors_cmd_background }, @@ -2442,6 +2748,7 @@ static int handler_compare(const void *_a, const void *_b) { static struct cmd_handler *find_handler(char *line, enum cmd_status block) { struct cmd_handler d = { .command=line }; struct cmd_handler *res = NULL; + sway_log(L_DEBUG, "find_handler(%s) %d", line, block == CMD_BLOCK_INPUT); if (block == CMD_BLOCK_BAR) { res = bsearch(&d, bar_handlers, sizeof(bar_handlers) / sizeof(struct cmd_handler), @@ -2450,6 +2757,11 @@ static struct cmd_handler *find_handler(char *line, enum cmd_status block) { res = bsearch(&d, bar_colors_handlers, sizeof(bar_colors_handlers) / sizeof(struct cmd_handler), sizeof(struct cmd_handler), handler_compare); + } else if (block == CMD_BLOCK_INPUT) { + sway_log(L_DEBUG, "lookng at input handlers"); + res = bsearch(&d, input_handlers, + sizeof(input_handlers) / sizeof(struct cmd_handler), + sizeof(struct cmd_handler), handler_compare); } else { res = bsearch(&d, handlers, sizeof(handlers) / sizeof(struct cmd_handler), diff --git a/sway/config.c b/sway/config.c index ae6a02b1b..debd480a3 100644 --- a/sway/config.c +++ b/sway/config.c @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include "wayland-desktop-shell-server-protocol.h" #include "readline.h" #include "stringop.h" @@ -16,6 +19,7 @@ #include "layout.h" #include "input_state.h" #include "criteria.h" +#include "input.h" struct sway_config *config = NULL; @@ -59,6 +63,11 @@ static void free_bar(struct bar_config *bar) { free(bar); } +void free_input_config(struct input_config *ic) { + free(ic->identifier); + free(ic); +} + void free_output_config(struct output_config *oc) { free(oc->name); free(oc); @@ -99,6 +108,11 @@ static void free_config(struct sway_config *config) { } list_free(config->criteria); + for (i = 0; i < config->input_configs->length; ++i) { + free_input_config(config->input_configs->items[i]); + } + list_free(config->input_configs); + for (i = 0; i < config->output_configs->length; ++i) { free_output_config(config->output_configs->items[i]); } @@ -119,6 +133,7 @@ static void config_defaults(struct sway_config *config) { config->bars = create_list(); config->workspace_outputs = create_list(); config->criteria = create_list(); + config->input_configs = create_list(); config->output_configs = create_list(); config->cmd_queue = create_list(); @@ -294,6 +309,14 @@ bool read_config(FILE *file, bool is_active) { } break; + case CMD_BLOCK_INPUT: + if (block == CMD_BLOCK_END) { + block = CMD_BLOCK_INPUT; + } else { + sway_log(L_ERROR, "Invalid block '%s'", line); + } + break; + case CMD_BLOCK_BAR: if (block == CMD_BLOCK_END) { block = CMD_BLOCK_BAR; @@ -318,6 +341,12 @@ bool read_config(FILE *file, bool is_active) { block = CMD_BLOCK_END; break; + case CMD_BLOCK_INPUT: + sway_log(L_DEBUG, "End of input block"); + current_input_config = NULL; + block = CMD_BLOCK_END; + break; + case CMD_BLOCK_BAR: sway_log(L_DEBUG, "End of bar block"); config->current_bar = NULL; @@ -353,6 +382,12 @@ bool read_config(FILE *file, bool is_active) { return success; } +int input_identifier_cmp(const void *item, const void *data) { + const struct input_config *ic = item; + const char *identifier = data; + return strcmp(ic->identifier, identifier); +} + int output_name_cmp(const void *item, const void *data) { const struct output_config *output = item; const char *name = data; @@ -360,6 +395,42 @@ int output_name_cmp(const void *item, const void *data) { return strcmp(output->name, name); } +void merge_input_config(struct input_config *dst, struct input_config *src) { + if (src->identifier) { + if (dst->identifier) { + free(dst->identifier); + } + dst->identifier = strdup(src->identifier); + } + if (src->click_method != INT_MIN) { + dst->click_method = src->click_method; + } + if (src->drag_lock != INT_MIN) { + dst->drag_lock = src->drag_lock; + } + if (src->dwt != INT_MIN) { + dst->dwt = src->dwt; + } + if (src->middle_emulation != INT_MIN) { + dst->middle_emulation = src->middle_emulation; + } + if (src->natural_scroll != INT_MIN) { + dst->natural_scroll = src->natural_scroll; + } + if (src->pointer_accel != FLT_MIN) { + dst->pointer_accel = src->pointer_accel; + } + if (src->scroll_method != INT_MIN) { + dst->scroll_method = src->scroll_method; + } + if (src->send_events != INT_MIN) { + dst->send_events = src->send_events; + } + if (src->tap != INT_MIN) { + dst->tap = src->tap; + } +} + void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->name) { if (dst->name) { @@ -508,6 +579,51 @@ void load_swaybars(swayc_t *output, int output_idx) { list_free(bars); } +void apply_input_config(struct input_config *ic, struct libinput_device *dev) { + if (ic) { + sway_log(L_DEBUG, + "apply_input_config(%s)", + ic->identifier); + } + + if (ic && ic->click_method != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) click_set_method(%d)", ic->identifier, ic->click_method); + libinput_device_config_click_set_method(dev, ic->click_method); + } + if (ic && ic->drag_lock != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) tap_set_drag_lock_enabled(%d)", ic->identifier, ic->click_method); + libinput_device_config_tap_set_drag_lock_enabled(dev, ic->drag_lock); + } + if (ic && ic->dwt != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) dwt_set_enabled(%d)", ic->identifier, ic->dwt); + libinput_device_config_dwt_set_enabled(dev, ic->dwt); + } + if (ic && ic->middle_emulation != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) middle_emulation_set_enabled(%d)", ic->identifier, ic->middle_emulation); + libinput_device_config_middle_emulation_set_enabled(dev, ic->middle_emulation); + } + if (ic && ic->natural_scroll != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) natural_scroll_set_enabled(%d)", ic->identifier, ic->natural_scroll); + libinput_device_config_scroll_set_natural_scroll_enabled(dev, ic->natural_scroll); + } + if (ic && ic->pointer_accel != FLT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) accel_set_speed(%f)", ic->identifier, ic->pointer_accel); + libinput_device_config_accel_set_speed(dev, ic->pointer_accel); + } + if (ic && ic->scroll_method != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) scroll_set_method(%d)", ic->identifier, ic->scroll_method); + libinput_device_config_scroll_set_method(dev, ic->scroll_method); + } + if (ic && ic->send_events != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) send_events_set_mode(%d)", ic->identifier, ic->send_events); + libinput_device_config_send_events_set_mode(dev, ic->send_events); + } + if (ic && ic->tap != INT_MIN) { + sway_log(L_DEBUG, "apply_input_config(%s) tap_set_enabled(%d)", ic->identifier, ic->tap); + libinput_device_config_tap_set_enabled(dev, ic->tap); + } +} + void apply_output_config(struct output_config *oc, swayc_t *output) { if (oc && oc->width > 0 && oc->height > 0) { output->width = oc->width; diff --git a/sway/handlers.c b/sway/handlers.c index 60bfac871..b1c0e26a2 100644 --- a/sway/handlers.c +++ b/sway/handlers.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ #include "extensions.h" #include "criteria.h" #include "ipc-server.h" +#include "list.h" +#include "input.h" // Event should be sent to client #define EVENT_PASSTHROUGH false @@ -30,6 +33,37 @@ /* Handles */ +static bool handle_input_created(struct libinput_device *device) { + const char *identifier = libinput_dev_unique_id(device); + sway_log(L_INFO, "Found input device (%s)", identifier); + + list_add(input_devices, device); + + struct input_config *ic = NULL; + int i; + for (i = 0; i < config->input_configs->length; ++i) { + struct input_config *cur = config->input_configs->items[i]; + if (strcasecmp(identifier, cur->identifier) == 0) { + ic = cur; + break; + } + } + + apply_input_config(ic, device); + return true; +} + +static void handle_input_destroyed(struct libinput_device *device) { + int i; + list_t *list = input_devices; + for (i = 0; i < list->length; ++i) { + if(((struct libinput_device *)list->items[i]) == device) { + list_del(list, i); + break; + } + } +} + static bool handle_output_created(wlc_handle output) { swayc_t *op = new_output(output); @@ -660,5 +694,9 @@ struct wlc_interface interface = { }, .compositor = { .ready = handle_wlc_ready + }, + .input = { + .created = handle_input_created, + .destroyed = handle_input_destroyed } }; diff --git a/sway/input.c b/sway/input.c new file mode 100644 index 000000000..fe0d1affa --- /dev/null +++ b/sway/input.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "input.h" +#include "list.h" +#include "log.h" + +struct input_config *new_input_config(const char* identifier) { + struct input_config *input = calloc(1, sizeof(struct input_config)); + sway_log(L_DEBUG, "new_input_config(%s)", identifier); + input->identifier = strdup(identifier); + + input->tap = INT_MIN; + input->drag_lock = INT_MIN; + input->dwt = INT_MIN; + input->send_events = INT_MIN; + input->click_method = INT_MIN; + input->middle_emulation = INT_MIN; + input->natural_scroll = INT_MIN; + input->pointer_accel = FLT_MIN; + input->scroll_method = INT_MIN; + + return input; +} + +char *libinput_dev_unique_id(struct libinput_device *device) { + int vendor = libinput_device_get_id_vendor(device); + int product = libinput_device_get_id_product(device); + char *name = strdup(libinput_device_get_name(device)); + + char *p = name; + for (; *p; ++p) { + if (*p == ' ') { + *p = '_'; + } + } + + sway_log(L_DEBUG, "rewritten name %s", name); + + int len = strlen(name) + sizeof(char) * 6; + char *identifier = malloc(len); + + const char *fmt = "%d:%d:%s"; + snprintf(identifier, len, fmt, vendor, product, name); + free(name); + return identifier; +} + +list_t *input_devices = NULL; +struct input_config *current_input_config = NULL; diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 8d92b919f..58a291cd4 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "ipc-server.h" #include "log.h" #include "config.h" @@ -20,6 +21,7 @@ #include "list.h" #include "stringop.h" #include "util.h" +#include "input.h" static int ipc_socket = -1; static struct wlc_event_source *ipc_event_source = NULL; @@ -325,6 +327,24 @@ void ipc_client_handle_command(struct ipc_client *client) { json_object_put(workspaces); // free break; } + case IPC_GET_INPUTS: + { + json_object *inputs = json_object_new_array(); + if (input_devices) { + for(int i=0; ilength; i++) { + struct libinput_device *device = input_devices->items[i]; + char* identifier = libinput_dev_unique_id(device); + json_object *device_object = json_object_new_object(); + json_object_object_add(device_object, "identifier", json_object_new_string(identifier)); + json_object_array_add(inputs, device_object); + free(identifier); + } + } + const char *json_string = json_object_to_json_string(inputs); + ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); + json_object_put(inputs); + break; + } case IPC_GET_OUTPUTS: { json_object *outputs = json_object_new_array(); diff --git a/sway/main.c b/sway/main.c index e85f72699..bec6a7257 100644 --- a/sway/main.c +++ b/sway/main.c @@ -17,6 +17,7 @@ #include "handlers.h" #include "ipc-client.h" #include "ipc-server.h" +#include "input.h" #include "sway.h" static bool terminate_request = false; @@ -173,6 +174,8 @@ int main(int argc, char **argv) { wlc_log_set_handler(wlc_log_handler); detect_proprietary(); + input_devices = create_list(); + /* Changing code earlier than this point requires detailed review */ /* (That code runs as root on systems without logind, and wlc_init drops to * another user.) */ @@ -208,6 +211,10 @@ int main(int argc, char **argv) { wlc_run(); } + if (input_devices) { + free(input_devices); + } + ipc_terminate(); return 0; diff --git a/sway/sway-input.5.txt b/sway/sway-input.5.txt new file mode 100644 index 000000000..ec5d1314d --- /dev/null +++ b/sway/sway-input.5.txt @@ -0,0 +1,46 @@ +///// +vim:set ts=4 sw=4 tw=82 noet: +///// +sway (5) +======== + +Name +---- +sway - input configuration file and commands + +Description +----------- + +Sway allows for configuration of libinput dveices. + + +Commands +-------- + +**input** click_method : + Changes the click method for the specified device. + +**input** drag_lock : + Enables or disables drag lock for specified input device. + +**input** dwt : + Enables or disables disable-while-typing for the specified input device. + +**input** events : + Enables or disables send_events for specified input device. + (Disabling send_events disables the input device) + +**input** middle_emulation : + Enables or disables middle click emulation. + +**input** natural_scroll : + Enables or disables natural scrolling for the specified input device. + +**input** pointer_accel <[-1,1]>: + Changes the pointer acceleration for the specified input device. + +**input** scroll_method : + Changes the scroll method for the specified input device. + +**input** tap : + Enables or disables tap for specified input device. diff --git a/swaymsg/main.c b/swaymsg/main.c index dac84a9b2..22572b6fc 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -87,6 +87,8 @@ int main(int argc, char **argv) { type = IPC_COMMAND; } else if (strcasecmp(cmdtype, "get_workspaces") == 0) { type = IPC_GET_WORKSPACES; + } else if (strcasecmp(cmdtype, "get_inputs") == 0) { + type = IPC_GET_INPUTS; } else if (strcasecmp(cmdtype, "get_outputs") == 0) { type = IPC_GET_OUTPUTS; } else if (strcasecmp(cmdtype, "get_tree") == 0) { diff --git a/swaymsg/swaymsg.1.txt b/swaymsg/swaymsg.1.txt index 984780fa0..c3af28d53 100644 --- a/swaymsg/swaymsg.1.txt +++ b/swaymsg/swaymsg.1.txt @@ -43,6 +43,9 @@ IPC Message Types *get_workspaces*:: Gets a JSON-encoded list of workspaces and their status. +*get_inputs*:: + Gets a JSON-encoded list of current inputs. + *get_outputs*:: Gets a JSON-encoded list of current outputs.