mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-30 01:42:49 +08:00
[fix] restore remote cursors on Linux without a pointing device, refs #130
This commit is contained in:
@@ -13,11 +13,16 @@ bool g_cursor_state_available = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
void PublishSharedCursorState(bool visible, RemoteCursorShape shape) {
|
||||
void PublishSharedCursorState(CursorRenderMode mode, RemoteCursorShape shape) {
|
||||
std::lock_guard<std::mutex> lock(g_cursor_state_mutex);
|
||||
++g_cursor_state.generation;
|
||||
g_cursor_state.visible = visible;
|
||||
g_cursor_state.shape = visible ? shape : RemoteCursorShape::none;
|
||||
g_cursor_state.visible = mode == CursorRenderMode::separate;
|
||||
g_cursor_state.shape = g_cursor_state.visible ? shape : RemoteCursorShape::none;
|
||||
g_cursor_state.render_mode = mode;
|
||||
g_cursor_state.hidden_reason = mode == CursorRenderMode::hidden
|
||||
? CursorHiddenReason::system_hidden
|
||||
: mode == CursorRenderMode::unknown ? CursorHiddenReason::sampling_failed
|
||||
: CursorHiddenReason::unspecified;
|
||||
g_cursor_state_available = true;
|
||||
}
|
||||
|
||||
@@ -38,6 +43,8 @@ void ClearSharedCursorState() {
|
||||
++g_cursor_state.generation;
|
||||
g_cursor_state.visible = false;
|
||||
g_cursor_state.shape = RemoteCursorShape::none;
|
||||
g_cursor_state.render_mode = CursorRenderMode::unknown;
|
||||
g_cursor_state.hidden_reason = CursorHiddenReason::sampling_failed;
|
||||
g_cursor_state_available = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <remote_cursor_shape.h>
|
||||
#include <remote_action.h>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -11,9 +11,11 @@ struct SharedCursorState {
|
||||
uint64_t generation = 0;
|
||||
bool visible = false;
|
||||
RemoteCursorShape shape = RemoteCursorShape::default_cursor;
|
||||
CursorRenderMode render_mode = CursorRenderMode::unknown;
|
||||
CursorHiddenReason hidden_reason = CursorHiddenReason::sampling_failed;
|
||||
};
|
||||
|
||||
void PublishSharedCursorState(bool visible, RemoteCursorShape shape);
|
||||
void PublishSharedCursorState(CursorRenderMode mode, RemoteCursorShape shape);
|
||||
bool GetSharedCursorState(SharedCursorState* state);
|
||||
void ClearSharedCursorState();
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ bool CursorStateProvider::Sample(const std::vector<DisplayInfo>& displays,
|
||||
#include <string>
|
||||
|
||||
#include "linux_cursor_shape.h"
|
||||
#include "linux_pointer_presence.h"
|
||||
#include "platform.h"
|
||||
#include "shared_cursor_state.h"
|
||||
#include "runtime/cursor_position.h"
|
||||
@@ -90,7 +91,9 @@ bool CursorHasVisiblePixel(const XFixesCursorImage& image) {
|
||||
} // namespace
|
||||
|
||||
struct CursorStateProvider::Impl {
|
||||
const bool wayland = IsWaylandSession();
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
LinuxPointerPresence pointer_presence;
|
||||
|
||||
~Impl() {
|
||||
if (display) XCloseDisplay(display);
|
||||
@@ -103,18 +106,26 @@ CursorStateProvider::~CursorStateProvider() = default;
|
||||
bool CursorStateProvider::Sample(const std::vector<DisplayInfo>& displays,
|
||||
int preferred_display, CursorState* state) {
|
||||
if (!state || !impl_) return false;
|
||||
*state = {};
|
||||
ResetCursorPosition(state);
|
||||
|
||||
if (IsWaylandSession()) {
|
||||
SharedCursorState shared{};
|
||||
if (GetSharedCursorState(&shared)) {
|
||||
state->seq = 0;
|
||||
state->visible = shared.visible;
|
||||
state->shape = shared.visible ? shared.shape : RemoteCursorShape::none;
|
||||
return true;
|
||||
SharedCursorState shared{};
|
||||
if (impl_->wayland && GetSharedCursorState(&shared)) {
|
||||
// XWayland only knows about X11 surfaces, not the captured Wayland
|
||||
// desktop. Missing native metadata must remain an unavailable sample.
|
||||
state->visible = shared.visible;
|
||||
state->shape = shared.shape;
|
||||
state->render_mode = shared.render_mode;
|
||||
state->hidden_reason = shared.hidden_reason;
|
||||
if (state->render_mode == CursorRenderMode::hidden &&
|
||||
impl_->pointer_presence.NoPointingDevice(nullptr, true)) {
|
||||
state->hidden_reason = CursorHiddenReason::no_pointing_device;
|
||||
}
|
||||
return state->render_mode != CursorRenderMode::unknown;
|
||||
}
|
||||
|
||||
// Linux may use X11 capture even in a Wayland session. Only an active
|
||||
// portal publishes shared state; preserve X11 sampling for that backend.
|
||||
if (!impl_->display) return false;
|
||||
|
||||
XFixesCursorImage* image = XFixesGetCursorImage(impl_->display);
|
||||
@@ -126,6 +137,12 @@ bool CursorStateProvider::Sample(const std::vector<DisplayInfo>& displays,
|
||||
state->visible = CursorHasVisiblePixel(*image);
|
||||
state->shape = state->visible ? ShapeFromLinuxCursorName(name)
|
||||
: RemoteCursorShape::none;
|
||||
state->render_mode = state->visible ? CursorRenderMode::separate
|
||||
: CursorRenderMode::hidden;
|
||||
state->hidden_reason = state->visible ? CursorHiddenReason::unspecified
|
||||
: impl_->pointer_presence.NoPointingDevice(impl_->display, false)
|
||||
? CursorHiddenReason::no_pointing_device
|
||||
: CursorHiddenReason::system_hidden;
|
||||
NormalizeCursorPosition(image->x, image->y, displays, preferred_display,
|
||||
state);
|
||||
XFree(image);
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
#include "linux_pointer_presence.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <dlfcn.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
enum class PointerPresence { unknown, present, absent };
|
||||
|
||||
PointerPresence QueryX11PointerPresence(Display* display) {
|
||||
if (!display) return PointerPresence::unknown;
|
||||
int opcode = 0, event = 0, error = 0;
|
||||
int major = 2, minor = 0;
|
||||
if (!XQueryExtension(display, "XInputExtension", &opcode, &event, &error) ||
|
||||
XIQueryVersion(display, &major, &minor) != Success || major < 2) {
|
||||
return PointerPresence::unknown;
|
||||
}
|
||||
int count = 0;
|
||||
XIDeviceInfo* devices = XIQueryDevice(display, XIAllDevices, &count);
|
||||
if (!devices) return PointerPresence::unknown;
|
||||
auto result = count > 0 ? PointerPresence::absent : PointerPresence::unknown;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const auto& device = devices[i];
|
||||
if (!device.enabled || device.use != XISlavePointer) continue;
|
||||
// Xorg creates an XTEST slave for every master even without hardware.
|
||||
// Match its generated name to its master, not a broad "virtual" filter:
|
||||
// mice exposed by xrdp and other virtual desktops still count as pointers.
|
||||
bool xtest = false;
|
||||
for (int j = 0; j < count && device.name; ++j) {
|
||||
const auto& master = devices[j];
|
||||
if (master.deviceid != device.attachment ||
|
||||
master.use != XIMasterPointer || !master.name)
|
||||
continue;
|
||||
const std::string name = master.name;
|
||||
constexpr char suffix[] = " pointer";
|
||||
if (name.size() >= sizeof(suffix) - 1 &&
|
||||
name.compare(name.size() - (sizeof(suffix) - 1), sizeof(suffix) - 1,
|
||||
suffix) == 0) {
|
||||
xtest = name.substr(0, name.size() - (sizeof(suffix) - 1)) +
|
||||
" XTEST pointer" ==
|
||||
device.name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!xtest) {
|
||||
result = PointerPresence::present;
|
||||
break;
|
||||
}
|
||||
}
|
||||
XIFreeDeviceInfo(devices);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Load the public client ABI, as the Wayland activation helper does. This
|
||||
// keeps X11-only installations free of a new build/runtime dependency. The
|
||||
// protocol interfaces themselves come from libwayland-client.
|
||||
struct WaylandInterface;
|
||||
struct WaylandApi {
|
||||
void* library = dlopen("libwayland-client.so.0", RTLD_NOW | RTLD_LOCAL);
|
||||
void* (*connect)(const char*) = nullptr;
|
||||
void (*disconnect)(void*) = nullptr;
|
||||
int (*get_fd)(void*) = nullptr;
|
||||
int (*dispatch_pending)(void*) = nullptr;
|
||||
int (*prepare_read)(void*) = nullptr;
|
||||
void (*cancel_read)(void*) = nullptr;
|
||||
int (*read_events)(void*) = nullptr;
|
||||
int (*flush)(void*) = nullptr;
|
||||
void (*destroy_proxy)(void*) = nullptr;
|
||||
void* (*construct)(void*, uint32_t, const WaylandInterface*, uint32_t,
|
||||
...) = nullptr;
|
||||
int (*add_listener)(void*, void (**)(void), void*) = nullptr;
|
||||
const WaylandInterface* registry_interface = nullptr;
|
||||
const WaylandInterface* seat_interface = nullptr;
|
||||
const WaylandInterface* callback_interface = nullptr;
|
||||
bool available = false;
|
||||
|
||||
template <class T>
|
||||
bool Load(T& target, const char* name) {
|
||||
target = reinterpret_cast<T>(dlsym(library, name));
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
WaylandApi() {
|
||||
available = library && Load(connect, "wl_display_connect") &&
|
||||
Load(disconnect, "wl_display_disconnect") &&
|
||||
Load(get_fd, "wl_display_get_fd") &&
|
||||
Load(dispatch_pending, "wl_display_dispatch_pending") &&
|
||||
Load(prepare_read, "wl_display_prepare_read") &&
|
||||
Load(cancel_read, "wl_display_cancel_read") &&
|
||||
Load(read_events, "wl_display_read_events") &&
|
||||
Load(flush, "wl_display_flush") &&
|
||||
Load(destroy_proxy, "wl_proxy_destroy") &&
|
||||
Load(construct, "wl_proxy_marshal_constructor_versioned") &&
|
||||
Load(add_listener, "wl_proxy_add_listener") &&
|
||||
Load(registry_interface, "wl_registry_interface") &&
|
||||
Load(seat_interface, "wl_seat_interface") &&
|
||||
Load(callback_interface, "wl_callback_interface");
|
||||
}
|
||||
~WaylandApi() {
|
||||
if (library) dlclose(library);
|
||||
}
|
||||
};
|
||||
|
||||
class WaylandPointerPresence {
|
||||
public:
|
||||
~WaylandPointerPresence() { Reset(); }
|
||||
|
||||
PointerPresence Query() {
|
||||
if (!api_.available) return PointerPresence::unknown;
|
||||
if ((!display_ && !Connect()) || !Pump() || failed_) {
|
||||
Reset();
|
||||
return PointerPresence::unknown;
|
||||
}
|
||||
if (!registry_ready_) return PointerPresence::unknown;
|
||||
bool pending = false;
|
||||
for (const auto& [id, seat] : seats_) {
|
||||
if (seat.presence == PointerPresence::present)
|
||||
return PointerPresence::present;
|
||||
pending = pending || seat.presence == PointerPresence::unknown;
|
||||
}
|
||||
return pending ? PointerPresence::unknown : PointerPresence::absent;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Seat {
|
||||
void* proxy = nullptr;
|
||||
PointerPresence presence = PointerPresence::unknown;
|
||||
};
|
||||
|
||||
bool Connect() {
|
||||
// A dedicated connection avoids consuming Slint's events or depending on
|
||||
// which toolkit/display backend the local window uses.
|
||||
display_ = api_.connect(nullptr);
|
||||
if (!display_) return false;
|
||||
registry_ =
|
||||
api_.construct(display_, 1, api_.registry_interface, 1, nullptr);
|
||||
sync_ = api_.construct(display_, 0, api_.callback_interface, 1, nullptr);
|
||||
static void (*registry_listener[])(void) = {
|
||||
reinterpret_cast<void (*)(void)>(&Global),
|
||||
reinterpret_cast<void (*)(void)>(&GlobalRemove)};
|
||||
static void (*sync_listener[])(void) = {
|
||||
reinterpret_cast<void (*)(void)>(&Ready)};
|
||||
return registry_ && sync_ &&
|
||||
api_.add_listener(registry_, registry_listener, this) == 0 &&
|
||||
api_.add_listener(sync_, sync_listener, this) == 0;
|
||||
}
|
||||
|
||||
bool Pump() {
|
||||
if (api_.dispatch_pending(display_) < 0) return false;
|
||||
if (api_.prepare_read(display_) != 0) return false;
|
||||
const int flushed = api_.flush(display_);
|
||||
if (flushed < 0 && errno != EAGAIN) {
|
||||
api_.cancel_read(display_);
|
||||
return false;
|
||||
}
|
||||
pollfd fd{api_.get_fd(display_), POLLIN, 0};
|
||||
if (fd.fd < 0) {
|
||||
api_.cancel_read(display_);
|
||||
return false;
|
||||
}
|
||||
const int ready = poll(&fd, 1, 0);
|
||||
if (ready < 0 || (fd.revents & (POLLERR | POLLHUP | POLLNVAL))) {
|
||||
api_.cancel_read(display_);
|
||||
return false;
|
||||
}
|
||||
if (!(fd.revents & POLLIN)) {
|
||||
api_.cancel_read(display_);
|
||||
return true;
|
||||
}
|
||||
// Never perform a blocking roundtrip on the UI's cursor-sampling tick.
|
||||
return api_.read_events(display_) >= 0 &&
|
||||
api_.dispatch_pending(display_) >= 0;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
for (auto& [id, seat] : seats_) {
|
||||
if (seat.proxy) api_.destroy_proxy(seat.proxy);
|
||||
}
|
||||
seats_.clear();
|
||||
if (sync_) api_.destroy_proxy(sync_);
|
||||
if (registry_) api_.destroy_proxy(registry_);
|
||||
if (display_) api_.disconnect(display_);
|
||||
display_ = registry_ = sync_ = nullptr;
|
||||
registry_ready_ = failed_ = false;
|
||||
}
|
||||
|
||||
static void Global(void* data, void* registry, uint32_t id,
|
||||
const char* interface, uint32_t version) {
|
||||
auto& self = *static_cast<WaylandPointerPresence*>(data);
|
||||
if (std::strcmp(interface, "wl_seat") != 0) return;
|
||||
if (version < 1 || self.seats_.count(id)) {
|
||||
self.failed_ = true;
|
||||
return;
|
||||
}
|
||||
auto& seat = self.seats_[id];
|
||||
seat.proxy = self.api_.construct(registry, 0, self.api_.seat_interface, 1,
|
||||
id, "wl_seat", uint32_t{1}, nullptr);
|
||||
// Version 1 only emits capabilities; seat names are not needed.
|
||||
static void (*listener[])(void) = {
|
||||
reinterpret_cast<void (*)(void)>(&Capabilities)};
|
||||
if (!seat.proxy ||
|
||||
self.api_.add_listener(seat.proxy, listener, &seat) != 0) {
|
||||
self.failed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void GlobalRemove(void* data, void*, uint32_t id) {
|
||||
auto& self = *static_cast<WaylandPointerPresence*>(data);
|
||||
const auto it = self.seats_.find(id);
|
||||
if (it == self.seats_.end()) return;
|
||||
if (it->second.proxy) self.api_.destroy_proxy(it->second.proxy);
|
||||
self.seats_.erase(it);
|
||||
}
|
||||
|
||||
static void Capabilities(void* data, void*, uint32_t capabilities) {
|
||||
// WL_SEAT_CAPABILITY_POINTER; keyboard/touch alone are not pointers.
|
||||
static_cast<Seat*>(data)->presence = (capabilities & 1u)
|
||||
? PointerPresence::present
|
||||
: PointerPresence::absent;
|
||||
}
|
||||
|
||||
static void Ready(void* data, void* callback, uint32_t) {
|
||||
auto& self = *static_cast<WaylandPointerPresence*>(data);
|
||||
self.registry_ready_ = true;
|
||||
self.api_.destroy_proxy(callback);
|
||||
self.sync_ = nullptr;
|
||||
}
|
||||
|
||||
WaylandApi api_;
|
||||
void* display_ = nullptr;
|
||||
void* registry_ = nullptr;
|
||||
void* sync_ = nullptr;
|
||||
std::map<uint32_t, Seat> seats_;
|
||||
bool registry_ready_ = false;
|
||||
bool failed_ = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct LinuxPointerPresence::Impl {
|
||||
std::unique_ptr<WaylandPointerPresence> wayland;
|
||||
std::chrono::steady_clock::time_point next_check{};
|
||||
void* source_display = nullptr;
|
||||
bool source_wayland = false;
|
||||
bool absent_before = false;
|
||||
bool confirmed = false;
|
||||
};
|
||||
|
||||
LinuxPointerPresence::LinuxPointerPresence()
|
||||
: impl_(std::make_unique<Impl>()) {}
|
||||
LinuxPointerPresence::~LinuxPointerPresence() = default;
|
||||
|
||||
bool LinuxPointerPresence::NoPointingDevice(void* x11_display, bool wayland) {
|
||||
if (impl_->source_wayland != wayland ||
|
||||
impl_->source_display != x11_display) {
|
||||
impl_->next_check = {};
|
||||
impl_->absent_before = impl_->confirmed = false;
|
||||
impl_->source_display = x11_display;
|
||||
impl_->source_wayland = wayland;
|
||||
}
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now < impl_->next_check) return impl_->confirmed;
|
||||
// Do not combine absence samples from unrelated, widely separated periods
|
||||
// of cursor hiding. Device changes and failed queries break the sequence.
|
||||
if (now > impl_->next_check + std::chrono::seconds(1))
|
||||
impl_->absent_before = false;
|
||||
impl_->next_check = now + std::chrono::seconds(1);
|
||||
PointerPresence presence;
|
||||
if (wayland) {
|
||||
if (!impl_->wayland)
|
||||
impl_->wayland = std::make_unique<WaylandPointerPresence>();
|
||||
presence = impl_->wayland->Query();
|
||||
} else {
|
||||
presence = QueryX11PointerPresence(static_cast<Display*>(x11_display));
|
||||
}
|
||||
const bool absent = presence == PointerPresence::absent;
|
||||
impl_->confirmed = absent && impl_->absent_before;
|
||||
impl_->absent_before = absent;
|
||||
return impl_->confirmed;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-10-29
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_POINTER_PRESENCE_H_
|
||||
#define _LINUX_POINTER_PRESENCE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
// Owned by the cursor sampler. Only confirmed absence permits a remote
|
||||
// locator; unavailable device information must preserve normal hiding.
|
||||
class LinuxPointerPresence {
|
||||
public:
|
||||
LinuxPointerPresence();
|
||||
~LinuxPointerPresence();
|
||||
|
||||
bool NoPointingDevice(void* x11_display, bool wayland);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -110,7 +110,9 @@ int ScreenCapturerWayland::Start(bool show_cursor) {
|
||||
cursor_metadata_enabled_ = false;
|
||||
cursor_metadata_seen_ = false;
|
||||
cursor_metadata_missing_buffers_ = 0;
|
||||
ClearSharedCursorState();
|
||||
// Claim the native cursor source before portal negotiation. Unknown data
|
||||
// must not fall through to an unrelated XWayland cursor while we wait.
|
||||
PublishSharedCursorState(CursorRenderMode::unknown, RemoteCursorShape::none);
|
||||
paused_ = false;
|
||||
pipewire_node_id_ = 0;
|
||||
UpdateDisplayGeometry(
|
||||
|
||||
+5
-3
@@ -976,21 +976,23 @@ void ScreenCapturerWayland::HandlePipeWireBuffer() {
|
||||
const CursorBitmapResult cursor_result = ReadCursorBitmap(
|
||||
*cursor_metadata, *cursor, &cursor_argb, &cursor_view);
|
||||
if (cursor_result == CursorBitmapResult::kInvisible) {
|
||||
PublishSharedCursorState(false, RemoteCursorShape::none);
|
||||
PublishSharedCursorState(CursorRenderMode::hidden,
|
||||
RemoteCursorShape::none);
|
||||
LOG_INFO("PipeWire cursor became invisible, id={}", cursor->id);
|
||||
} else if (cursor_result == CursorBitmapResult::kImage) {
|
||||
const RemoteCursorShape shape =
|
||||
cursor_theme_matcher_
|
||||
? cursor_theme_matcher_->Match(cursor_view)
|
||||
: RemoteCursorShape::default_cursor;
|
||||
PublishSharedCursorState(true, shape);
|
||||
PublishSharedCursorState(CursorRenderMode::separate, shape);
|
||||
LOG_INFO(
|
||||
"PipeWire cursor shape update: id={}, size={}x{}, "
|
||||
"hotspot=({},{}), shape={}",
|
||||
cursor->id, cursor_view.width, cursor_view.height,
|
||||
cursor_view.xhot, cursor_view.yhot, static_cast<int>(shape));
|
||||
} else if (cursor_result == CursorBitmapResult::kInvalid) {
|
||||
PublishSharedCursorState(true, RemoteCursorShape::default_cursor);
|
||||
PublishSharedCursorState(CursorRenderMode::separate,
|
||||
RemoteCursorShape::default_cursor);
|
||||
LOG_WARN("Using default shape for malformed PipeWire cursor "
|
||||
"metadata, id={}",
|
||||
cursor->id);
|
||||
|
||||
@@ -489,7 +489,7 @@ bool ScreenCapturerWayland::SelectPortalSource() {
|
||||
uint32_t available_cursor_modes = kCursorModeHidden | kCursorModeEmbedded;
|
||||
if (!GetAvailableCursorModes(&available_cursor_modes)) {
|
||||
LOG_WARN(
|
||||
"Unable to query portal cursor modes; using embedded cursor fallback");
|
||||
"Unable to query portal cursor modes; using hidden/embedded fallback");
|
||||
}
|
||||
|
||||
uint32_t cursor_mode = 0;
|
||||
@@ -497,6 +497,10 @@ bool ScreenCapturerWayland::SelectPortalSource() {
|
||||
if (!show_cursor_ && (available_cursor_modes & kCursorModeMetadata) != 0) {
|
||||
cursor_mode = kCursorModeMetadata;
|
||||
cursor_metadata_enabled_ = true;
|
||||
} else if (!show_cursor_ && (available_cursor_modes & kCursorModeHidden) != 0) {
|
||||
// Native controllers can draw a default arrow. Without metadata, do not
|
||||
// rely on an embedded pointer that may disappear with the last mouse.
|
||||
cursor_mode = kCursorModeHidden;
|
||||
} else if ((available_cursor_modes & kCursorModeEmbedded) != 0) {
|
||||
cursor_mode = kCursorModeEmbedded;
|
||||
} else if ((available_cursor_modes & kCursorModeMetadata) != 0) {
|
||||
@@ -515,11 +519,14 @@ bool ScreenCapturerWayland::SelectPortalSource() {
|
||||
// When metadata is unavailable, embedding is the only way to preserve the
|
||||
// exact Wayland cursor. Hide the controller-side cursor to avoid a double
|
||||
// cursor over the video stream.
|
||||
PublishSharedCursorState(false, RemoteCursorShape::none);
|
||||
PublishSharedCursorState(CursorRenderMode::embedded, RemoteCursorShape::none);
|
||||
} else if (!cursor_metadata_enabled_) {
|
||||
// A portal without metadata or embedded cursor support cannot expose the
|
||||
// native Wayland shape. Keep a visible default cursor on the controller.
|
||||
PublishSharedCursorState(true, RemoteCursorShape::default_cursor);
|
||||
// Hidden capture provides no native shape. Keep a visible default cursor
|
||||
// on the controller, independent of whether a local mouse is connected.
|
||||
PublishSharedCursorState(CursorRenderMode::separate,
|
||||
RemoteCursorShape::default_cursor);
|
||||
} else {
|
||||
PublishSharedCursorState(CursorRenderMode::unknown, RemoteCursorShape::none);
|
||||
}
|
||||
|
||||
LOG_INFO("Wayland portal cursor mode: {}",
|
||||
|
||||
@@ -96,6 +96,7 @@ function setup_targets()
|
||||
"apps/desktop/src/platform/linux/screen_lock.cpp",
|
||||
"apps/desktop/src/platform/linux/common/*.cpp")
|
||||
add_includedirs("apps/desktop/src/platform/linux/common")
|
||||
add_syslinks("Xi", {public = true})
|
||||
end
|
||||
add_includedirs("apps/desktop/src/common", {public = true})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user