@@ -8,6 +8,7 @@
obj-$(CONFIG_INPUT) += input-core.o
input-core-y := input.o input-compat.o input-mt.o input-poller.o ff-core.o
input-core-y += touchscreen.o
+input-core-y += ts-virtobj.o
obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o
obj-$(CONFIG_INPUT_SPARSEKMAP) += sparse-keymap.o
new file mode 100644
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Helper functions for virtual objects on touchscreens
+ *
+ * Copyright (c) 2023 Javier Carrasco <javier.carrasco@wolfvision.net>
+ */
+
+#include <linux/property.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/module.h>
+#include <linux/input/ts-virtobj.h>
+
+static int ts_virtobj_count_buttons(struct device *dev)
+{
+ struct fwnode_handle *child_node;
+ struct fwnode_handle *child_button;
+ int count = 0;
+
+ child_node = device_get_named_child_node(dev, ts_virtobj_names[BUTTON]);
+ if (!child_node)
+ return 0;
+
+ fwnode_for_each_child_node(child_node, child_button)
+ count++;
+ fwnode_handle_put(child_node);
+
+ return count;
+}
+
+static int ts_virtobj_get_shape_properties(struct fwnode_handle *child_node,
+ struct ts_virtobj_shape *shape)
+{
+ int rc;
+
+ rc = fwnode_property_read_u32(child_node, "x-origin", &shape->x_origin);
+ if (rc < 0)
+ return rc;
+
+ rc = fwnode_property_read_u32(child_node, "y-origin", &shape->y_origin);
+ if (rc < 0)
+ return rc;
+
+ rc = fwnode_property_read_u32(child_node, "x-size", &shape->x_size);
+ if (rc < 0)
+ return rc;
+
+ rc = fwnode_property_read_u32(child_node, "y-size", &shape->y_size);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static int ts_virtobj_get_button_properties(struct device *dev,
+ struct fwnode_handle *child_node,
+ struct ts_virtobj_button *btn)
+{
+ struct fwnode_handle *child_btn;
+ int rc;
+ int j = 0;
+
+ fwnode_for_each_child_node(child_node, child_btn) {
+ rc = ts_virtobj_get_shape_properties(child_btn, &btn[j].shape);
+ if (rc < 0) {
+ fwnode_handle_put(child_btn);
+ return rc;
+ }
+
+ rc = fwnode_property_read_u32(child_btn, "linux,code",
+ &btn[j].key);
+ if (rc < 0) {
+ fwnode_handle_put(child_btn);
+ return rc;
+ }
+
+ dev_info(dev, "Added button at (%u, %u), size %ux%u, code=%u\n",
+ btn[j].shape.x_origin, btn[j].shape.y_origin,
+ btn[j].shape.x_size, btn[j].shape.y_size, btn[j].key);
+ j++;
+ }
+
+ return 0;
+}
+
+static void ts_virtobj_set_button_caps(struct ts_virtobj_map *map,
+ struct input_dev *dev)
+{
+ int i;
+
+ for (i = 0; i < map->button_count; i++)
+ input_set_capability(dev, EV_KEY,
+ map->buttons[i].key);
+}
+
+static int ts_virtobj_map_touchscreen(struct device *dev,
+ struct ts_virtobj_map *map)
+{
+ struct fwnode_handle *child;
+ int rc = 0;
+
+ child = device_get_named_child_node(dev, ts_virtobj_names[TOUCHSCREEN]);
+ if (!child)
+ return 0;
+
+ map->touchscreen = devm_kzalloc(dev, sizeof(*map->touchscreen),
+ GFP_KERNEL);
+ if (!map->touchscreen) {
+ fwnode_handle_put(child);
+ return -ENOMEM;
+ }
+ rc = ts_virtobj_get_shape_properties(child, map->touchscreen);
+ if (rc < 0) {
+ devm_kfree(dev, map->touchscreen);
+ fwnode_handle_put(child);
+ return rc;
+ }
+ map->virtual_touchscreen = 1;
+ dev_info(dev, "Added virtual touchscreen at (%u, %u), size %u x %u\n",
+ map->touchscreen->x_origin, map->touchscreen->y_origin,
+ map->touchscreen->x_size, map->touchscreen->y_size);
+
+ fwnode_handle_put(child);
+ return 0;
+}
+
+static int ts_virtobj_map_buttons(struct device *dev,
+ struct ts_virtobj_map *map,
+ struct input_dev *input)
+{
+ struct fwnode_handle *child;
+ u32 button_count;
+ int rc;
+
+ button_count = ts_virtobj_count_buttons(dev);
+ if (button_count) {
+ map->buttons = devm_kcalloc(dev, button_count,
+ sizeof(*map->buttons), GFP_KERNEL);
+ if (!map->buttons)
+ return -ENOMEM;
+
+ child = device_get_named_child_node(dev,
+ ts_virtobj_names[BUTTON]);
+ if (unlikely(!child)) {
+ devm_kfree(dev, map->buttons);
+ return 0;
+ }
+ rc = ts_virtobj_get_button_properties(dev, child, map->buttons);
+ if (rc < 0) {
+ devm_kfree(dev, map->buttons);
+ return rc;
+ }
+ map->button_count = button_count;
+ ts_virtobj_set_button_caps(map, input);
+ }
+
+ return 0;
+}
+
+static bool ts_virtobj_defined_objects(struct device *dev)
+{
+ struct fwnode_handle *child;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts_virtobj_names); i++) {
+ child = device_get_named_child_node(dev, ts_virtobj_names[i]);
+ if (child) {
+ fwnode_handle_put(child);
+ return true;
+ }
+ fwnode_handle_put(child);
+ }
+
+ return false;
+}
+
+struct ts_virtobj_map *ts_virtobj_map_objects(struct device *dev,
+ struct input_dev *input)
+{
+ struct ts_virtobj_map *map;
+ int rc;
+
+ if (!ts_virtobj_defined_objects(dev))
+ return NULL;
+
+ map = devm_kzalloc(dev, sizeof(*map), GFP_KERNEL);
+ if (!map)
+ return ERR_PTR(-ENOMEM);
+
+ rc = ts_virtobj_map_touchscreen(dev, map);
+ if (rc < 0) {
+ devm_kfree(dev, map);
+ return ERR_PTR(rc);
+ }
+ rc = ts_virtobj_map_buttons(dev, map, input);
+ if (rc < 0) {
+ devm_kfree(dev, map);
+ return ERR_PTR(rc);
+ }
+
+ return map;
+}
+EXPORT_SYMBOL(ts_virtobj_map_objects);
+
+void ts_virtobj_retrieve_abs(struct ts_virtobj_map *map, u16 *x, u16 *y)
+{
+ *x = map->touchscreen->x_size - 1;
+ *y = map->touchscreen->y_size - 1;
+}
+EXPORT_SYMBOL(ts_virtobj_retrieve_abs);
+
+static bool ts_virtobj_event_in_shape_range(struct ts_virtobj_shape *shape,
+ u32 x, u32 y)
+{
+ if (!shape)
+ return false;
+
+ if (x >= shape->x_origin && x < (shape->x_origin + shape->x_size) &&
+ y >= shape->y_origin && y < (shape->y_origin + shape->y_size))
+ return true;
+
+ return false;
+}
+
+bool ts_virtobj_touchscreen_event(struct ts_virtobj_shape *touchscreen,
+ u32 *x, u32 *y)
+{
+ if (ts_virtobj_event_in_shape_range(touchscreen, *x, *y)) {
+ *x -= touchscreen->x_origin;
+ *y -= touchscreen->y_origin;
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(ts_virtobj_touchscreen_event);
+
+bool ts_virtobj_mapped_touchscreen(struct ts_virtobj_map *map)
+{
+ if (!map || !map->virtual_touchscreen)
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL(ts_virtobj_mapped_touchscreen);
+
+static bool ts_virtobj_mapped_button(struct ts_virtobj_map *map)
+{
+ if (!map || !map->button_count)
+ return false;
+
+ return true;
+}
+
+bool ts_virtobj_mt_on_touchscreen(struct ts_virtobj_map *map, u32 *x, u32 *y)
+{
+ if (!ts_virtobj_mapped_touchscreen(map))
+ return true;
+
+ if (!ts_virtobj_touchscreen_event(map->touchscreen, x, y))
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL(ts_virtobj_mt_on_touchscreen);
+
+bool ts_virtobj_button_event(struct ts_virtobj_map *map,
+ struct input_dev *input, u32 x, u32 y)
+{
+ int i;
+
+ if (!ts_virtobj_mapped_button(map))
+ return false;
+
+ for (i = 0; i < map->button_count; i++) {
+ if (ts_virtobj_event_in_shape_range(&map->buttons[i].shape, x, y)) {
+ input_report_key(input, map->buttons[i].key, 1);
+ map->buttons[i].pressed = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(ts_virtobj_button_event);
+
+void ts_virtobj_button_release_pressed(struct ts_virtobj_map *map,
+ struct input_dev *input)
+{
+ int i;
+
+ if (!map || !map->button_count)
+ return;
+
+ for (i = 0; i < map->button_count; i++) {
+ if (map->buttons[i].pressed) {
+ input_report_key(input, map->buttons[i].key, 0);
+ map->buttons[i].pressed = false;
+ }
+ }
+}
+EXPORT_SYMBOL(ts_virtobj_button_release_pressed);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Helper functions for virtual objects on touchscreens");
new file mode 100644
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Javier Carrasco <javier.carrasco@wolfvision.net>
+ */
+
+#ifndef _TS_VIRTOBJ
+#define _TS_VIRTOBJ
+
+struct input_dev;
+struct device;
+
+enum ts_virtobj_valid_objects {
+ TOUCHSCREEN,
+ BUTTON,
+};
+
+static const char * const ts_virtobj_names[] = {
+ [TOUCHSCREEN] = "virtual-touchscreen",
+ [BUTTON] = "virtual-buttons",
+};
+
+struct ts_virtobj_shape {
+ u32 x_origin;
+ u32 y_origin;
+ u32 x_size;
+ u32 y_size;
+};
+
+struct ts_virtobj_button {
+ struct ts_virtobj_shape shape;
+ u32 key;
+ bool pressed;
+};
+
+struct ts_virtobj_map {
+ struct ts_virtobj_shape *touchscreen;
+ bool virtual_touchscreen;
+ struct ts_virtobj_button *buttons;
+ u32 button_count;
+};
+
+struct ts_virtobj_map *ts_virtobj_map_objects(struct device *dev,
+ struct input_dev *input);
+
+void ts_virtobj_retrieve_abs(struct ts_virtobj_map *map, u16 *x, u16 *y);
+
+bool ts_virtobj_touchscreen_event(struct ts_virtobj_shape *touchscreen,
+ u32 *x, u32 *y);
+
+bool ts_virtobj_mapped_touchscreen(struct ts_virtobj_map *map);
+
+bool ts_virtobj_mt_on_touchscreen(struct ts_virtobj_map *map, u32 *x, u32 *y);
+
+bool ts_virtobj_button_event(struct ts_virtobj_map *map,
+ struct input_dev *input, u32 x, u32 y);
+
+void ts_virtobj_button_release_pressed(struct ts_virtobj_map *map,
+ struct input_dev *input);
+#endif