Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)

Expand All @@ -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;" )
Expand All @@ -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
Expand Down
229 changes: 98 additions & 131 deletions src/display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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 {
Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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());
}

Expand All @@ -193,132 +230,62 @@ void display::handle_events(int) {
continue;
}

if (ev.xcookie.evtype != XI_RawMotion || hidden) {
auto b = static_cast<XIBarrierEvent*>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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");
}
Loading