aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/config.c303
-rw-r--r--examples/config.h39
-rw-r--r--examples/ini.c195
-rw-r--r--examples/ini.h93
-rw-r--r--examples/meson.build2
-rw-r--r--examples/output-layout.c5
-rw-r--r--examples/pointer.c361
-rw-r--r--examples/rotation.c1
-rw-r--r--examples/shared.c148
-rw-r--r--examples/shared.h22
-rw-r--r--examples/touch.c2
-rw-r--r--examples/wlr-example.ini.example57
12 files changed, 1015 insertions, 213 deletions
diff --git a/examples/config.c b/examples/config.c
new file mode 100644
index 00000000..954edb06
--- /dev/null
+++ b/examples/config.c
@@ -0,0 +1,303 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <stdlib.h>
+#include <limits.h>
+#include <getopt.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <wlr/util/log.h>
+#include <wlr/types/wlr_box.h>
+#include "shared.h"
+#include "config.h"
+#include "ini.h"
+
+static void usage(const char *name, int ret) {
+ fprintf(stderr,
+ "usage: %s [-C <FILE>]\n"
+ "\n"
+ " -C <FILE> Path to the configuration file\n"
+ " (default: wlr-example.ini).\n"
+ " See `examples/wlr-example.ini.example` for config\n"
+ " file documentation.\n", name);
+
+ exit(ret);
+}
+
+static struct wlr_box *parse_geometry(const char *str) {
+ // format: {width}x{height}+{x}+{y}
+ if (strlen(str) > 255) {
+ wlr_log(L_ERROR, "cannot parse geometry string, too long");
+ return NULL;
+ }
+
+ char *buf = strdup(str);
+ struct wlr_box *box = calloc(1, sizeof(struct wlr_box));
+
+ bool has_width = false;
+ bool has_height = false;
+ bool has_x = false;
+ bool has_y = false;
+
+ char *pch = strtok(buf, "x+");
+ while (pch != NULL) {
+ errno = 0;
+ char *endptr;
+ long val = strtol(pch, &endptr, 0);
+
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
+ (errno != 0 && val == 0)) {
+ goto invalid_input;
+ }
+
+ if (endptr == pch) {
+ goto invalid_input;
+ }
+
+ if (!has_width) {
+ box->width = val;
+ has_width = true;
+ } else if (!has_height) {
+ box->height = val;
+ has_height = true;
+ } else if (!has_x) {
+ box->x = val;
+ has_x = true;
+ } else if (!has_y) {
+ box->y = val;
+ has_y = true;
+ } else {
+ break;
+ }
+ pch = strtok(NULL, "x+");
+ }
+
+ if (!has_width || !has_height) {
+ goto invalid_input;
+ }
+
+ free(buf);
+ return box;
+
+invalid_input:
+ wlr_log(L_ERROR, "could not parse geometry string: %s", str);
+ free(buf);
+ free(box);
+ return NULL;
+}
+
+static const char *output_prefix = "output:";
+static const char *device_prefix = "device:";
+
+static int config_ini_handler(void *user, const char *section, const char *name,
+ const char *value) {
+ struct example_config *config = user;
+ if (strncmp(output_prefix, section, strlen(output_prefix)) == 0) {
+ const char *output_name = section + strlen(output_prefix);
+ struct output_config *oc;
+ bool found = false;
+
+ wl_list_for_each(oc, &config->outputs, link) {
+ if (strcmp(oc->name, output_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ oc = calloc(1, sizeof(struct output_config));
+ oc->name = strdup(output_name);
+ oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ wl_list_insert(&config->outputs, &oc->link);
+ }
+
+ if (strcmp(name, "x") == 0) {
+ oc->x = strtol(value, NULL, 10);
+ } else if (strcmp(name, "y") == 0) {
+ oc->y = strtol(value, NULL, 10);
+ } else if (strcmp(name, "rotate") == 0) {
+ if (strcmp(value, "90") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_90;
+ } else if (strcmp(value, "180") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_180;
+ } else if (strcmp(value, "270") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_270;
+ } else if (strcmp(value, "flipped") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED;
+ } else if (strcmp(value, "flipped-90") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90;
+ } else if (strcmp(value, "flipped-180") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180;
+ } else if (strcmp(value, "flipped-270") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270;
+ } else {
+ wlr_log(L_ERROR, "got unknown transform value: %s", value);
+ }
+ }
+ } else if (strcmp(section, "cursor") == 0) {
+ if (strcmp(name, "map-to-output") == 0) {
+ free(config->cursor.mapped_output);
+ config->cursor.mapped_output = strdup(value);
+ } else if (strcmp(name, "geometry") == 0) {
+ free(config->cursor.mapped_box);
+ config->cursor.mapped_box = parse_geometry(value);
+ } else {
+ wlr_log(L_ERROR, "got unknown cursor config: %s", name);
+ }
+ } else if (strncmp(device_prefix, section, strlen(device_prefix)) == 0) {
+ const char *device_name = section + strlen(device_prefix);
+ struct device_config *dc;
+ bool found = false;
+
+ wl_list_for_each(dc, &config->devices, link) {
+ if (strcmp(dc->name, device_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dc = calloc(1, sizeof(struct device_config));
+ dc->name = strdup(device_name);
+ wl_list_insert(&config->devices, &dc->link);
+ }
+
+ if (strcmp(name, "map-to-output") == 0) {
+ free(dc->mapped_output);
+ dc->mapped_output = strdup(value);
+ } else if (strcmp(name, "geometry") == 0) {
+ free(dc->mapped_box);
+ dc->mapped_box = parse_geometry(value);
+ } else {
+ wlr_log(L_ERROR, "got unknown device config: %s", name);
+ }
+ } else {
+ wlr_log(L_ERROR, "got unknown config section: %s", section);
+ }
+
+ return 1;
+}
+
+struct example_config *parse_args(int argc, char *argv[]) {
+ struct example_config *config = calloc(1, sizeof(struct example_config));
+ wl_list_init(&config->outputs);
+ wl_list_init(&config->devices);
+
+ int c;
+ while ((c = getopt(argc, argv, "C:h")) != -1) {
+ switch (c) {
+ case 'C':
+ config->config_path = strdup(optarg);
+ break;
+ case 'h':
+ case '?':
+ usage(argv[0], c != 'h');
+ }
+ }
+
+ if (!config->config_path) {
+ // get the config path from the current directory
+ char cwd[MAXPATHLEN];
+ if (getcwd(cwd, sizeof(cwd)) != NULL) {
+ char buf[MAXPATHLEN];
+ snprintf(buf, MAXPATHLEN, "%s/%s", cwd, "wlr-example.ini");
+ config->config_path = strdup(buf);
+ } else {
+ wlr_log(L_ERROR, "could not get cwd");
+ exit(1);
+ }
+ }
+
+ int result = ini_parse(config->config_path, config_ini_handler, config);
+
+ if (result == -1) {
+ wlr_log(L_DEBUG, "No config file found. Using empty config.");
+ } else if (result == -2) {
+ wlr_log(L_ERROR, "Could not allocate memory to parse config file");
+ exit(1);
+ } else if (result != 0) {
+ wlr_log(L_ERROR, "Could not parse config file");
+ exit(1);
+ }
+
+ return config;
+}
+
+void example_config_destroy(struct example_config *config) {
+ struct output_config *oc, *otmp = NULL;
+ wl_list_for_each_safe(oc, otmp, &config->outputs, link) {
+ free(oc->name);
+ free(oc);
+ }
+
+ struct device_config *dc, *dtmp = NULL;
+ wl_list_for_each_safe(dc, dtmp, &config->devices, link) {
+ free(dc->name);
+ if (dc->mapped_output) {
+ free(dc->mapped_output);
+ }
+ if (dc->mapped_box) {
+ free(dc->mapped_box);
+ }
+ free(dc);
+ }
+
+ if (config->config_path) {
+ free(config->config_path);
+ }
+ if (config->cursor.mapped_output) {
+ free(config->cursor.mapped_output);
+ }
+ if (config->cursor.mapped_box) {
+ free(config->cursor.mapped_box);
+ }
+ free(config);
+}
+
+struct wlr_output_layout *configure_layout(struct example_config *config,
+ struct wl_list *outputs) {
+ struct wlr_output_layout *layout = wlr_output_layout_init();
+ int max_x = INT_MIN;
+ int max_x_y = INT_MIN; // y value for the max_x output
+
+ // first add all the configured outputs
+ struct output_state *output;
+ wl_list_for_each(output, outputs, link) {
+ struct output_config *conf;
+ wl_list_for_each(conf, &config->outputs, link) {
+ if (strcmp(conf->name, output->output->name) == 0) {
+ wlr_output_layout_add(layout, output->output,
+ conf->x, conf->y);
+ wlr_output_transform(output->output, conf->transform);
+ int width, height;
+ wlr_output_effective_resolution(output->output, &width,
+ &height);
+ if (conf->x + width > max_x) {
+ max_x = conf->x + width;
+ max_x_y = conf->y;
+ }
+ break;
+ }
+ }
+ }
+
+ if (max_x == INT_MIN) {
+ // couldn't find a configured output
+ max_x = 0;
+ max_x_y = 0;
+ }
+
+ // now add all the other configured outputs in a sensible position
+ wl_list_for_each(output, outputs, link) {
+ if (wlr_output_layout_get(layout, output->output)) {
+ continue;
+ }
+ wlr_output_layout_add(layout, output->output, max_x, max_x_y);
+ int width, height;
+ wlr_output_effective_resolution(output->output, &width, &height);
+ max_x += width;
+ }
+
+ return layout;
+}
diff --git a/examples/config.h b/examples/config.h
new file mode 100644
index 00000000..2a69c4f4
--- /dev/null
+++ b/examples/config.h
@@ -0,0 +1,39 @@
+#ifndef _EXAMPLE_CONFIG_H
+#define _EXAMPLE_CONFIG_H
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+#include <wlr/types/wlr_output_layout.h>
+
+struct output_config {
+ char *name;
+ enum wl_output_transform transform;
+ int x, y;
+ struct wl_list link;
+};
+
+struct device_config {
+ char *name;
+ char *mapped_output;
+ struct wlr_box *mapped_box;
+ struct wl_list link;
+};
+
+struct example_config {
+ struct {
+ char *mapped_output;
+ struct wlr_box *mapped_box;
+ } cursor;
+
+ struct wl_list outputs;
+ struct wl_list devices;
+ char *config_path;
+};
+
+struct example_config *parse_args(int argc, char *argv[]);
+
+void example_config_destroy(struct example_config *config);
+
+struct wlr_output_layout *configure_layout(struct example_config *config,
+ struct wl_list *outputs);
+#endif
diff --git a/examples/ini.c b/examples/ini.c
new file mode 100644
index 00000000..6be9c44a
--- /dev/null
+++ b/examples/ini.c
@@ -0,0 +1,195 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "ini.h"
+
+#if !INI_USE_STACK
+#include <stdlib.h>
+#endif
+
+#define MAX_SECTION 50
+#define MAX_NAME 50
+
+/* Strip whitespace chars off end of given string, in place. Return s. */
+static char* rstrip(char* s)
+{
+ char* p = s + strlen(s);
+ while (p > s && isspace((unsigned char)(*--p)))
+ *p = '\0';
+ return s;
+}
+
+/* Return pointer to first non-whitespace char in given string. */
+static char* lskip(const char* s)
+{
+ while (*s && isspace((unsigned char)(*s)))
+ s++;
+ return (char*)s;
+}
+
+/* Return pointer to first char (of chars) or inline comment in given string,
+ or pointer to null at end of string if neither found. Inline comment must
+ be prefixed by a whitespace character to register as a comment. */
+static char* find_chars_or_comment(const char* s, const char* chars)
+{
+#if INI_ALLOW_INLINE_COMMENTS
+ int was_space = 0;
+ while (*s && (!chars || !strchr(chars, *s)) &&
+ !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
+ was_space = isspace((unsigned char)(*s));
+ s++;
+ }
+#else
+ while (*s && (!chars || !strchr(chars, *s))) {
+ s++;
+ }
+#endif
+ return (char*)s;
+}
+
+/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
+static char* strncpy0(char* dest, const char* src, size_t size)
+{
+ strncpy(dest, src, size);
+ dest[size - 1] = '\0';
+ return dest;
+}
+
+/* See documentation in header file. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user)
+{
+ /* Uses a fair bit of stack (use heap instead if you need to) */
+#if INI_USE_STACK
+ char line[INI_MAX_LINE];
+#else
+ char* line;
+#endif
+ char section[MAX_SECTION] = "";
+ char prev_name[MAX_NAME] = "";
+
+ char* start;
+ char* end;
+ char* name;
+ char* value;
+ int lineno = 0;
+ int error = 0;
+
+#if !INI_USE_STACK
+ line = (char*)malloc(INI_MAX_LINE);
+ if (!line) {
+ return -2;
+ }
+#endif
+
+ /* Scan through stream line by line */
+ while (reader(line, INI_MAX_LINE, stream) != NULL) {
+ lineno++;
+
+ start = line;
+#if INI_ALLOW_BOM
+ if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
+ (unsigned char)start[1] == 0xBB &&
+ (unsigned char)start[2] == 0xBF) {
+ start += 3;
+ }
+#endif
+ start = lskip(rstrip(start));
+
+ if (*start == ';' || *start == '#') {
+ /* Per Python configparser, allow both ; and # comments at the
+ start of a line */
+ }
+#if INI_ALLOW_MULTILINE
+ else if (*prev_name && *start && start > line) {
+ /* Non-blank line with leading whitespace, treat as continuation
+ of previous name's value (as per Python configparser). */
+ if (!handler(user, section, prev_name, start) && !error)
+ error = lineno;
+ }
+#endif
+ else if (*start == '[') {
+ /* A "[section]" line */
+ end = find_chars_or_comment(start + 1, "]");
+ if (*end == ']') {
+ *end = '\0';
+ strncpy0(section, start + 1, sizeof(section));
+ *prev_name = '\0';
+ }
+ else if (!error) {
+ /* No ']' found on section line */
+ error = lineno;
+ }
+ }
+ else if (*start) {
+ /* Not a comment, must be a name[=:]value pair */
+ end = find_chars_or_comment(start, "=:");
+ if (*end == '=' || *end == ':') {
+ *end = '\0';
+ name = rstrip(start);
+ value = lskip(end + 1);
+#if INI_ALLOW_INLINE_COMMENTS
+ end = find_chars_or_comment(value, NULL);
+ if (*end)
+ *end = '\0';
+#endif
+ rstrip(value);
+
+ /* Valid name[=:]value pair found, call handler */
+ strncpy0(prev_name, name, sizeof(prev_name));
+ if (!handler(user, section, name, value) && !error)
+ error = lineno;
+ memset(value, 0, strlen(value));
+ }
+ else if (!error) {
+ /* No '=' or ':' found on name[=:]value line */
+ error = lineno;
+ }
+ }
+
+#if INI_STOP_ON_FIRST_ERROR
+ if (error)
+ break;
+#endif
+ }
+
+#if !INI_USE_STACK
+ free(line);
+#endif
+
+ return error;
+}
+
+/* See documentation in header file. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user)
+{
+ return ini_parse_stream((ini_reader)fgets, file, handler, user);
+}
+
+/* See documentation in header file. */
+int ini_parse(const char* filename, ini_handler handler, void* user)
+{
+ FILE* file;
+ int error;
+
+ file = fopen(filename, "r");
+ if (!file)
+ return -1;
+ error = ini_parse_file(file, handler, user);
+ fclose(file);
+ return error;
+}
diff --git a/examples/ini.h b/examples/ini.h
new file mode 100644
index 00000000..2804255b
--- /dev/null
+++ b/examples/ini.h
@@ -0,0 +1,93 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#ifndef __INI_H__
+#define __INI_H__
+
+/* Make this header file easier to include in C++ code */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* Typedef for prototype of handler function. */
+typedef int (*ini_handler)(void* user, const char* section,
+ const char* name, const char* value);
+
+/* Typedef for prototype of fgets-style reader function. */
+typedef char* (*ini_reader)(char* str, int num, void* stream);
+
+/* Parse given INI-style file. May have [section]s, name=value pairs
+ (whitespace stripped), and comments starting with ';' (semicolon). Section
+ is "" if name=value pair parsed before any section heading. name:value
+ pairs are also supported as a concession to Python's configparser.
+
+ For each name=value pair parsed, call handler function with given user
+ pointer as well as section, name, and value (data only valid for duration
+ of handler call). Handler should return nonzero on success, zero on error.
+
+ Returns 0 on success, line number of first error on parse error (doesn't
+ stop on first error), -1 on file open error, or -2 on memory allocation
+ error (only when INI_USE_STACK is zero).
+*/
+int ini_parse(const char* filename, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
+ close the file when it's finished -- the caller must do that. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes an ini_reader function pointer instead of
+ filename. Used for implementing custom or string-based I/O. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user);
+
+/* Nonzero to allow multi-line value parsing, in the style of Python's
+ configparser. If allowed, ini_parse() will call the handler with the same
+ name for each subsequent line parsed. */
+#ifndef INI_ALLOW_MULTILINE
+#define INI_ALLOW_MULTILINE 1
+#endif
+
+/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
+ the file. See http://code.google.com/p/inih/issues/detail?id=21 */
+#ifndef INI_ALLOW_BOM
+#define INI_ALLOW_BOM 1
+#endif
+
+/* Nonzero to allow inline comments (with valid inline comment characters
+ specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
+ Python 3.2+ configparser behaviour. */
+#ifndef INI_ALLOW_INLINE_COMMENTS
+#define INI_ALLOW_INLINE_COMMENTS 1
+#endif
+#ifndef INI_INLINE_COMMENT_PREFIXES
+#define INI_INLINE_COMMENT_PREFIXES ";"
+#endif
+
+/* Nonzero to use stack, zero to use heap (malloc/free). */
+#ifndef INI_USE_STACK
+#define INI_USE_STACK 1
+#endif
+
+/* Stop parsing on first error (default is to keep parsing). */
+#ifndef INI_STOP_ON_FIRST_ERROR
+#define INI_STOP_ON_FIRST_ERROR 0
+#endif
+
+/* Maximum line length for any line in INI file. */
+#ifndef INI_MAX_LINE
+#define INI_MAX_LINE 2000
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INI_H__ */
diff --git a/examples/meson.build b/examples/meson.build
index d4d96984..8fda974f 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -1,5 +1,5 @@
lib_shared = static_library('shared',
- ['shared.c', 'cat.c'],
+ ['shared.c', 'cat.c', 'ini.c', 'config.c'],
dependencies: wlroots)
executable('simple', 'simple.c', dependencies: wlroots, link_with: lib_shared)
diff --git a/examples/output-layout.c b/examples/output-layout.c
index 0dcbc1f8..90ad558f 100644
--- a/examples/output-layout.c
+++ b/examples/output-layout.c
@@ -22,6 +22,7 @@
#include <wlr/types/wlr_keyboard.h>
#include <math.h>
#include "shared.h"
+#include "config.h"
#include "cat.h"
struct sample_state {
@@ -49,8 +50,8 @@ static void handle_output_frame(struct output_state *output, struct timespec *ts
float matrix[16];
// transform global coordinates to local coordinates
- int local_x = sample->x_offs;
- int local_y = sample->y_offs;
+ double local_x = sample->x_offs;
+ double local_y = sample->y_offs;
wlr_output_layout_output_coords(sample->layout, output->output, &local_x,
&local_y);
diff --git a/examples/pointer.c b/examples/pointer.c
index 12253b62..d9a06339 100644
--- a/examples/pointer.c
+++ b/examples/pointer.c
@@ -6,6 +6,7 @@
#include <string.h>
#include <unistd.h>
#include <math.h>
+#include <assert.h>
#include <wayland-server.h>
#include <wayland-server-protocol.h>
#include <xkbcommon/xkbcommon.h>
@@ -16,19 +17,71 @@
#include <wlr/backend.h>
#include <wlr/backend/session.h>
#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_output_layout.h>
#include <wlr/xcursor.h>
+#include <wlr/types/wlr_cursor.h>
#include <wlr/util/log.h>
#include "shared.h"
+#include "config.h"
#include "cat.h"
+struct sample_input_device {
+ struct wlr_input_device *device;
+ struct wl_list link;
+};
+
struct sample_state {
- struct wlr_xcursor *cursor;
+ struct compositor_state *compositor;
+ struct example_config *config;
+ struct wlr_xcursor *xcursor;
+ struct wlr_cursor *cursor;
double cur_x, cur_y;
float default_color[4];
float clear_color[4];
+ struct wlr_output_layout *layout;
+ struct wl_list devices;
+
+ struct wl_listener cursor_motion;
+ struct wl_listener cursor_motion_absolute;
+ struct wl_listener cursor_button;
+ struct wl_listener cursor_axis;
+
+ struct wl_listener touch_motion;
+ struct wl_listener touch_up;
+ struct wl_listener touch_down;
+ struct wl_listener touch_cancel;
+ list_t *touch_points;
+
+ struct wl_listener tablet_tool_axis;
+ struct wl_listener tablet_tool_proxmity;
+ struct wl_listener tablet_tool_tip;
+ struct wl_listener tablet_tool_button;
+};
+
+struct touch_point {
+ int32_t slot;
+ double x, y;
};
-static void handle_output_frame(struct output_state *output, struct timespec *ts) {
+static void warp_to_touch(struct sample_state *sample,
+ struct wlr_input_device *dev) {
+ if (sample->touch_points->length == 0) {
+ return;
+ }
+
+ double x = 0, y = 0;
+ for (size_t i = 0; i < sample->touch_points->length; ++i) {
+ struct touch_point *point = sample->touch_points->items[i];
+ x += point->x;
+ y += point->y;
+ }
+ x /= sample->touch_points->length;
+ y /= sample->touch_points->length;
+ wlr_cursor_warp_absolute(sample->cursor, dev, x, y);
+}
+
+static void handle_output_frame(struct output_state *output,
+ struct timespec *ts) {
struct compositor_state *state = output->compositor;
struct sample_state *sample = state->data;
struct wlr_output *wlr_output = output->output;
@@ -36,65 +89,178 @@ static void handle_output_frame(struct output_state *output, struct timespec *ts
wlr_output_make_current(wlr_output);
glClearColor(sample->clear_color[0], sample->clear_color[1],
- sample->clear_color[2], sample->clear_color[3]);
+ sample->clear_color[2], sample->clear_color[3]);
glClear(GL_COLOR_BUFFER_BIT);
wlr_output_swap_buffers(wlr_output);
}
-static void handle_pointer_motion(struct pointer_state *pstate,
- double d_x, double d_y) {
- struct sample_state *state = pstate->compositor->data;
- state->cur_x += d_x;
- state->cur_y += d_y;
+static void configure_devices(struct sample_state *sample) {
+ struct sample_input_device *dev;
+ struct device_config *dc;
- struct wlr_xcursor_image *image = state->cursor->images[0];
+ // reset device mappings
+ wl_list_for_each(dev, &sample->devices, link) {
+ wlr_cursor_map_input_to_output(sample->cursor, dev->device, NULL);
+ wl_list_for_each(dc, &sample->config->devices, link) {
+ if (strcmp(dev->device->name, dc->name) == 0) {
+ wlr_cursor_map_input_to_region(sample->cursor, dev->device,
+ dc->mapped_box);
+ }
+ }
+ }
- struct output_state *output;
- wl_list_for_each(output, &pstate->compositor->outputs, link) {
- wlr_output_move_cursor(output->output,
- state->cur_x - image->hotspot_x,
- state->cur_y - image->hotspot_y);
+ struct output_state *ostate;
+ wl_list_for_each(ostate, &sample->compositor->outputs, link) {
+ wl_list_for_each(dc, &sample->config->devices, link) {
+ // configure device to output mappings
+ if (dc->mapped_output &&
+ strcmp(dc->mapped_output, ostate->output->name) == 0) {
+ wl_list_for_each(dev, &sample->devices, link) {
+ if (strcmp(dev->device->name, dc->name) == 0) {
+ wlr_cursor_map_input_to_output(sample->cursor,
+ dev->device, ostate->output);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void handle_output_add(struct output_state *ostate) {
+ struct sample_state *sample = ostate->compositor->data;
+ struct wlr_output *wlr_output = ostate->output;
+ struct wlr_xcursor_image *image = sample->xcursor->images[0];
+
+ // reset layout
+ wlr_output_layout_destroy(sample->layout);
+ sample->layout =
+ configure_layout(sample->config, &ostate->compositor->outputs);
+ wlr_cursor_attach_output_layout(sample->cursor, sample->layout);
+
+ // cursor configuration
+ char *mapped_output = sample->config->cursor.mapped_output;
+ if (mapped_output && strcmp(mapped_output, wlr_output->name) == 0) {
+ wlr_cursor_map_to_output(sample->cursor, wlr_output);
+ }
+
+ configure_devices(sample);
+
+ // TODO move to wlr_cursor
+ if (!wlr_output_set_cursor(wlr_output, image->buffer,
+ image->width, image->width, image->height)) {
+ wlr_log(L_DEBUG, "Failed to set hardware cursor");
+ return;
+ }
+ if (!wlr_output_move_cursor(wlr_output, 0, 0)) {
+ wlr_log(L_DEBUG, "Failed to move hardware cursor");
+ }
+}
+
+static void handle_output_remove(struct output_state *ostate) {
+ struct sample_state *sample = ostate->compositor->data;
+ wlr_output_layout_destroy(sample->layout);
+ sample->layout =
+ configure_layout(sample->config, &ostate->compositor->outputs);
+ wlr_cursor_attach_output_layout(sample->cursor, sample->layout);
+
+ configure_devices(sample);
+
+ char *mapped_output = sample->config->cursor.mapped_output;
+ if (mapped_output && strcmp(mapped_output, ostate->output->name) == 0) {
+ wlr_cursor_map_to_output(sample->cursor, NULL);
}
}
-static void handle_pointer_motion_absolute(struct pointer_state *pstate,
- double x, double y) {
- struct sample_state *state = pstate->compositor->data;
- state->cur_x = x;
- state->cur_y = y;
+static void handle_output_resolution(struct compositor_state *state,
+ struct output_state *ostate) {
+ struct sample_state *sample = ostate->compositor->data;
+ wlr_output_layout_destroy(sample->layout);
+ sample->layout =
+ configure_layout(sample->config, &ostate->compositor->outputs);
+ wlr_cursor_attach_output_layout(sample->cursor, sample->layout);
+}
+
+static void handle_input_add(struct compositor_state *state,
+ struct wlr_input_device *device) {
+ struct sample_state *sample = state->data;
+
+ if (device->type == WLR_INPUT_DEVICE_POINTER ||
+ device->type == WLR_INPUT_DEVICE_TOUCH ||
+ device->type == WLR_INPUT_DEVICE_TABLET_TOOL) {
+ struct sample_input_device *s_device;
+ s_device = calloc(1, sizeof(struct sample_input_device));
+ s_device->device = device;
- struct wlr_xcursor_image *image = state->cursor->images[0];
+ wl_list_insert(&sample->devices, &s_device->link);
+ wlr_cursor_attach_input_device(sample->cursor, device);
+ configure_devices(sample);
+ }
+}
+
+static void handle_input_remove(struct compositor_state *state,
+ struct wlr_input_device *device) {
+ struct sample_state *sample = state->data;
+ struct sample_input_device *s_device, *tmp = NULL;
+ wl_list_for_each_safe(s_device, tmp, &sample->devices, link) {
+ if (s_device->device == device) {
+ wl_list_remove(&s_device->link);
+ free(s_device);
+ }
+ }
+}
+
+static void handle_cursor_motion(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, cursor_motion);
+ struct wlr_event_pointer_motion *event = data;
+ wlr_cursor_move(sample->cursor, event->device, event->delta_x,
+ event->delta_y);
+}
+
+static void handle_cursor_motion_absolute(struct wl_listener *listener,
+ void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, cursor_motion_absolute);
+ struct wlr_event_pointer_motion_absolute *event = data;
+
+ sample->cur_x = event->x_mm;
+ sample->cur_y = event->y_mm;
+
+ struct wlr_xcursor_image *image = sample->xcursor->images[0];
struct output_state *output;
- wl_list_for_each(output, &pstate->compositor->outputs, link) {
+ wl_list_for_each(output, &sample->compositor->outputs, link) {
wlr_output_move_cursor(output->output,
- state->cur_x - image->hotspot_x,
- state->cur_y - image->hotspot_y);
+ sample->cur_x - image->hotspot_x,
+ sample->cur_y - image->hotspot_y);
}
}
-static void handle_pointer_button(struct pointer_state *pstate,
- uint32_t button, enum wlr_button_state state) {
- struct sample_state *sample = pstate->compositor->data;
+static void handle_cursor_button(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, cursor_button);
+ struct wlr_event_pointer_button *event = data;
+
float (*color)[4];
- if (state == WLR_BUTTON_RELEASED) {
+ if (event->state == WLR_BUTTON_RELEASED) {
color = &sample->default_color;
+ memcpy(&sample->clear_color, color, sizeof(*color));
} else {
float red[4] = { 0.25f, 0.25f, 0.25f, 1 };
- red[button % 3] = 1;
+ red[event->button % 3] = 1;
color = &red;
+ memcpy(&sample->clear_color, color, sizeof(*color));
}
- memcpy(&sample->clear_color, color, sizeof(*color));
}
-static void handle_pointer_axis(struct pointer_state *pstate,
- enum wlr_axis_source source,
- enum wlr_axis_orientation orientation,
- double delta) {
- struct sample_state *sample = pstate->compositor->data;
+static void handle_cursor_axis(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, cursor_axis);
+ struct wlr_event_pointer_axis *event = data;
+
for (size_t i = 0; i < 3; ++i) {
- sample->default_color[i] += delta > 0 ? -0.05f : 0.05f;
+ sample->default_color[i] += event->delta > 0 ? -0.05f : 0.05f;
if (sample->default_color[i] > 1.0f) {
sample->default_color[i] = 1.0f;
}
@@ -102,52 +268,143 @@ static void handle_pointer_axis(struct pointer_state *pstate,
sample->default_color[i] = 0.0f;
}
}
+
memcpy(&sample->clear_color, &sample->default_color,
sizeof(sample->clear_color));
}
-static void handle_output_add(struct output_state *ostate) {
- struct sample_state *state = ostate->compositor->data;
- struct wlr_output *wlr_output = ostate->output;
- struct wlr_xcursor_image *image = state->cursor->images[0];
- if (!wlr_output_set_cursor(wlr_output, image->buffer,
- image->width, image->width, image->height)) {
- wlr_log(L_DEBUG, "Failed to set hardware cursor");
- return;
+static void handle_touch_up(struct wl_listener *listener, void *data) {
+ struct sample_state *sample = wl_container_of(listener, sample, touch_up);
+ struct wlr_event_touch_up *event = data;
+ for (size_t i = 0; i < sample->touch_points->length; ++i) {
+ struct touch_point *point = sample->touch_points->items[i];
+ if (point->slot == event->slot) {
+ list_del(sample->touch_points, i);
+ break;
+ }
}
- if (!wlr_output_move_cursor(wlr_output, 0, 0)) {
- wlr_log(L_DEBUG, "Failed to move hardware cursor");
+
+ warp_to_touch(sample, event->device);
+}
+
+static void handle_touch_down(struct wl_listener *listener, void *data) {
+ struct sample_state *sample = wl_container_of(listener, sample, touch_down);
+ struct wlr_event_touch_down *event = data;
+ struct touch_point *point = calloc(1, sizeof(struct touch_point));
+ point->slot = event->slot;
+ point->x = event->x_mm / event->width_mm;
+ point->y = event->y_mm / event->height_mm;
+ if (list_add(sample->touch_points, point) == -1) {
+ free(point);
+ }
+
+ warp_to_touch(sample, event->device);
+}
+
+static void handle_touch_motion(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, touch_motion);
+ struct wlr_event_touch_motion *event = data;
+ for (size_t i = 0; i < sample->touch_points->length; ++i) {
+ struct touch_point *point = sample->touch_points->items[i];
+ if (point->slot == event->slot) {
+ point->x = event->x_mm / event->width_mm;
+ point->y = event->y_mm / event->height_mm;
+ break;
+ }
+ }
+
+ warp_to_touch(sample, event->device);
+}
+
+static void handle_touch_cancel(struct wl_listener *listener, void *data) {
+ wlr_log(L_DEBUG, "TODO: touch cancel");
+}
+
+static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, tablet_tool_axis);
+ struct wlr_event_tablet_tool_axis *event = data;
+ if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) &&
+ (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ wlr_cursor_warp_absolute(sample->cursor, event->device,
+ event->x_mm / event->width_mm, event->y_mm / event->height_mm);
}
}
int main(int argc, char *argv[]) {
struct sample_state state = {
.default_color = { 0.25f, 0.25f, 0.25f, 1 },
- .clear_color = { 0.25f, 0.25f, 0.25f, 1 }
+ .clear_color = { 0.25f, 0.25f, 0.25f, 1 },
+ .touch_points = list_create(),
};
+
+ state.config = parse_args(argc, argv);
+ state.cursor = wlr_cursor_create();
+ wlr_cursor_map_to_region(state.cursor, state.config->cursor.mapped_box);
+ wl_list_init(&state.devices);
+
+ // pointer events
+ wl_signal_add(&state.cursor->events.motion, &state.cursor_motion);
+ state.cursor_motion.notify = handle_cursor_motion;
+
+ wl_signal_add(&state.cursor->events.motion_absolute,
+ &state.cursor_motion_absolute);
+ state.cursor_motion_absolute.notify = handle_cursor_motion_absolute;
+
+ wl_signal_add(&state.cursor->events.button, &state.cursor_button);
+ state.cursor_button.notify = handle_cursor_button;
+
+ wl_signal_add(&state.cursor->events.axis, &state.cursor_axis);
+ state.cursor_axis.notify = handle_cursor_axis;
+
+ // touch events
+ wl_signal_add(&state.cursor->events.touch_up, &state.touch_up);
+ state.touch_up.notify = handle_touch_up;
+
+ wl_signal_add(&state.cursor->events.touch_down, &state.touch_down);
+ state.touch_down.notify = handle_touch_down;
+
+ wl_signal_add(&state.cursor->events.touch_motion, &state.touch_motion);
+ state.touch_motion.notify = handle_touch_motion;
+
+ wl_signal_add(&state.cursor->events.touch_cancel, &state.touch_cancel);
+ state.touch_cancel.notify = handle_touch_cancel;
+
+ // tool events
+ wl_signal_add(&state.cursor->events.tablet_tool_axis,
+ &state.tablet_tool_axis);
+ state.tablet_tool_axis.notify = handle_tablet_tool_axis;
+
struct compositor_state compositor = { 0 };
compositor.data = &state;
compositor.output_add_cb = handle_output_add;
+ compositor.output_remove_cb = handle_output_remove;
+ compositor.output_resolution_cb = handle_output_resolution;
compositor.output_frame_cb = handle_output_frame;
- compositor.pointer_motion_cb = handle_pointer_motion;
- compositor.pointer_motion_absolute_cb = handle_pointer_motion_absolute;
- compositor.pointer_button_cb = handle_pointer_button;
- compositor.pointer_axis_cb = handle_pointer_axis;
+ compositor.input_add_cb = handle_input_add;
+ compositor.input_remove_cb = handle_input_remove;
+
+ state.compositor = &compositor;
struct wlr_xcursor_theme *theme = wlr_xcursor_theme_load("default", 16);
if (!theme) {
wlr_log(L_ERROR, "Failed to load cursor theme");
return 1;
}
- state.cursor = wlr_xcursor_theme_get_cursor(theme, "left_ptr");
- if (!state.cursor) {
+ state.xcursor = wlr_xcursor_theme_get_cursor(theme, "left_ptr");
+ if (!state.xcursor) {
wlr_log(L_ERROR, "Failed to load left_ptr cursor");
return 1;
}
+ wlr_cursor_set_xcursor(state.cursor, state.xcursor);
+
compositor_init(&compositor);
wl_display_run(compositor.display);
compositor_fini(&compositor);
wlr_xcursor_theme_destroy(theme);
+ example_config_destroy(state.config);
+ wlr_cursor_destroy(state.cursor);
}
diff --git a/examples/rotation.c b/examples/rotation.c
index 1dcbc80f..2596e492 100644
--- a/examples/rotation.c
+++ b/examples/rotation.c
@@ -19,6 +19,7 @@
#include <wlr/util/log.h>
#include <math.h>
#include "shared.h"
+#include "config.h"
#include "cat.h"
struct sample_state {
diff --git a/examples/shared.c b/examples/shared.c
index f37140cf..0346c96d 100644
--- a/examples/shared.c
+++ b/examples/shared.c
@@ -18,145 +18,6 @@
#include <wlr/util/log.h>
#include "shared.h"
-static void usage(const char *name, int ret) {
- fprintf(stderr,
- "usage: %s [-d <name> [-r <rotation> | -f]]*\n"
- "\n"
- " -o <output> The name of the DRM display. e.g. DVI-I-1.\n"
- " -r <rotation> The rotation counter clockwise. Valid values are 90, 180, 270.\n"
- " -x <position> The X-axis coordinate position of this output in the layout.\n"
- " -y <position> The Y-axis coordinate position of this output in the layout.\n"
- " -f Flip the output along the vertical axis.\n", name);
-
- exit(ret);
-}
-
-struct example_config *parse_args(int argc, char *argv[]) {
- struct example_config *config = calloc(1, sizeof(struct example_config));
- wl_list_init(&config->outputs);
- struct output_config *oc = NULL;
-
- int c;
- while ((c = getopt(argc, argv, "o:r:x:y:fh")) != -1) {
- switch (c) {
- case 'o':
- oc = calloc(1, sizeof(*oc));
- oc->name = optarg;
- oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
- wl_list_insert(&config->outputs, &oc->link);
- break;
- case 'r':
- if (!oc) {
- fprintf(stderr, "You must specify an output first\n");
- usage(argv[0], 1);
- }
-
- if (oc->transform != WL_OUTPUT_TRANSFORM_NORMAL
- && oc->transform != WL_OUTPUT_TRANSFORM_FLIPPED) {
- fprintf(stderr, "Rotation for %s already specified\n", oc->name);
- usage(argv[0], 1);
- }
-
- if (strcmp(optarg, "90") == 0) {
- oc->transform += WL_OUTPUT_TRANSFORM_90;
- } else if (strcmp(optarg, "180") == 0) {
- oc->transform += WL_OUTPUT_TRANSFORM_180;
- } else if (strcmp(optarg, "270") == 0) {
- oc->transform += WL_OUTPUT_TRANSFORM_270;
- } else {
- fprintf(stderr, "Invalid rotation '%s'\n", optarg);
- usage(argv[0], 1);
- }
- break;
- case 'x':
- if (!oc) {
- fprintf(stderr, "You must specify an output first\n");
- usage(argv[0], 1);
- }
- oc->x = strtol(optarg, NULL, 0);
- break;
- case 'y':
- if (!oc) {
- fprintf(stderr, "You must specify an output first\n");
- usage(argv[0], 1);
- }
- oc->y = strtol(optarg, NULL, 0);
- break;
- case 'f':
- if (!oc) {
- fprintf(stderr, "You must specify an output first\n");
- usage(argv[0], 1);
- }
-
- if (oc->transform >= WL_OUTPUT_TRANSFORM_FLIPPED) {
- fprintf(stderr, "Flip for %s already specified\n", oc->name);
- usage(argv[0], 1);
- }
-
- oc->transform += WL_OUTPUT_TRANSFORM_FLIPPED;
- break;
- case 'h':
- case '?':
- usage(argv[0], c != 'h');
- }
- }
-
- return config;
-}
-
-void example_config_destroy(struct example_config *config) {
- struct output_config *oc, *tmp = NULL;
- wl_list_for_each_safe(oc, tmp, &config->outputs, link) {
- free(oc);
- }
- free(config);
-}
-
-struct wlr_output_layout *configure_layout(struct example_config *config, struct wl_list *outputs) {
- struct wlr_output_layout *layout = wlr_output_layout_init();
- int max_x = INT_MIN;
- int max_x_y = INT_MIN; // y value for the max_x output
-
- // first add all the configured outputs
- struct output_state *output;
- wl_list_for_each(output, outputs, link) {
- struct output_config *conf;
- wl_list_for_each(conf, &config->outputs, link) {
- if (strcmp(conf->name, output->output->name) == 0) {
- wlr_output_layout_add(layout, output->output,
- conf->x, conf->y);
- wlr_output_transform(output->output, conf->transform);
- int width, height;
- wlr_output_effective_resolution(output->output, &width, &height);
- if (conf->x + width > max_x) {
- max_x = conf->x + width;
- max_x_y = conf->y;
- }
- break;
- }
- }
- }
-
- if (max_x == INT_MIN) {
- // couldn't find a configured output
- max_x = 0;
- max_x_y = 0;
- }
-
- // now add all the other configured outputs in a sensible position
- wl_list_for_each(output, outputs, link) {
- if (wlr_output_layout_get(layout, output->output)) {
- continue;
- }
- wlr_output_layout_add(layout, output->output, max_x, max_x_y);
- int width, height;
- wlr_output_effective_resolution(output->output, &width, &height);
- max_x += width;
- }
-
- return layout;
-}
-
static void keyboard_led_update(struct keyboard_state *kbstate) {
uint32_t leds = 0;
@@ -441,6 +302,10 @@ static void input_add_notify(struct wl_listener *listener, void *data) {
default:
break;
}
+
+ if (state->input_add_cb) {
+ state->input_add_cb(state, device);
+ }
}
static void keyboard_remove(struct wlr_input_device *device, struct compositor_state *state) {
@@ -536,6 +401,11 @@ static void tablet_pad_remove(struct wlr_input_device *device, struct compositor
static void input_remove_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct compositor_state *state = wl_container_of(listener, state, input_remove);
+
+ if (state->input_remove_cb) {
+ state->input_remove_cb(state, device);
+ }
+
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
keyboard_remove(device, state);
diff --git a/examples/shared.h b/examples/shared.h
index d8007753..7cf4db63 100644
--- a/examples/shared.h
+++ b/examples/shared.h
@@ -12,24 +12,6 @@
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h>
-struct output_config {
- char *name;
- enum wl_output_transform transform;
- int x, y;
- struct wl_list link;
-};
-
-struct example_config {
- struct wl_list outputs;
-};
-
-struct example_config *parse_args(int argc, char *argv[]);
-
-void example_config_destroy(struct example_config *config);
-
-struct wlr_output_layout *configure_layout(struct example_config *config,
- struct wl_list *outputs);
-
struct output_state {
struct compositor_state *compositor;
struct wlr_output *output;
@@ -93,6 +75,10 @@ struct tablet_pad_state {
};
struct compositor_state {
+ void (*input_add_cb)(struct compositor_state *compositor,
+ struct wlr_input_device *device);
+ void (*input_remove_cb)(struct compositor_state *compositor,
+ struct wlr_input_device *device);
void (*output_add_cb)(struct output_state *s);
void (*keyboard_add_cb)(struct keyboard_state *s);
void (*output_frame_cb)(struct output_state *s, struct timespec *ts);
diff --git a/examples/touch.c b/examples/touch.c
index 7e8d9827..40fc4ca2 100644
--- a/examples/touch.c
+++ b/examples/touch.c
@@ -61,7 +61,7 @@ static void handle_output_frame(struct output_state *output, struct timespec *ts
static void handle_touch_down(struct touch_state *tstate, int32_t slot,
double x, double y, double width, double height) {
struct sample_state *sample = tstate->compositor->data;
- struct touch_point *point = calloc(1, sizeof(struct touch_state));
+ struct touch_point *point = calloc(1, sizeof(struct touch_point));
point->slot = slot;
point->x = x / width;
point->y = y / height;
diff --git a/examples/wlr-example.ini.example b/examples/wlr-example.ini.example
new file mode 100644
index 00000000..1698e0c6
--- /dev/null
+++ b/examples/wlr-example.ini.example
@@ -0,0 +1,57 @@
+# Configuration
+# -------------
+# Some examples will read a configuration file. Not all examples will use all of
+# the configuration options. The configuration file will be loaded from
+# `wlr-example.ini` from the current directory or the path can be specified by the
+# `-C` option given on the command line.
+#
+# Output configuration
+# ~~~~~~~~~~~~~~~~~~~~
+# Each output is specified in a section named [output:{NAME}] where NAME is the
+# drm name for this output.
+#
+# Value "x" specifies the x-coordinate in the output layout.
+#
+# Value "y" specifies the y-coordinate in the output layout.
+#
+# Value "rotate" specifies output rotation and can be 90, 180, 270, flipped,
+# flipped-90, flipped-180, or flipped-270
+[output:HDMI-A-1]
+x=3000
+y=0
+rotate=90
+
+[output:DP-1]
+x=0
+y=0
+rotate=270
+
+[output:DVI-D-1]
+x=1080
+y=232
+
+# Cursor Configuration
+# ~~~~~~~~~~~~~~~~~~~~
+# Value "map-to-output" specifies the output to which the cursor is
+# constrained.
+#
+# Value "geometry" specifies the geometry (widthxheight+x+y) to which the cursor
+# is constrained.
+[cursor]
+map-to-output=HDMI-A-1
+geometry=500x700+50+50
+
+# Device Configuration
+# ~~~~~~~~~~~~~~~~~~~~
+# Each device is specified in a section named [device:{NAME}] where NAME is the
+# name given to this device. See a log file for device names.
+#
+# Value "map-to-output" specifies the output to which the device is constrained.
+#
+# Value "geometry" specifies the geometry (widthxheight+x+y) to which the device
+# is constrained.
+[device:Razer Razer DeathAdder 2013]
+map-to-output=DP-1
+geometry=500x700+50+50
+
+# vim:filetype=dosini