From ecd478799c53231bb7e44d97b37867afcde29936 Mon Sep 17 00:00:00 2001 From: Boris Ivanov Date: Wed, 22 Apr 2026 15:07:20 -0400 Subject: [PATCH 1/2] fix: autorotate-iio: fix D-Bus events not processed when display is idle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin used an OUTPUT_EFFECT_PRE hook (on_frame) to pump the GLib main context. This hook only fires while the compositor is actively rendering frames for that output. When the display is idle — no damaged windows, no animations — no frames are rendered, so GLib's main context never runs and property-change signals from iio-sensor-proxy queue up unprocessed. Rotation stops working until something triggers a redraw (e.g. an active terminal window causes continuous repaints). Fix: replace on_frame with a wl_event_loop_add_timer(50ms) wired into the Wayland event loop via wl_display_get_event_loop(). This pumps the GLib main context at a steady rate regardless of display activity. Additional fixes: - Guard against a null Glib::Variant from get_cached_property() when the D-Bus proxy cache is not yet populated; calling .get() on an uninitialized Variant crashes the compositor. - Call update_orientation() immediately after ClaimAccelerometer so the initial device orientation is applied without waiting for a change event. - Add an asymmetric debounce state machine to handle noisy sensors: 500ms to commit a rotation, 1200ms to return to normal. The longer normal timeout prevents the screen from flipping back during the brief "normal" readings that occur mid-rotation on some hardware. The rotation timer is not reset on subsequent non-normal readings so the delay is measured from the first reading, not the last. --- src/autorotate-iio.cpp | 92 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/src/autorotate-iio.cpp b/src/autorotate-iio.cpp index 4f0ba11..e4f7733 100644 --- a/src/autorotate-iio.cpp +++ b/src/autorotate-iio.cpp @@ -22,6 +22,7 @@ extern "C" { #include #include +#include } using namespace Gio; @@ -105,6 +106,12 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t /* Transform coming from the iio-sensors, -1 means not set */ int32_t sensor_transform = -1; + /* Debounce: pending transform waiting to be confirmed stable */ + int32_t pending_transform = -1; + guint debounce_timer_id = 0; + static constexpr guint DEBOUNCE_ROTATE_MS = 500; + static constexpr guint DEBOUNCE_NORMAL_MS = 1200; + bool on_rotate_binding(int32_t target_rotation) { if (!output->can_activate_plugin(&grab_interface)) @@ -154,10 +161,18 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t return true; } - wf::effect_hook_t on_frame = [=] () + /* Wayland event loop timer — fires every 50ms to pump the GLib main context + * even when the display is idle and on_frame would not run. */ + wl_event_source *glib_timer = nullptr; + + static int glib_timer_cb(void *data) { + auto *self = static_cast(data); Glib::MainContext::get_default()->iteration(false); - }; + wl_event_source_timer_update(self->glib_timer, 50); + return 0; + } + Glib::RefPtr loop; public: @@ -185,7 +200,10 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t Gio::init(); loop = Glib::MainLoop::create(true); - output->render->add_effect(&on_frame, wf::OUTPUT_EFFECT_PRE); + + auto *evloop = wl_display_get_event_loop(wf::get_core().display); + glib_timer = wl_event_loop_add_timer(evloop, glib_timer_cb, this); + wl_event_source_timer_update(glib_timer, 50); watch_id = DBus::watch_name(DBus::BUS_TYPE_SYSTEM, "net.hadess.SensorProxy", sigc::mem_fun(this, &WayfireAutorotateIIO::on_iio_appeared), @@ -210,6 +228,7 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t iio_proxy->signal_properties_changed().connect_notify( sigc::mem_fun(this, &WayfireAutorotateIIO::on_properties_changed)); iio_proxy->call_sync("ClaimAccelerometer"); + update_orientation(); } void on_properties_changed( @@ -228,6 +247,11 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t Glib::Variant orientation; iio_proxy->get_cached_property(orientation, "AccelerometerOrientation"); + if (!orientation) + { + return; + } + LOGI("IIO Accelerometer orientation: %s", orientation.get().c_str()); static const std::map transform_by_name = @@ -238,10 +262,57 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t {"bottom-up", WL_OUTPUT_TRANSFORM_180}, }; - if (transform_by_name.count(orientation.get())) + if (!transform_by_name.count(orientation.get())) + { + return; + } + + int32_t new_transform = transform_by_name.find(orientation.get())->second; + + bool timer_running = (debounce_timer_id != 0); + bool new_is_normal = (new_transform == WL_OUTPUT_TRANSFORM_NORMAL); + bool pend_is_normal = (pending_transform == WL_OUTPUT_TRANSFORM_NORMAL); + + if (!timer_running) + { + if (new_transform == sensor_transform) + { + return; + } + + pending_transform = new_transform; + guint delay = new_is_normal ? DEBOUNCE_NORMAL_MS : DEBOUNCE_ROTATE_MS; + debounce_timer_id = g_timeout_add(delay, [] (gpointer data) -> gboolean + { + auto *self = static_cast(data); + self->debounce_timer_id = 0; + self->sensor_transform = self->pending_transform; + self->update_transform(); + return G_SOURCE_REMOVE; + }, this); + } + else if (!pend_is_normal) + { + if (!new_is_normal) + { + pending_transform = new_transform; + } + } + else { - sensor_transform = transform_by_name.find(orientation.get())->second; - update_transform(); + if (!new_is_normal) + { + g_source_remove(debounce_timer_id); + pending_transform = new_transform; + debounce_timer_id = g_timeout_add(DEBOUNCE_ROTATE_MS, [] (gpointer data) -> gboolean + { + auto *self = static_cast(data); + self->debounce_timer_id = 0; + self->sensor_transform = self->pending_transform; + self->update_transform(); + return G_SOURCE_REMOVE; + }, this); + } } } @@ -259,13 +330,20 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t output->rem_binding(&on_rotate_up); output->rem_binding(&on_rotate_down); + if (debounce_timer_id) + { + g_source_remove(debounce_timer_id); + debounce_timer_id = 0; + } + /* If loop is NULL, autorotate was disabled for the current output */ if (loop) { iio_proxy.reset(); DBus::unwatch_name(watch_id); loop->quit(); - output->render->rem_effect(&on_frame); + wl_event_source_remove(glib_timer); + glib_timer = nullptr; } } }; From 76d49f724db7b8b19b5a280306a6c4e1d2e0f3dc Mon Sep 17 00:00:00 2001 From: Boris Ivanov Date: Wed, 22 Apr 2026 17:50:40 -0400 Subject: [PATCH 2/2] fix code style to pass uncrustify CI check Co-Authored-By: Claude Sonnet 4.6 --- src/autorotate-iio.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/autorotate-iio.cpp b/src/autorotate-iio.cpp index e4f7733..7656698 100644 --- a/src/autorotate-iio.cpp +++ b/src/autorotate-iio.cpp @@ -108,7 +108,7 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t /* Debounce: pending transform waiting to be confirmed stable */ int32_t pending_transform = -1; - guint debounce_timer_id = 0; + guint debounce_timer_id = 0; static constexpr guint DEBOUNCE_ROTATE_MS = 500; static constexpr guint DEBOUNCE_NORMAL_MS = 1200; @@ -290,15 +290,13 @@ class WayfireAutorotateIIO : public wf::per_output_plugin_instance_t self->update_transform(); return G_SOURCE_REMOVE; }, this); - } - else if (!pend_is_normal) + } else if (!pend_is_normal) { if (!new_is_normal) { pending_transform = new_transform; } - } - else + } else { if (!new_is_normal) {