From 4db24ddc5eeb560b84521d85fd5401bb77829bdb Mon Sep 17 00:00:00 2001 From: Andrey Skvortsov Date: Sun, 4 May 2025 00:33:51 +0300 Subject: [PATCH] Automatically rotate image based on accelerometer values Device orientation is provided by net.hadess.SensorProxy iio-proxy-service. If there is no accelerometer on target platform, then window orientation from X11/wayland is used as before. --- flatpak/me.gapixels.Megapixels.yml | 3 + meson.build | 3 + src/io_pipeline.c | 4 + src/main.c | 16 +++ src/process_pipeline.c | 13 ++- src/rotation.c | 182 +++++++++++++++++++++++++++++ src/rotation.h | 9 ++ src/state.h | 6 + 8 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 src/rotation.c create mode 100644 src/rotation.h diff --git a/flatpak/me.gapixels.Megapixels.yml b/flatpak/me.gapixels.Megapixels.yml index b26d78a..dbcc300 100644 --- a/flatpak/me.gapixels.Megapixels.yml +++ b/flatpak/me.gapixels.Megapixels.yml @@ -27,6 +27,9 @@ finish-args: # camera shutter feedback - --talk-name=org.sigxcpu.Feedback + # accelerometer sensor for orientation + - --system-talk-name=net.hadess.SensorProxy + modules: - name: megapixels buildsystem: meson diff --git a/meson.build b/meson.build index 1114a6d..7b2d43f 100644 --- a/meson.build +++ b/meson.build @@ -54,6 +54,7 @@ executable('megapixels', 'src/matrix.c', 'src/pipeline.c', 'src/process_pipeline.c', + 'src/rotation.c', 'src/zbar_pipeline.c', 'src/dcp.c', resources, @@ -126,6 +127,8 @@ if clang_format.found() 'src/pipeline.h', 'src/process_pipeline.c', 'src/process_pipeline.h', + 'src/rotation.c', + 'src/rotation.h', 'src/zbar_pipeline.c', 'src/zbar_pipeline.h', 'tools/camera_test.c', diff --git a/src/io_pipeline.c b/src/io_pipeline.c index ac33b28..5aeebf2 100644 --- a/src/io_pipeline.c +++ b/src/io_pipeline.c @@ -96,6 +96,8 @@ update_process_pipeline() .preview_width = state_io.preview_width, .preview_height = state_io.preview_height, .device_rotation = state_io.device_rotation, + .device_accel_rotation = state_io.device_accel_rotation, + .device_accel_rotation_good = state_io.device_accel_rotation_good, .gain.control = state_io.gain.control, .gain.auto_control = state_io.gain.auto_control, @@ -669,6 +671,8 @@ update_state(MPPipeline *pipeline, const mp_state_io *new_state) state_io.preview_width = new_state->preview_width; state_io.preview_height = new_state->preview_height; state_io.device_rotation = new_state->device_rotation; + state_io.device_accel_rotation = new_state->device_accel_rotation; + state_io.device_accel_rotation_good = new_state->device_accel_rotation_good; if (state_io.camera) { state_io.gain.value = new_state->gain.value; diff --git a/src/main.c b/src/main.c index 0ef3176..51c39ea 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "gl_util.h" #include "io_pipeline.h" #include "process_pipeline.h" +#include "rotation.h" #include "state.h" #include #include @@ -120,6 +121,8 @@ update_io_pipeline() .preview_width = state.preview_width, .preview_height = state.preview_height, .device_rotation = state.device_rotation, + .device_accel_rotation = state.device_accel_rotation, + .device_accel_rotation_good = state.device_accel_rotation_good, .gain.control = state.gain.control, .gain.auto_control = state.gain.auto_control, @@ -1291,6 +1294,17 @@ xevent_handler(GdkDisplay *display, XEvent *xevent, gpointer data) } #endif // GDK_WINDOWING_X11 +static void +rotation_sensor_handler(int rotation, bool good) +{ + if (rotation != state.device_accel_rotation || + good != state.device_accel_rotation_good) { + state.device_accel_rotation = rotation; + state.device_accel_rotation_good = good; + update_io_pipeline(); + } +} + static void activate(GtkApplication *app, gpointer data) { @@ -1472,6 +1486,7 @@ activate(GtkApplication *app, gpointer data) GDBusConnection *conn = g_application_get_dbus_connection(G_APPLICATION(app)); mp_flash_gtk_init(conn); + mp_device_rotation_init(rotation_sensor_handler); if (state.configuration->count > 0) { mp_io_pipeline_start(); @@ -1501,6 +1516,7 @@ shutdown(GApplication *app, gpointer data) #ifdef DEBUG mp_io_pipeline_stop(); mp_flash_gtk_clean(); + mp_device_rotation_clean(); g_clear_object(&fb_settings); g_clear_object(&capture_event); diff --git a/src/process_pipeline.c b/src/process_pipeline.c index e50d806..f544fe2 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -1564,7 +1564,9 @@ update_state(MPPipeline *pipeline, const mp_state_proc *new_state) new_state->camera->current_mode) || state_proc.preview_width != new_state->preview_width || state_proc.preview_height != new_state->preview_height || - state_proc.device_rotation != new_state->device_rotation; + state_proc.device_rotation != new_state->device_rotation || + state_proc.device_accel_rotation != new_state->device_accel_rotation || + state_proc.device_accel_rotation_good != new_state->device_accel_rotation_good; bool format_changed = state_proc.mode == NULL; @@ -1579,6 +1581,8 @@ update_state(MPPipeline *pipeline, const mp_state_proc *new_state) state_proc.preview_height = new_state->preview_height; state_proc.device_rotation = new_state->device_rotation; + state_proc.device_accel_rotation = new_state->device_accel_rotation; + state_proc.device_accel_rotation_good = new_state->device_accel_rotation_good; state_proc.burst_length = new_state->burst_length; state_proc.balance[0] = new_state->balance[0]; @@ -1602,9 +1606,12 @@ update_state(MPPipeline *pipeline, const mp_state_proc *new_state) } if (output_changed || camera_changed) { + int rotation; + rotation = state_proc.device_accel_rotation_good ? + state_proc.device_accel_rotation : + state_proc.device_rotation; state_proc.camera_rotation = mod( - state_proc.mode->rotation - state_proc.device_rotation, 360); - + state_proc.mode->rotation - rotation, 360); on_output_changed(format_changed); } diff --git a/src/rotation.c b/src/rotation.c new file mode 100644 index 0000000..32b938f --- /dev/null +++ b/src/rotation.c @@ -0,0 +1,182 @@ +#include "gtk/gtk.h" +#include +#include "rotation.h" + +struct sensor_cb_data { + sensor_cb rotation_update; +}; + +static GDBusProxy *iio_proxy; +static bool rotation_valid = false; +static int rotation; +struct sensor_cb_data cb_data; + +static int +rotation_string_to_degree(const gchar* val) +{ + int rot = -1; + if (!g_ascii_strcasecmp(val, "normal")) + rot = 0; + if (!g_ascii_strcasecmp(val, "right-up")) + rot = 270; + if (!g_ascii_strcasecmp(val, "left-up")) + rot = 90; + if (!g_ascii_strcasecmp(val, "bottom-up")) + rot = 180; + return rot; +} + +static void +rotation_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data) +{ + GVariant *v; + GVariantDict dict; + const gchar *val; + struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data; + + g_variant_dict_init (&dict, changed_properties); + + if (g_variant_dict_contains (&dict, "HasAccelerometer")) { + v = g_dbus_proxy_get_cached_property (iio_proxy, "HasAccelerometer"); + rotation_valid = g_variant_get_boolean (v); + g_debug ("accelerometer changed: %d\n", rotation_valid); + g_variant_unref (v); + } + if (g_variant_dict_contains (&dict, "AccelerometerOrientation")) { + v = g_dbus_proxy_get_cached_property (iio_proxy, "AccelerometerOrientation"); + val = g_variant_get_string (v, NULL); + rotation = rotation_string_to_degree(val); + g_variant_unref (v); + } + g_variant_dict_clear (&dict); + + g_debug ("sensor rotation changed: %d %d\n", + rotation, rotation_valid); + cb_data->rotation_update(rotation, rotation_valid); +} + +static void +rotation_get_initial_values (struct sensor_cb_data *cb_data) +{ + GVariant *v; + const gchar *val; + + v = g_dbus_proxy_get_cached_property (iio_proxy, "HasAccelerometer"); + rotation_valid = g_variant_get_boolean (v); + if (rotation_valid) { + g_variant_unref (v); + v = g_dbus_proxy_get_cached_property (iio_proxy, "AccelerometerOrientation"); + val = g_variant_get_string (v, NULL); + rotation = rotation_string_to_degree(val); + g_debug ("rotation: %d\n", rotation); + } else + g_debug ("no accelerometer\n"); + g_variant_unref (v); + cb_data->rotation_update(rotation, rotation_valid); +} + +static void +rotation_accelerometer_claimed(GDBusProxy *proxy, + GAsyncResult *res, + gpointer user_data) +{ + struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data; + + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) result = g_dbus_proxy_call_finish(proxy, res, &error); + + if (!result) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to claim accelerometer: %s", + error->message); + return; + } + rotation_get_initial_values (cb_data); +} + +static void +rotation_iio_proxy_init (GObject *src, + GAsyncResult *res, + gpointer user_data) +{ + struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data; + g_autoptr(GError) err = NULL; + + iio_proxy = g_dbus_proxy_new_finish(res, &err); + if (!iio_proxy || err) { + g_warning ("Failed to connect to sensor proxy service %s\n", + err->message); + return; + } + + g_signal_connect (G_OBJECT (iio_proxy), "g-properties-changed", + G_CALLBACK (rotation_properties_changed), + cb_data); + + g_dbus_proxy_call (iio_proxy, + "ClaimAccelerometer", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback)rotation_accelerometer_claimed, + cb_data); +} + +static void +rotation_iio_proxy_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + g_debug ("iio-sensor-proxy appeared\n"); + g_dbus_proxy_new (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "net.hadess.SensorProxy", + "/net/hadess/SensorProxy", + "net.hadess.SensorProxy", + NULL, + (GAsyncReadyCallback)rotation_iio_proxy_init, + user_data); +} + +static void +rotation_iio_proxy_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data; + + if (iio_proxy) { + g_clear_object (&iio_proxy); + g_debug ("iio-sensor-proxy vanished\n"); + + rotation_valid = false; + rotation = 0; + cb_data->rotation_update(rotation, rotation_valid); + } +} + +void +mp_device_rotation_init(sensor_cb cb) +{ + g_assert(cb != NULL); + cb_data.rotation_update = cb; + g_bus_watch_name (G_BUS_TYPE_SYSTEM, + "net.hadess.SensorProxy", + G_BUS_NAME_WATCHER_FLAGS_NONE, + rotation_iio_proxy_appeared_cb, + rotation_iio_proxy_vanished_cb, + &cb_data, + NULL); +} + +void +mp_device_rotation_clean(void) +{ + g_object_unref(iio_proxy); +} diff --git a/src/rotation.h b/src/rotation.h new file mode 100644 index 0000000..deafedf --- /dev/null +++ b/src/rotation.h @@ -0,0 +1,9 @@ +#ifndef _MP_ROTATION_H +#define _MP_ROTATION_H + +typedef void(*sensor_cb)(int, bool); + +void mp_device_rotation_init(sensor_cb cb); +void mp_device_rotation_clean(void); + +#endif diff --git a/src/state.h b/src/state.h index 5145a57..7517e0c 100644 --- a/src/state.h +++ b/src/state.h @@ -29,6 +29,8 @@ typedef struct state_main { int preview_buffer_height; int device_rotation; + int device_accel_rotation; + bool device_accel_rotation_good; int burst_length; @@ -70,6 +72,8 @@ typedef struct state_io { int preview_width; int preview_height; int device_rotation; + int device_accel_rotation; + bool device_accel_rotation_good; } mp_state_io; typedef enum aaa_mode { @@ -93,6 +97,8 @@ typedef struct state_proc { // Device orientation in degrees int device_rotation; + int device_accel_rotation; + bool device_accel_rotation_good; // Camera orientation as number int camera_rotation;