diff --git a/data/sm.puri.phoc.gschema.xml b/data/sm.puri.phoc.gschema.xml index 53919ec3..b437d68c 100644 --- a/data/sm.puri.phoc.gschema.xml +++ b/data/sm.puri.phoc.gschema.xml @@ -9,6 +9,17 @@ Wayland applications and only their main window. + + false + Draw a marker at each touch point + + Whether to draw a marker wherever a finger is touching the screen. + The same overlay as the touch-points debug flag, switchable while the + compositor runs so that a screen recording can show what was pressed + and put it away again afterwards. + + + false Enable automatic scaling of all oversized windows diff --git a/src/desktop.c b/src/desktop.c index 785fb058..d87ca8b8 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -448,6 +448,24 @@ handle_pointer_constraint (struct wl_listener *listener, void *data) } +static void +touch_points_changed_cb (PhocDesktop *self, const char *key, GSettings *settings) +{ + gboolean enable = g_settings_get_boolean (settings, key); + PhocOutput *output; + + g_debug ("touch-points: %d", enable); + phoc_server_set_debug_flag (phoc_server_get_default (), + PHOC_SERVER_DEBUG_FLAG_TOUCH_POINTS, + enable); + + /* The markers are drawn from the render pass, so nothing appears or clears + * until something asks for a frame. */ + wl_list_for_each (output, &self->outputs, link) + phoc_output_damage_whole (output); +} + + static void auto_maximize_changed_cb (PhocDesktop *self, const gchar *key, @@ -767,6 +785,9 @@ phoc_desktop_constructed (GObject *object) G_CALLBACK (auto_maximize_changed_cb), self); auto_maximize_changed_cb (self, "auto-maximize", priv->settings); g_settings_bind (priv->settings, "scale-to-fit", self, "scale-to-fit", G_SETTINGS_BIND_DEFAULT); + g_signal_connect_swapped (priv->settings, "changed::touch-points", + G_CALLBACK (touch_points_changed_cb), self); + touch_points_changed_cb (self, "touch-points", priv->settings); /* org.gnome.desktop.interface settings */ priv->interface_settings = g_settings_new ("org.gnome.desktop.interface"); diff --git a/src/render.c b/src/render.c index b181c48d..fb56b566 100644 --- a/src/render.c +++ b/src/render.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -41,11 +42,30 @@ #include #include #include +#include #include #include +#include +/* Set to 0 to build the upstream touch point markers instead of the FuriOS + * ones. Everything this fork changes about them is behind it. */ +#define IS_FURIOS 1 + +#if IS_FURIOS +#define TOUCH_POINT_SIZE 60 +#else #define TOUCH_POINT_SIZE 20 +#endif #define TOUCH_POINT_BORDER 0.1 +#if IS_FURIOS +/* How long the marker takes to shrink from full size to its resting size, and + * what fraction of full size it rests at. It does not shrink to nothing: a + * finger held down or dragged has to stay visible, which is the whole point of + * the overlay during a screen recording. */ +#define TOUCH_POINT_SHRINK_MS 333 +#define TOUCH_POINT_REST 0.34 +#define TOUCH_POINT_MAX_IDS 10 +#endif #define COLOR_BLACK ((struct wlr_render_color){0.0f, 0.0f, 0.0f, 1.0f}) #define COLOR_TRANSPARENT {0.0f, 0.0f, 0.0f, 0.0f} @@ -97,6 +117,9 @@ struct touch_point_data { int id; double x; double y; +#if IS_FURIOS + double radius; /* in logical pixels, animated down from TOUCH_POINT_SIZE */ +#endif }; @@ -189,6 +212,46 @@ render_texture (PhocOutput *output, pixman_region32_fini (&damage); } +#if IS_FURIOS +/* + * The size of a touch marker, shrinking from full to its resting size over + * TOUCH_POINT_SHRINK_MS from the moment the finger went down. + * + * The start times are kept here rather than on the touch point, because the + * points are rebuilt from the seat every frame and carry no history. An id that + * has not been seen for a frame is treated as a new touch, which is what makes + * a second tap animate again rather than staying at its resting size. + */ +static gint64 touch_point_started[TOUCH_POINT_MAX_IDS]; +static gint64 touch_point_last_seen[TOUCH_POINT_MAX_IDS]; + +static double +touch_point_radius (int id) +{ + gint64 now = g_get_monotonic_time (); + double age_ms, t; + + if (id < 0 || id >= TOUCH_POINT_MAX_IDS) + id = 0; + + /* A gap of more than a couple of frames means the finger was lifted and this + * is a new touch, not the same one continuing. */ + if (now - touch_point_last_seen[id] > 100 * G_TIME_SPAN_MILLISECOND) + touch_point_started[id] = now; + + touch_point_last_seen[id] = now; + + age_ms = (now - touch_point_started[id]) / (double) G_TIME_SPAN_MILLISECOND; + t = CLAMP (age_ms / TOUCH_POINT_SHRINK_MS, 0.0, 1.0); + /* Ease out, so it drops away quickly and settles gently */ + t = 1.0 - (1.0 - t) * (1.0 - t); + + return TOUCH_POINT_SIZE * (1.0 - t * (1.0 - TOUCH_POINT_REST)); +} +#endif + + + static void collect_touch_points (PhocOutput *output, struct wlr_surface *surface, struct wlr_box box, float scale) { @@ -211,6 +274,9 @@ collect_touch_points (PhocOutput *output, struct wlr_surface *surface, struct wl touch_point = g_new (struct touch_point_data, 1); touch_point->id = point->touch_id; +#if IS_FURIOS + touch_point->radius = touch_point_radius (point->touch_id); +#endif touch_point->x = box.x + point->sx * output->wlr_output->scale * scale; touch_point->y = box.y + point->sy * output->wlr_output->scale * scale; output->debug_touch_points = g_list_append (output->debug_touch_points, touch_point); @@ -317,6 +383,80 @@ render_drag_icons (PhocInput *input, PhocRenderContext *ctx) } +static struct wlr_box +phoc_box_from_touch_point (struct touch_point_data *touch_point, int width, int height) +{ + return (struct wlr_box) { + .x = touch_point->x - width / 2.0, + .y = touch_point->y - height / 2.0, + .width = width, + .height = height + }; +} + +#if IS_FURIOS +/* + * Draw a filled circle out of horizontal slices. + * + * The render pass can only add rectangles, and a couple of dozen slices is + * indistinguishable from a circle at this size while staying inside the + * existing pass -- no shader, no GL state to save and restore. + */ +static void +render_disc (PhocRenderContext *ctx, double cx, double cy, double radius, struct wlr_render_color color) +{ + int steps = CLAMP ((int) (radius / 1.5), 6, 28); + + for (int i = 0; i < steps; i++) { + /* Sample each slice at its centre, so the top and bottom do not flatten */ + double t0 = (i + 0.5) / steps; + double dy = (t0 * 2.0 - 1.0) * radius; + double half_w = sqrt (MAX (radius * radius - dy * dy, 0.0)); + double y0 = cy - radius + (i / (double) steps) * radius * 2.0; + double h = (radius * 2.0) / steps; + struct wlr_box box; + + if (half_w < 0.5) + continue; + + box = (struct wlr_box) { + .x = round (cx - half_w), + .y = round (y0), + .width = MAX (1, (int) round (half_w * 2.0)), + .height = MAX (1, (int) ceil (h)), + }; + phoc_output_transform_box (ctx->output, &box); + wlr_render_pass_add_rect (ctx->render_pass, &(struct wlr_render_rect_options){ + .box = box, + .color = color, + }); + } +} + + +static void +render_touch_point_cb (gpointer data, gpointer user_data) +{ + struct touch_point_data *touch_point = data; + PhocRenderContext *ctx = user_data; + struct wlr_output *wlr_output = ctx->output->wlr_output; + /* Blue, and the same blue for every finger: this marks where the screen was + * touched, it is not trying to tell one finger from another. */ + struct wlr_render_color color = {0.21f, 0.52f, 0.89f, 0.55f}; + struct wlr_render_color core = {0.85f, 0.92f, 1.0f, 0.75f}; + double radius = touch_point->radius * wlr_output->scale / 2.0; + + if (radius < 1.0) + return; + + render_disc (ctx, touch_point->x, touch_point->y, radius, color); + /* A small bright centre so the exact point stays readable once the ring has + * shrunk, and while it is over a busy wallpaper. */ + render_disc (ctx, touch_point->x, touch_point->y, radius * 0.3, core); +} + +#else + static void color_hsv_to_rgb (struct wlr_render_color *color) { @@ -344,17 +484,6 @@ color_hsv_to_rgb (struct wlr_render_color *color) } -static struct wlr_box -phoc_box_from_touch_point (struct touch_point_data *touch_point, int width, int height) -{ - return (struct wlr_box) { - .x = touch_point->x - width / 2.0, - .y = touch_point->y - height / 2.0, - .width = width, - .height = height - }; -} - static void render_touch_point_cb (gpointer data, gpointer user_data) { @@ -397,6 +526,9 @@ render_touch_point_cb (gpointer data, gpointer user_data) }); } +#endif + + static void render_touch_points (PhocRenderContext *ctx) { diff --git a/src/server.c b/src/server.c index dc44f824..50e1bc8f 100644 --- a/src/server.c +++ b/src/server.c @@ -583,6 +583,29 @@ phoc_server_get_config (PhocServer *self) return self->config; } +/** + * phoc_server_set_debug_flag: + * @self: The server + * @flag: The flag to change + * @enable: Whether to set or clear it + * + * Turn a debug flag on or off while the compositor is running. The flags are + * otherwise parsed once from PHOC_DEBUG at startup, which is no use to anything + * that wants to switch one on for a moment -- a screen recording showing where + * the screen was touched, say -- and off again afterwards. + */ +void +phoc_server_set_debug_flag (PhocServer *self, PhocServerDebugFlags flag, gboolean enable) +{ + g_assert (PHOC_IS_SERVER (self)); + + if (enable) + self->debug_flags |= flag; + else + self->debug_flags &= ~flag; +} + + /** * phoc_server_check_debug_flags: * @self: The server diff --git a/src/server.h b/src/server.h index e5045e48..9d8f412a 100644 --- a/src/server.h +++ b/src/server.h @@ -58,6 +58,9 @@ gboolean phoc_server_setup (PhocServer *self, GMainLoop *mainloop, PhocServerFlags flags, PhocServerDebugFlags debug_flags); +void phoc_server_set_debug_flag (PhocServer *self, + PhocServerDebugFlags flag, + gboolean enable); gboolean phoc_server_check_debug_flags (PhocServer *self, PhocServerDebugFlags check); const char *phoc_server_get_session_exec (PhocServer *self);