[Concept,11/17] mouse: Provide a way to read clicks

Message ID 20250915104705.937780-10-sjg@u-boot.org
State New
Headers
Series mouse: Provide some support for using a mouse |

Commit Message

Simon Glass Sept. 15, 2025, 10:46 a.m. UTC
  From: Simon Glass <sjg@chromium.org>

A mouse click is defined as pressing the mouse and then releasing it
over a given spot. Add a function the tracks the mouse state and
returns the most recent mouse click, if any.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 drivers/input/mouse-uclass.c |  43 +++++++++++++
 include/mouse.h              |  25 ++++++++
 test/dm/mouse.c              | 121 +++++++++++++++++++++++++++++++++++
 3 files changed, 189 insertions(+)
  

Patch

diff --git a/drivers/input/mouse-uclass.c b/drivers/input/mouse-uclass.c
index f42ef346c5c..256642ef55e 100644
--- a/drivers/input/mouse-uclass.c
+++ b/drivers/input/mouse-uclass.c
@@ -23,7 +23,50 @@  int mouse_get_event(struct udevice *dev, struct mouse_event *evt)
 	return 0;
 }
 
+int mouse_get_click(struct udevice *dev, int *xp, int *yp)
+{
+	struct mouse_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct mouse_event event;
+	int ret;
+
+	/* Get one mouse event */
+	ret = mouse_get_event(dev, &event);
+	if (ret)
+		return -EAGAIN; /* No event available */
+
+	/* Only process button events for left button */
+	if (event.type == MOUSE_EV_BUTTON &&
+	    event.button.button == BUTTON_LEFT) {
+		enum mouse_press_state_t new_state = event.button.press_state;
+		bool pending = false;
+
+		/* Detect press->release transition (click) */
+		if (uc_priv->left_button_state == BUTTON_PRESSED &&
+		    new_state == BUTTON_RELEASED) {
+			pending = true;
+			uc_priv->click_x = event.button.x;
+			uc_priv->click_y = event.button.y;
+		}
+
+		/* Update button state */
+		uc_priv->left_button_state = new_state;
+
+		/* If we just detected a click, return it */
+		if (pending) {
+			if (xp)
+				*xp = uc_priv->click_x;
+			if (yp)
+				*yp = uc_priv->click_y;
+
+			return 0;
+		}
+	}
+
+	return -EAGAIN;
+}
+
 UCLASS_DRIVER(mouse) = {
 	.id		= UCLASS_MOUSE,
 	.name		= "mouse",
+	.per_device_auto = sizeof(struct mouse_uc_priv),
 };
diff --git a/include/mouse.h b/include/mouse.h
index c96c63918ea..0d20f8ffdbc 100644
--- a/include/mouse.h
+++ b/include/mouse.h
@@ -8,6 +8,8 @@ 
 #ifndef _MOUSE_H
 #define _MOUSE_H
 
+#include <stdbool.h>
+
 struct udevice;
 
 enum mouse_ev_t {
@@ -29,6 +31,19 @@  enum mouse_press_state_t {
 	BUTTON_PRESSED,
 };
 
+/**
+ * struct mouse_uc_priv - private data for mouse uclass
+ *
+ * @left_button_state: Current state of left button (BUTTON_PRESSED/BUTTON_RELEASED)
+ * @click_x: X coordinate where the click occurred
+ * @click_y: Y coordinate where the click occurred
+ */
+struct mouse_uc_priv {
+	enum mouse_press_state_t left_button_state;
+	int click_x;
+	int click_y;
+};
+
 /**
  * struct mouse_event - information about a mouse event
  *
@@ -77,4 +92,14 @@  struct mouse_ops {
 
 int mouse_get_event(struct udevice *dev, struct mouse_event *event);
 
+/**
+ * mouse_get_click() - Check if a left mouse button click has occurred
+ *
+ * @dev: Mouse device
+ * @xp: Returns X coordinate of click (can be NULL)
+ * @yp: Returns Y coordinate of click (can be NULL)
+ * Returns: 0 if a click has occurred, -EAGAIN if no click pending
+ */
+int mouse_get_click(struct udevice *dev, int *xp, int *py);
+
 #endif
diff --git a/test/dm/mouse.c b/test/dm/mouse.c
index f9c2e88c43a..1b4c2b5f60f 100644
--- a/test/dm/mouse.c
+++ b/test/dm/mouse.c
@@ -94,3 +94,124 @@  static int dm_test_mouse_button(struct unit_test_state *uts)
 	return 0;
 }
 DM_TEST(dm_test_mouse_button, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+static int dm_test_mouse_click(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	struct mouse_event inject;
+	int x, y;
+
+	ut_assertok(uclass_first_device_err(UCLASS_MOUSE, &dev));
+
+	/* put mouse in test mode */
+	sandbox_mouse_set_test_mode(dev, true);
+
+	/* test that no click is detected initially */
+	ut_asserteq(-EAGAIN, mouse_get_click(dev, &x, &y));
+
+	/* inject a left button press */
+	inject.type = MOUSE_EV_BUTTON;
+	inject.button.button = BUTTON_LEFT;
+	inject.button.press_state = BUTTON_PRESSED;
+	inject.button.clicks = 1;
+	inject.button.x = 300;
+	inject.button.y = 400;
+
+	sandbox_mouse_inject(dev, &inject);
+
+	/*
+	 * calling mouse_get_click() should not detect a click yet (press
+	 * only)
+	 */
+	ut_asserteq(-EAGAIN, mouse_get_click(dev, &x, &y));
+
+	/* inject a left button release */
+	inject.type = MOUSE_EV_BUTTON;
+	inject.button.button = BUTTON_LEFT;
+	inject.button.press_state = BUTTON_RELEASED;
+	inject.button.clicks = 1;
+	inject.button.x = 300;
+	inject.button.y = 400;
+
+	sandbox_mouse_inject(dev, &inject);
+
+	/* now mouse_get_click() should detect the click */
+	ut_assertok(mouse_get_click(dev, &x, &y));
+	ut_asserteq(300, x);
+	ut_asserteq(400, y);
+
+	/* verify no more clicks are pending */
+	ut_asserteq(-EAGAIN, mouse_get_click(dev, &x, &y));
+
+	return 0;
+}
+DM_TEST(dm_test_mouse_click, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+static int dm_test_mouse_click_no_coordinates(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	struct mouse_event inject;
+
+	ut_assertok(uclass_first_device_err(UCLASS_MOUSE, &dev));
+
+	/* put mouse in test mode */
+	sandbox_mouse_set_test_mode(dev, true);
+
+	/* inject press and release to create a click */
+	inject.type = MOUSE_EV_BUTTON;
+	inject.button.button = BUTTON_LEFT;
+	inject.button.press_state = BUTTON_PRESSED;
+	inject.button.clicks = 1;
+	inject.button.x = 500;
+	inject.button.y = 600;
+	sandbox_mouse_inject(dev, &inject);
+
+	/* process the press event */
+	ut_asserteq(-EAGAIN, mouse_get_click(dev, NULL, NULL));
+
+	inject.button.press_state = BUTTON_RELEASED;
+	sandbox_mouse_inject(dev, &inject);
+
+	/*
+	 * now test that click is detected without coordinate return
+	 */
+	ut_assertok(mouse_get_click(dev, NULL, NULL));
+
+	return 0;
+}
+DM_TEST(dm_test_mouse_click_no_coordinates, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+static int dm_test_mouse_right_button(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	struct mouse_event inject;
+	int x, y;
+
+	ut_assertok(uclass_first_device_err(UCLASS_MOUSE, &dev));
+
+	/* put mouse in test mode */
+	sandbox_mouse_set_test_mode(dev, true);
+
+	/*
+	 * right button events should not be detected as clicks by
+	 * mouse_get_click()
+	 */
+	inject.type = MOUSE_EV_BUTTON;
+	inject.button.button = BUTTON_RIGHT;
+	inject.button.press_state = BUTTON_PRESSED;
+	inject.button.clicks = 1;
+	inject.button.x = 100;
+	inject.button.y = 200;
+	sandbox_mouse_inject(dev, &inject);
+
+	ut_asserteq(-EAGAIN, mouse_get_click(dev, &x, &y));
+
+	inject.button.press_state = BUTTON_RELEASED;
+	sandbox_mouse_inject(dev, &inject);
+
+	/* still no click detected since it was right button */
+	ut_asserteq(-EAGAIN, mouse_get_click(dev, &x, &y));
+
+	return 0;
+}
+DM_TEST(dm_test_mouse_right_button, UTF_SCAN_PDATA | UTF_SCAN_FDT);