diff --git a/src/lib/platform/EiScreen.cpp b/src/lib/platform/EiScreen.cpp index 08e59f6a0..d17814851 100644 --- a/src/lib/platform/EiScreen.cpp +++ b/src/lib/platform/EiScreen.cpp @@ -88,6 +88,8 @@ EiScreen::~EiScreen() m_events->adoptBuffer(nullptr); m_events->removeHandler(EventTypes::System, m_events->getSystemTarget()); + cancelIdleEmulationTimer(); + cleanupEi(); delete m_keyState; @@ -294,6 +296,7 @@ void EiScreen::fakeMouseButton(ButtonID button, bool press) break; } + ensureEmulating(); ei_device_button_button(m_eiPointer, code, press); ei_device_frame(m_eiPointer, ei_now(m_ei)); } @@ -310,6 +313,7 @@ void EiScreen::fakeMouseMove(int32_t x, int32_t y) if (!m_eiAbs) return; + ensureEmulating(); ei_device_pointer_motion_absolute(m_eiAbs, x, y); ei_device_frame(m_eiAbs, ei_now(m_ei)); } @@ -319,6 +323,7 @@ void EiScreen::fakeMouseRelativeMove(int32_t dx, int32_t dy) const if (!m_eiPointer) return; + ensureEmulating(); ei_device_pointer_motion(m_eiPointer, dx, dy); ei_device_frame(m_eiPointer, ei_now(m_ei)); } @@ -332,6 +337,7 @@ void EiScreen::fakeMouseWheel(ScrollDelta delta) const // libei and deskflow seem to use opposite directions, so we have // to send EI the opposite of the value received if we want to remain // compatible with other platforms (including X11). + ensureEmulating(); ei_device_scroll_discrete(m_eiPointer, -delta.x, -delta.y); ei_device_frame(m_eiPointer, ei_now(m_ei)); } @@ -343,6 +349,7 @@ void EiScreen::fakeKey(uint32_t keycode, bool isDown) const auto xkbKeycode = keycode + 8; m_keyState->updateXkbState(xkbKeycode, isDown); + ensureEmulating(); ei_device_keyboard_key(m_eiKeyboard, keycode, isDown); ei_device_frame(m_eiKeyboard, ei_now(m_ei)); } @@ -361,22 +368,62 @@ void EiScreen::disable() // Socket-based clipboard is passive (no monitoring needed) } +void EiScreen::cancelIdleEmulationTimer() const +{ + if (m_idleEmulationTimer) { + m_events->removeHandler(EventTypes::Timer, m_idleEmulationTimer); + m_events->deleteTimer(m_idleEmulationTimer); + m_idleEmulationTimer = nullptr; + } +} + +void EiScreen::ensureEmulating() const +{ + if (m_isPrimary || !m_isOnScreen) + return; + + if (!m_isEmulating) { + ++m_sequenceNumber; + if (m_eiPointer) + ei_device_start_emulating(m_eiPointer, m_sequenceNumber); + if (m_eiKeyboard) + ei_device_start_emulating(m_eiKeyboard, m_sequenceNumber); + if (m_eiAbs) + ei_device_start_emulating(m_eiAbs, m_sequenceNumber); + m_isEmulating = true; + } + + cancelIdleEmulationTimer(); + if (Settings::value(Settings::Core::PreventSleep).toBool()) + return; + + m_idleEmulationTimer = m_events->newOneShotTimer(s_idleEmulationTimeout, nullptr); + m_events->addHandler(EventTypes::Timer, m_idleEmulationTimer, [this](const auto &) { stopEmulating(); }); +} + +void EiScreen::stopEmulating() const +{ + cancelIdleEmulationTimer(); + if (!m_isEmulating) + return; + if (m_eiPointer) + ei_device_stop_emulating(m_eiPointer); + if (m_eiKeyboard) + ei_device_stop_emulating(m_eiKeyboard); + if (m_eiAbs) + ei_device_stop_emulating(m_eiAbs); + m_isEmulating = false; +} + void EiScreen::enter() { m_isOnScreen = true; - if (!m_isPrimary) { - ++m_sequenceNumber; - if (m_eiPointer) { - ei_device_start_emulating(m_eiPointer, m_sequenceNumber); - } - if (m_eiKeyboard) { - ei_device_start_emulating(m_eiKeyboard, m_sequenceNumber); - } - if (m_eiAbs) { - ei_device_start_emulating(m_eiAbs, m_sequenceNumber); - fakeMouseMove(m_cursorX, m_cursorY); - } - } else { + if (!m_isPrimary && m_eiAbs) { + // Emulation is started lazily by ensureEmulating() on the first injected + // input and released again after a short idle, so this screen can DPMS-sleep + // while the cursor sits here with no relayed activity. + fakeMouseMove(m_cursorX, m_cursorY); + } else if (m_isPrimary) { LOG_DEBUG("releasing input capture at x=%i y=%i", m_cursorX, m_cursorY); m_portalInputCapture->release(m_cursorX, m_cursorY); } @@ -390,15 +437,7 @@ bool EiScreen::canLeave() void EiScreen::leave() { if (!m_isPrimary) { - if (m_eiPointer) { - ei_device_stop_emulating(m_eiPointer); - } - if (m_eiKeyboard) { - ei_device_stop_emulating(m_eiKeyboard); - } - if (m_eiAbs) { - ei_device_stop_emulating(m_eiAbs); - } + stopEmulating(); } m_isOnScreen = false; @@ -577,12 +616,24 @@ void EiScreen::removeDevice(struct ei_device *device) { LOG_DEBUG("removing device %s", ei_device_get_name(device)); - if (device == m_eiPointer) + bool wasTracked = false; + if (device == m_eiPointer) { m_eiPointer = ei_device_unref(m_eiPointer); - if (device == m_eiKeyboard) + wasTracked = true; + } + if (device == m_eiKeyboard) { m_eiKeyboard = ei_device_unref(m_eiKeyboard); - if (device == m_eiAbs) + wasTracked = true; + } + if (device == m_eiAbs) { m_eiAbs = ei_device_unref(m_eiAbs); + wasTracked = true; + } + + if (wasTracked) { + m_isEmulating = false; + cancelIdleEmulationTimer(); + } for (auto it = m_eiDevices.begin(); it != m_eiDevices.end(); it++) { if (*it == device) { @@ -894,11 +945,13 @@ void EiScreen::handleSystemEvent(const Event &) break; case EI_EVENT_DEVICE_PAUSED: LOG_DEBUG("device %s is paused", ei_device_get_name(device)); + m_isEmulating = false; + cancelIdleEmulationTimer(); break; case EI_EVENT_DEVICE_RESUMED: LOG_DEBUG("device %s is resumed", ei_device_get_name(device)); if (!m_isPrimary && m_isOnScreen) { - ei_device_start_emulating(device, ++m_sequenceNumber); + ensureEmulating(); } break; case EI_EVENT_KEYBOARD_MODIFIERS: diff --git a/src/lib/platform/EiScreen.h b/src/lib/platform/EiScreen.h index 57e498e50..ab7b21cd4 100644 --- a/src/lib/platform/EiScreen.h +++ b/src/lib/platform/EiScreen.h @@ -22,6 +22,8 @@ struct ei_event; struct ei_seat; struct ei_device; +class EventQueueTimer; + namespace deskflow { class EiKeyState; @@ -122,6 +124,9 @@ private: void handleConnectedToEisEvent(const Event &event); void handlePortalSessionClosed(); + void ensureEmulating() const; + void stopEmulating() const; + void cancelIdleEmulationTimer() const; static void handleEiLogEvent(ei *ei, const ei_log_priority priority, const char *message, ei_log_context *) { @@ -151,7 +156,15 @@ private: ei_device *m_eiKeyboard = nullptr; ei_device *m_eiAbs = nullptr; - std::uint32_t m_sequenceNumber = 0; + mutable std::uint32_t m_sequenceNumber = 0; + + // Lazily-started EIS emulation: only grab while relayed input is actually + // flowing, and release after a short idle so the compositor can DPMS-sleep + // this screen even while the deskflow cursor logically sits on it. + mutable bool m_isEmulating = false; + mutable EventQueueTimer *m_idleEmulationTimer = nullptr; + // Chosen empirically on one machine in 2026-06; not derived from any protocol constant. + static constexpr double s_idleEmulationTimeout = 4.0; std::uint32_t m_activeSides = 0; std::uint32_t m_x = 0;