diff --git a/CMakeLists.txt b/CMakeLists.txt index 96a14f8..844eb8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(XRandR REQUIRED IMPORTED_TARGET xrandr) pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11) pkg_check_modules(Xi REQUIRED IMPORTED_TARGET xi) +pkg_check_modules(Xfixes REQUIRED IMPORTED_TARGET xfixes) set(XDG_AUTOSTART_DIR "/etc/xdg/autostart" CACHE PATH "Path to the freedesktop autostart directory") @@ -42,7 +43,8 @@ target_link_libraries(lmss PUBLIC PkgConfig::XRandR PkgConfig::X11 - PkgConfig::Xi) + PkgConfig::Xi + PkgConfig::Xfixes) target_compile_options(lmss PRIVATE -Wall -Wextra -std=c++20) @@ -68,6 +70,7 @@ set(CPACK_GENERATOR "TGZ;DEB") # DEB set(CPACK_DEBIAN_PACKAGE_DEPENDS "libxi6 (>= 1.7.0)") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libxfixes3 (>= 5.0.3)") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "WEY Technology AG") set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/install/postinst;${CMAKE_CURRENT_SOURCE_DIR}/install/prerm;" ) @@ -83,6 +86,7 @@ set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/install/postinst") set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/install/prerm") set(CPACK_RPM_PACKAGE_REQUIRES "libXi >= 1.7.0") +set(CPACK_RPM_PACKAGE_REQUIRES "libXfixes >= 5.0.3") set(CPACK_SOURCE_IGNORE_FILES /.git diff --git a/src/display.cpp b/src/display.cpp index 396748c..6b65243 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -12,12 +12,12 @@ static const char screen_config_file[] = "/etc/lmss.sl"; -static const int BORDER_WIDTH = 1; static const int BORDER_CLEARANCE = 1; static const int RESOLUTION = 65536; -display::display(logger & log, context & ctx) +display::display(logger & log, bool dnd, context & ctx) : log(log) + , enable_dnd(dnd) , ctx(ctx) { auto dsp_var = getenv("DISPLAY"); @@ -39,6 +39,19 @@ display::display(logger & log, context & ctx) throw std::runtime_error("XInput 2.0 not supported"); } + int fixes_opcode; + + if (!XQueryExtension(dsp.get(), "XFIXES", &fixes_opcode, &event, &error)) { + throw std::runtime_error("XFIXES extension not supported"); + } + + major = 5; + minor = 0; + XFixesQueryVersion(dsp.get(), &major, &minor); + if (major < 5) { + throw std::runtime_error("XFIXES 5.0 not supported"); + } + if (std::filesystem::exists(screen_config_file)) { read_screen_layout_from_file(screen_config_file); } else { @@ -50,6 +63,12 @@ display::display(logger & log, context & ctx) ctx.get_el().add_fd(*xfd, std::bind(&display::handle_events, this, std::placeholders::_1)); } +display::~display() { + for (auto b : barriers) { + XFixesDestroyPointerBarrier(dsp.get(), b.id); + } +} + void display::read_screen_layout_from_file(std::string const & config_file) { std::ifstream cf(config_file); @@ -79,12 +98,12 @@ void display::read_screen_layout_from_file(std::string const & config_file) { throw std::runtime_error("empty screen layout configuration"); } - subscribe_to_motion_events(XDefaultRootWindow(dsp.get())); + subscribe_to_barrier_events(XDefaultRootWindow(dsp.get())); } -void display::subscribe_to_motion_events(Window win) { +void display::subscribe_to_barrier_events(Window win) { unsigned char mask_bytes[(XI_LASTEVENT + 7) / 8] = {0}; - XISetMask(mask_bytes, XI_RawMotion); + XISetMask(mask_bytes, XI_BarrierHit); XIEventMask evmasks[1]; evmasks[0].deviceid = XIAllDevices; @@ -94,6 +113,7 @@ void display::subscribe_to_motion_events(Window win) { if (auto mask = XISelectEvents(dsp.get(), win, evmasks, 1); mask > 0) { throw std::runtime_error("failed to select XI events"); } + XSync(dsp.get(), False); } void display::detect_screen_layout() { @@ -122,20 +142,25 @@ void display::detect_screen_layout() { ++id; } - log.info("display size: " + std::to_string(width) + "x" + std::to_string(height)); - subscribe_to_motion_events(root); + subscribe_to_barrier_events(root); } } void display::add_monitor(int mon, int x, int y, int w, int h, Window root) { - border_rects.emplace_back(x, y, BORDER_WIDTH, h, mon, border_t::LEFT, root); - border_rects.emplace_back(x, y, w, BORDER_WIDTH, mon, border_t::TOP, root); - border_rects.emplace_back(x + w - BORDER_WIDTH, y, BORDER_WIDTH, h, mon, border_t::RIGHT, root); - border_rects.emplace_back(x, y + h - BORDER_WIDTH, w, BORDER_WIDTH, mon, border_t::BOTTOM, root); + barriers.emplace_back( + XFixesCreatePointerBarrier(dsp.get(), root, x + 1, y, x + 1, y + h, BarrierPositiveX, 0, nullptr), + border_t::LEFT, mon); + barriers.emplace_back( + XFixesCreatePointerBarrier(dsp.get(), root, x + w, y, x + w, y + h, BarrierNegativeX, 0, nullptr), + border_t::RIGHT, mon); + barriers.emplace_back( + XFixesCreatePointerBarrier(dsp.get(), root, x, y + 1, x + w, y + 1, BarrierPositiveY, 0, nullptr), + border_t::TOP, mon); + barriers.emplace_back( + XFixesCreatePointerBarrier(dsp.get(), root, x, y + h, x + w, y + h, BarrierNegativeY, 0, nullptr), + border_t::BOTTOM, mon); monitors.emplace_back(monitor_t { .id = mon, .x = x, .y = y, .w = w, .h = h, .root = root }); - width = std::max(x + w, width); - height = std::max(y + h, height); } void display::set_mouse_pos(mouse_pos_t const & mp) { @@ -145,8 +170,20 @@ void display::set_mouse_pos(mouse_pos_t const & mp) { << " pos:" << mp.pos; log.debug(ss.str()); + if (hidden) { + hidden = false; + for (auto const & m : monitors) { + XFixesShowCursor(dsp.get(), m.root); + XSync(dsp.get(), False); + } + } + + if (mp.border < border_t::HIDE && mp.screen == last_mon) { + log.debug("ignoring repositioning on border of same screen"); + return; + } + int x, y; - hidden = false; switch (mp.border) { case border_t::TOP: x = monitors[mp.screen].x + monitors[mp.screen].w * mp.pos / RESOLUTION; @@ -166,19 +203,19 @@ void display::set_mouse_pos(mouse_pos_t const & mp) { break; case border_t::HIDE: log.debug("hiding cursor"); - // we position the pointer in the left bottom border, there is still 1 pixel visible - // maybe we could really hide it by overriding the cursor bitmap - x = monitors[mp.screen].x; - y = monitors[mp.screen].y + height; + for (auto const & m : monitors) { + XFixesHideCursor(dsp.get(), m.root); + XSync(dsp.get(), False); + } hidden = true; - break; + return; default: log.debug("centering cursor"); x = monitors[mp.screen].x + monitors[mp.screen].w / 2; y = monitors[mp.screen].y + monitors[mp.screen].h / 2; } XWarpPointer(dsp.get(), None, monitors[mp.screen].root, 0, 0, 0, 0, x, y); - last_pos = { x, y, monitors[mp.screen].root }; + last_mon = mp.screen; XFlush(dsp.get()); } @@ -193,132 +230,62 @@ void display::handle_events(int) { continue; } - if (ev.xcookie.evtype != XI_RawMotion || hidden) { + auto b = static_cast(ev.xcookie.data); + if (b->evtype != XI_BarrierHit || hidden) { XFreeEventData(dsp.get(), &ev.xcookie); continue; } - XFreeEventData(dsp.get(), &ev.xcookie); - - Window root, child; - int root_x, root_y; - int x, y; - unsigned int mask; - for (auto & m : monitors) { - if (XQueryPointer(dsp.get(), m.root, &root, &child, &root_x, &root_y, - &x, &y, &mask)) { - - break; - } - } + std::stringstream ss; + ss << "Barrier " << b->barrier << " hit: " << b->root_x << " / " << b->root_y + << " dx/dy: " << b->dx << " / " << b->dy; + log.debug(ss.str()); - log.debug("pointer: " + std::to_string(root_x) + "/" + std::to_string(root_y)); + int root_x = b->root_x; + int root_y = b->root_y; - if (mask & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) { - log.debug("mouse button pressed, skipping border detection"); - continue; - } + if (enable_dnd) { + Window root, child; + int rx, ry; + int x, y; + unsigned int mask; - auto const & m_last = get_mon_for_pos(last_pos); - auto const & m_cur = get_mon_for_pos({root_x, root_y, root}); - auto diff_x = std::abs(root_x - last_pos.x); - auto diff_y = std::abs(root_y - last_pos.y); - uint16_t pos = 0; - border_t border; - - // we need to check if we crossed a border since last pointer update - if (root != last_pos.root) { - if (diff_x < diff_y) { // top/bottom - pos = RESOLUTION * (last_pos.x - m_last.x) / m_last.w; - if (root_y < m_cur.h / 2) { // top - border = border_t::TOP; - } else { //bottom - border = border_t::BOTTOM; - } - } else { // left/right - pos = RESOLUTION * (last_pos.y - m_last.y) / m_last.h; - if (root_x < m_cur.w / 2) { // right - border = border_t::RIGHT; - } else { // left - border = border_t::LEFT; + for (auto & m : monitors) { + if (XQueryPointer(dsp.get(), m.root, &root, &child, &rx, &ry, &x, &y, &mask)) { + break; } } - log.debug("different root window, left at border: " + std::to_string(border)); - - ctx.mouse_at_border({ - .screen = static_cast(m_last.id), - .border = border, - .pos = pos - }); - } else if (m_last != m_cur && (diff_x >= 1 || diff_y >= 1)) { - if (m_cur.x == m_last.x) { - log.debug("mons are above each other: " - + std::to_string(m_cur.y) + "+" + std::to_string(m_cur.h) + " | " - + std::to_string(m_last.y) + "+" + std::to_string(m_last.h)); - - pos = RESOLUTION * (root_x - m_cur.x) / m_cur.w; - border = m_cur.y + m_cur.h == m_last.y ? border_t::TOP : border_t::BOTTOM; - } else { - log.debug("mons are next to each other: " - + std::to_string(m_cur.x) + "+" + std::to_string(m_cur.w) + " | " - + std::to_string(m_last.x) + "+" + std::to_string(m_last.w)); - - border = m_cur.x + m_cur.w == m_last.x ? border_t::LEFT : border_t::RIGHT; - pos = RESOLUTION * (root_y - m_cur.y) / m_cur.h; + if (mask & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) { + log.debug("mouse button pressed, disabling barrier for next move"); + XIBarrierReleasePointer(dsp.get(), b->deviceid, b->barrier, b->eventid); + XFlush(dsp.get()); + XFreeEventData(dsp.get(), &ev.xcookie); + continue; } - log.debug("border: " + std::to_string(border)); - - ctx.mouse_at_border({ - .screen = static_cast(m_last.id), - .border = border, - .pos = pos - }); - } else { - for (auto & b : border_rects) { - if (b.inside(root_x, root_y) && b.root == root) { - switch (b.border) { - case border_t::TOP: - case border_t::BOTTOM: - pos = RESOLUTION * (root_x - b.x) / b.w; - break; - case border_t::LEFT: - case border_t::RIGHT: - pos = RESOLUTION * (root_y - b.y) / b.h; - break; - default: - throw std::runtime_error("invalid border"); - } - - log.debug("pointer at border " + std::to_string(b.border) + " of screen " - + std::to_string(b.screen)); - - ctx.mouse_at_border({ - .screen = static_cast(b.screen), - .border = b.border, - .pos = pos - }); + } - break; + for (auto const & barrier : barriers) { + if (barrier.id == b->barrier) { + auto const & m_cur = monitors[barrier.mon]; + + uint16_t pos = 0; + if (barrier.border == border_t::TOP || barrier.border == border_t::BOTTOM) { + pos = RESOLUTION * (root_x - m_cur.x) / m_cur.w; + } else { + pos = RESOLUTION * (root_y - m_cur.y) / m_cur.h; } + + last_mon = barrier.mon; + ctx.mouse_at_border({ + .screen = static_cast(barrier.mon), + .border = barrier.border, + .pos = pos + }); + break; } } - last_pos = { root_x, root_y, root }; - } - } -} - -display::monitor_t const & display::get_mon_for_pos(pos_t const & pos) const { - for (auto const & m : monitors) { - if ((pos.root == m.root || pos.root == 0) - && pos.x >= m.x - && pos.x <= m.x + m.w - && pos.y >= m.y - && pos.y <= m.y + m.h) { - - return m; + XFreeEventData(dsp.get(), &ev.xcookie); } } - - throw std::logic_error("pointer not inside of any known monitor"); } diff --git a/src/display.hpp b/src/display.hpp index 1f566c3..10db9e8 100644 --- a/src/display.hpp +++ b/src/display.hpp @@ -2,9 +2,10 @@ #pragma once +#include +#include #include #include -#include #include #include @@ -17,7 +18,8 @@ class display final { public: - display(logger &, context &); + display(logger &, bool dnd, context &); + ~display(); void set_mouse_pos(mouse_pos_t const &); void handle_events(int); @@ -45,27 +47,25 @@ class display final { inline bool operator!=(monitor_t const & other) const { return !operator==(other); } }; - struct pos_t { - int x = 0; - int y = 0; - Window root = 0; + struct barrier_t { + PointerBarrier id = 0; + border_t border; + int mon = 0; }; - monitor_t const & get_mon_for_pos(pos_t const &) const; void detect_screen_layout(); void read_screen_layout_from_file(std::string const &); void add_monitor(int mon, int x, int y, int w, int h, Window); - void subscribe_to_motion_events(Window); + void subscribe_to_barrier_events(Window); - pos_t last_pos; + size_t last_mon = 0; file_descriptor xfd; logger & log; + bool enable_dnd; context & ctx; int xi_opcode = 0; dsp_t dsp; - std::vector border_rects; + std::vector barriers; std::vector monitors; bool hidden = false; - int width = 0; - int height = 0; }; diff --git a/src/lmss.cpp b/src/lmss.cpp index bd0ff77..e83704d 100644 --- a/src/lmss.cpp +++ b/src/lmss.cpp @@ -4,11 +4,11 @@ #include -lmss::lmss(logger & log) +lmss::lmss(logger & log, bool dnd) : log(log) , el(log) , usb(log, *this) - , dsp(log, *this) + , dsp(log, dnd, *this) , tfd(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)) { if (!tfd.valid()) { diff --git a/src/lmss.hpp b/src/lmss.hpp index cd768ca..839b0ef 100644 --- a/src/lmss.hpp +++ b/src/lmss.hpp @@ -10,7 +10,7 @@ class lmss final : public context { public: - lmss(logger &); + lmss(logger &, bool dnd); event_loop & get_el() override { return el; } void set_mouse_pos(mouse_pos_t const &) override; diff --git a/src/main.cpp b/src/main.cpp index 08cbb66..d15e118 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,10 @@ int main(int argc, char* argv[]) { .default_value(false) .implicit_value(true) .help("print lmss version"); + app.add_argument("-d", "--disable-dnd") + .default_value(true) + .implicit_value(false) + .help("disable drag'n'drop over internal borders"); try { app.parse_args(argc, argv); @@ -41,7 +45,7 @@ int main(int argc, char* argv[]) { while (true) { try { - lmss l(log); + lmss l(log, app.get("-d")); l.run(); } catch (std::runtime_error const & e) { log.err(e.what());