mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-06 14:09:09 +08:00
[refactor] reorganize platform and wire sources
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
#include "linux_cursor_shape.h"
|
||||
|
||||
#include <remote_action.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
constexpr LinuxCursorAlias kCursorAliases[] = {
|
||||
{"help", RemoteCursorShape::help},
|
||||
{"question-arrow", RemoteCursorShape::help},
|
||||
{"left-ptr-help", RemoteCursorShape::help},
|
||||
{"whats-this", RemoteCursorShape::help},
|
||||
{"dnd-ask", RemoteCursorShape::help},
|
||||
{"pointer", RemoteCursorShape::pointer},
|
||||
{"hand", RemoteCursorShape::pointer},
|
||||
{"hand1", RemoteCursorShape::pointer},
|
||||
{"hand2", RemoteCursorShape::pointer},
|
||||
{"link", RemoteCursorShape::pointer},
|
||||
{"pointing-hand", RemoteCursorShape::pointer},
|
||||
{"progress", RemoteCursorShape::progress},
|
||||
{"left-ptr-watch", RemoteCursorShape::progress},
|
||||
{"half-busy", RemoteCursorShape::progress},
|
||||
{"wait", RemoteCursorShape::wait},
|
||||
{"watch", RemoteCursorShape::wait},
|
||||
{"crosshair", RemoteCursorShape::crosshair},
|
||||
{"cross", RemoteCursorShape::crosshair},
|
||||
{"cross-reverse", RemoteCursorShape::crosshair},
|
||||
{"tcross", RemoteCursorShape::crosshair},
|
||||
{"cell", RemoteCursorShape::crosshair},
|
||||
{"plus", RemoteCursorShape::crosshair},
|
||||
{"target", RemoteCursorShape::crosshair},
|
||||
{"dotbox", RemoteCursorShape::crosshair},
|
||||
{"dot-box-mask", RemoteCursorShape::crosshair},
|
||||
{"text", RemoteCursorShape::text},
|
||||
{"xterm", RemoteCursorShape::text},
|
||||
{"ibeam", RemoteCursorShape::text},
|
||||
{"vertical-text", RemoteCursorShape::text},
|
||||
{"alias", RemoteCursorShape::alias},
|
||||
{"dnd-link", RemoteCursorShape::alias},
|
||||
{"copy", RemoteCursorShape::copy},
|
||||
{"dnd-copy", RemoteCursorShape::copy},
|
||||
{"move", RemoteCursorShape::move},
|
||||
{"fleur", RemoteCursorShape::move},
|
||||
{"size-all", RemoteCursorShape::move},
|
||||
{"all-scroll", RemoteCursorShape::move},
|
||||
{"dnd-move", RemoteCursorShape::move},
|
||||
{"no-drop", RemoteCursorShape::no_drop},
|
||||
{"dnd-no-drop", RemoteCursorShape::no_drop},
|
||||
{"dnd-none", RemoteCursorShape::no_drop},
|
||||
{"not-allowed", RemoteCursorShape::not_allowed},
|
||||
{"forbidden", RemoteCursorShape::not_allowed},
|
||||
{"crossed-circle", RemoteCursorShape::not_allowed},
|
||||
{"circle", RemoteCursorShape::not_allowed},
|
||||
{"pirate", RemoteCursorShape::not_allowed},
|
||||
{"grab", RemoteCursorShape::grab},
|
||||
{"openhand", RemoteCursorShape::grab},
|
||||
{"open-hand", RemoteCursorShape::grab},
|
||||
{"grabbing", RemoteCursorShape::grabbing},
|
||||
{"closedhand", RemoteCursorShape::grabbing},
|
||||
{"closed-hand", RemoteCursorShape::grabbing},
|
||||
{"col-resize", RemoteCursorShape::col_resize},
|
||||
{"row-resize", RemoteCursorShape::row_resize},
|
||||
{"n-resize", RemoteCursorShape::n_resize},
|
||||
{"top-side", RemoteCursorShape::n_resize},
|
||||
{"top-tee", RemoteCursorShape::n_resize},
|
||||
{"sb-up-arrow", RemoteCursorShape::n_resize},
|
||||
{"e-resize", RemoteCursorShape::e_resize},
|
||||
{"right-side", RemoteCursorShape::e_resize},
|
||||
{"right-tee", RemoteCursorShape::e_resize},
|
||||
{"sb-right-arrow", RemoteCursorShape::e_resize},
|
||||
{"s-resize", RemoteCursorShape::s_resize},
|
||||
{"bottom-side", RemoteCursorShape::s_resize},
|
||||
{"bottom-tee", RemoteCursorShape::s_resize},
|
||||
{"sb-down-arrow", RemoteCursorShape::s_resize},
|
||||
{"w-resize", RemoteCursorShape::w_resize},
|
||||
{"left-side", RemoteCursorShape::w_resize},
|
||||
{"left-tee", RemoteCursorShape::w_resize},
|
||||
{"sb-left-arrow", RemoteCursorShape::w_resize},
|
||||
{"ne-resize", RemoteCursorShape::ne_resize},
|
||||
{"top-right-corner", RemoteCursorShape::ne_resize},
|
||||
{"ur-angle", RemoteCursorShape::ne_resize},
|
||||
{"nw-resize", RemoteCursorShape::nw_resize},
|
||||
{"top-left-corner", RemoteCursorShape::nw_resize},
|
||||
{"ul-angle", RemoteCursorShape::nw_resize},
|
||||
{"se-resize", RemoteCursorShape::se_resize},
|
||||
{"bottom-right-corner", RemoteCursorShape::se_resize},
|
||||
{"lr-angle", RemoteCursorShape::se_resize},
|
||||
{"sw-resize", RemoteCursorShape::sw_resize},
|
||||
{"bottom-left-corner", RemoteCursorShape::sw_resize},
|
||||
{"ll-angle", RemoteCursorShape::sw_resize},
|
||||
{"ew-resize", RemoteCursorShape::ew_resize},
|
||||
{"sb-h-double-arrow", RemoteCursorShape::ew_resize},
|
||||
{"h-double-arrow", RemoteCursorShape::ew_resize},
|
||||
{"size-hor", RemoteCursorShape::ew_resize},
|
||||
{"split-h", RemoteCursorShape::ew_resize},
|
||||
{"exchange", RemoteCursorShape::ew_resize},
|
||||
{"ns-resize", RemoteCursorShape::ns_resize},
|
||||
{"sb-v-double-arrow", RemoteCursorShape::ns_resize},
|
||||
{"v-double-arrow", RemoteCursorShape::ns_resize},
|
||||
{"double-arrow", RemoteCursorShape::ns_resize},
|
||||
{"size-ver", RemoteCursorShape::ns_resize},
|
||||
{"split-v", RemoteCursorShape::ns_resize},
|
||||
{"nesw-resize", RemoteCursorShape::nesw_resize},
|
||||
{"fd-double-arrow", RemoteCursorShape::nesw_resize},
|
||||
{"size-bdiag", RemoteCursorShape::nesw_resize},
|
||||
{"nwse-resize", RemoteCursorShape::nwse_resize},
|
||||
{"bd-double-arrow", RemoteCursorShape::nwse_resize},
|
||||
{"size-fdiag", RemoteCursorShape::nwse_resize},
|
||||
{"default", RemoteCursorShape::default_cursor},
|
||||
{"left-ptr", RemoteCursorShape::default_cursor},
|
||||
{"left-arrow", RemoteCursorShape::default_cursor},
|
||||
{"arrow", RemoteCursorShape::default_cursor},
|
||||
{"top-left-arrow", RemoteCursorShape::default_cursor},
|
||||
{"right-ptr", RemoteCursorShape::default_cursor},
|
||||
{"center-ptr", RemoteCursorShape::default_cursor},
|
||||
{"context-menu", RemoteCursorShape::default_cursor},
|
||||
};
|
||||
|
||||
std::string NormalizeCursorName(const std::string& raw_name) {
|
||||
std::string normalized;
|
||||
normalized.reserve(raw_name.size());
|
||||
for (unsigned char ch : raw_name) {
|
||||
if (ch == '_' || std::isspace(ch)) {
|
||||
normalized.push_back('-');
|
||||
} else {
|
||||
normalized.push_back(static_cast<char>(std::tolower(ch)));
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
|
||||
struct CursorCandidate {
|
||||
RemoteCursorShape shape = RemoteCursorShape::default_cursor;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t xhot = 0;
|
||||
uint32_t yhot = 0;
|
||||
std::vector<uint32_t> argb;
|
||||
};
|
||||
|
||||
uint8_t Alpha(uint32_t argb) {
|
||||
return static_cast<uint8_t>((argb >> 24U) & 0xffU);
|
||||
}
|
||||
|
||||
double BilinearAlpha(const CursorCandidate& candidate, double x, double y) {
|
||||
if (candidate.width == 0 || candidate.height == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
x = std::max(0.0, std::min(x, static_cast<double>(candidate.width - 1)));
|
||||
y = std::max(0.0, std::min(y, static_cast<double>(candidate.height - 1)));
|
||||
const uint32_t x0 = static_cast<uint32_t>(std::floor(x));
|
||||
const uint32_t y0 = static_cast<uint32_t>(std::floor(y));
|
||||
const uint32_t x1 = std::min(x0 + 1, candidate.width - 1);
|
||||
const uint32_t y1 = std::min(y0 + 1, candidate.height - 1);
|
||||
const double fx = x - x0;
|
||||
const double fy = y - y0;
|
||||
|
||||
const auto sample = [&](uint32_t px, uint32_t py) {
|
||||
return static_cast<double>(
|
||||
Alpha(candidate.argb[static_cast<size_t>(py) * candidate.width + px]));
|
||||
};
|
||||
const double top = sample(x0, y0) * (1.0 - fx) + sample(x1, y0) * fx;
|
||||
const double bottom = sample(x0, y1) * (1.0 - fx) + sample(x1, y1) * fx;
|
||||
return top * (1.0 - fy) + bottom * fy;
|
||||
}
|
||||
|
||||
double SimilarityError(const LinuxCursorImageView& image,
|
||||
const CursorCandidate& candidate) {
|
||||
if (!image.argb || image.width == 0 || image.height == 0 ||
|
||||
candidate.argb.empty()) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
double alpha_error = 0.0;
|
||||
for (uint32_t y = 0; y < image.height; ++y) {
|
||||
const double source_y =
|
||||
(static_cast<double>(y) + 0.5) * candidate.height / image.height - 0.5;
|
||||
for (uint32_t x = 0; x < image.width; ++x) {
|
||||
const double source_x = (static_cast<double>(x) + 0.5) *
|
||||
candidate.width / image.width -
|
||||
0.5;
|
||||
const double expected = BilinearAlpha(candidate, source_x, source_y);
|
||||
const double actual = static_cast<double>(
|
||||
Alpha(image.argb[static_cast<size_t>(y) * image.width + x]));
|
||||
alpha_error += std::abs(actual - expected) / 255.0;
|
||||
}
|
||||
}
|
||||
alpha_error /= static_cast<double>(image.width) * image.height;
|
||||
|
||||
const double observed_hot_x =
|
||||
static_cast<double>(image.xhot) / std::max(1u, image.width);
|
||||
const double observed_hot_y =
|
||||
static_cast<double>(image.yhot) / std::max(1u, image.height);
|
||||
const double candidate_hot_x =
|
||||
static_cast<double>(candidate.xhot) / std::max(1u, candidate.width);
|
||||
const double candidate_hot_y =
|
||||
static_cast<double>(candidate.yhot) / std::max(1u, candidate.height);
|
||||
const double hotspot_error =
|
||||
std::abs(observed_hot_x - candidate_hot_x) +
|
||||
std::abs(observed_hot_y - candidate_hot_y);
|
||||
|
||||
const double observed_aspect =
|
||||
static_cast<double>(image.width) / image.height;
|
||||
const double candidate_aspect =
|
||||
static_cast<double>(candidate.width) / candidate.height;
|
||||
const double aspect_error =
|
||||
std::abs(std::log(observed_aspect / candidate_aspect));
|
||||
|
||||
return alpha_error + hotspot_error * 0.12 + aspect_error * 0.08;
|
||||
}
|
||||
|
||||
bool SameImage(const LinuxCursorImageView& image,
|
||||
const CursorCandidate& candidate) {
|
||||
if (image.width != candidate.width || image.height != candidate.height ||
|
||||
image.xhot != candidate.xhot || image.yhot != candidate.yhot) {
|
||||
return false;
|
||||
}
|
||||
const size_t pixel_count =
|
||||
static_cast<size_t>(image.width) * static_cast<size_t>(image.height);
|
||||
return std::equal(image.argb, image.argb + pixel_count,
|
||||
candidate.argb.begin());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
const LinuxCursorAlias* LinuxCursorAliases(size_t* count) {
|
||||
if (count) {
|
||||
*count = sizeof(kCursorAliases) / sizeof(kCursorAliases[0]);
|
||||
}
|
||||
return kCursorAliases;
|
||||
}
|
||||
|
||||
RemoteCursorShape ShapeFromLinuxCursorName(const std::string& raw_name) {
|
||||
const std::string name = NormalizeCursorName(raw_name);
|
||||
for (const auto& alias : kCursorAliases) {
|
||||
if (name == alias.name) {
|
||||
return alias.shape;
|
||||
}
|
||||
}
|
||||
|
||||
// Some toolkits prefix a theme or toolkit name to the standard cursor name.
|
||||
// Restrict the fallback to separators so names such as "pointer-progress"
|
||||
// still prefer their exact semantic alias above.
|
||||
for (const auto& alias : kCursorAliases) {
|
||||
const std::string suffix = std::string("-") + alias.name;
|
||||
if (name.size() > suffix.size() &&
|
||||
name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0) {
|
||||
return alias.shape;
|
||||
}
|
||||
}
|
||||
return RemoteCursorShape::default_cursor;
|
||||
}
|
||||
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
|
||||
struct LinuxCursorThemeMatcher::Impl {
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
std::string theme;
|
||||
int default_size = 24;
|
||||
std::unordered_map<int, std::vector<CursorCandidate>> candidates_by_size;
|
||||
|
||||
Impl() {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
if (const char* active_theme = XcursorGetTheme(display)) {
|
||||
theme = active_theme;
|
||||
}
|
||||
const int active_size = XcursorGetDefaultSize(display);
|
||||
if (active_size > 0) {
|
||||
default_size = active_size;
|
||||
}
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
if (display) {
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<CursorCandidate>& Candidates(int requested_size) {
|
||||
requested_size = std::max(1, requested_size);
|
||||
auto existing = candidates_by_size.find(requested_size);
|
||||
if (existing != candidates_by_size.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
std::vector<CursorCandidate> candidates;
|
||||
const char* theme_name = theme.empty() ? nullptr : theme.c_str();
|
||||
for (const auto& alias : kCursorAliases) {
|
||||
XcursorImages* images =
|
||||
XcursorLibraryLoadImages(alias.name, theme_name, requested_size);
|
||||
if (!images) {
|
||||
continue;
|
||||
}
|
||||
for (int index = 0; index < images->nimage; ++index) {
|
||||
const XcursorImage* image = images->images[index];
|
||||
if (!image || !image->pixels || image->width == 0 ||
|
||||
image->height == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CursorCandidate candidate;
|
||||
candidate.shape = alias.shape;
|
||||
candidate.width = image->width;
|
||||
candidate.height = image->height;
|
||||
candidate.xhot = image->xhot;
|
||||
candidate.yhot = image->yhot;
|
||||
const size_t pixel_count = static_cast<size_t>(image->width) *
|
||||
static_cast<size_t>(image->height);
|
||||
candidate.argb.assign(image->pixels, image->pixels + pixel_count);
|
||||
|
||||
const bool duplicate =
|
||||
std::any_of(candidates.begin(), candidates.end(),
|
||||
[&](const CursorCandidate& current) {
|
||||
return current.shape == candidate.shape &&
|
||||
current.width == candidate.width &&
|
||||
current.height == candidate.height &&
|
||||
current.xhot == candidate.xhot &&
|
||||
current.yhot == candidate.yhot &&
|
||||
current.argb == candidate.argb;
|
||||
});
|
||||
if (!duplicate) {
|
||||
candidates.push_back(std::move(candidate));
|
||||
}
|
||||
}
|
||||
XcursorImagesDestroy(images);
|
||||
}
|
||||
|
||||
return candidates_by_size
|
||||
.emplace(requested_size, std::move(candidates))
|
||||
.first->second;
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
struct LinuxCursorThemeMatcher::Impl {};
|
||||
|
||||
#endif
|
||||
|
||||
LinuxCursorThemeMatcher::LinuxCursorThemeMatcher()
|
||||
: impl_(std::make_unique<Impl>()) {}
|
||||
|
||||
LinuxCursorThemeMatcher::~LinuxCursorThemeMatcher() = default;
|
||||
|
||||
RemoteCursorShape LinuxCursorThemeMatcher::Match(
|
||||
const LinuxCursorImageView& image) {
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
if (!impl_ || !image.argb || image.width == 0 || image.height == 0) {
|
||||
return RemoteCursorShape::default_cursor;
|
||||
}
|
||||
|
||||
const int requested_size =
|
||||
static_cast<int>(std::max(image.width, image.height));
|
||||
const auto& candidates = impl_->Candidates(requested_size);
|
||||
for (const auto& candidate : candidates) {
|
||||
if (SameImage(image, candidate)) {
|
||||
return candidate.shape;
|
||||
}
|
||||
}
|
||||
|
||||
double best_error = std::numeric_limits<double>::infinity();
|
||||
RemoteCursorShape best_shape = RemoteCursorShape::default_cursor;
|
||||
for (const auto& candidate : candidates) {
|
||||
const double error = SimilarityError(image, candidate);
|
||||
if (error < best_error) {
|
||||
best_error = error;
|
||||
best_shape = candidate.shape;
|
||||
}
|
||||
}
|
||||
|
||||
// Scaled theme cursors normally remain well below this threshold, while an
|
||||
// unrelated custom application cursor should fall back to the default.
|
||||
return best_error <= 0.20 ? best_shape
|
||||
: RemoteCursorShape::default_cursor;
|
||||
#else
|
||||
(void)image;
|
||||
return RemoteCursorShape::default_cursor;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef CROSSDESK_COMMON_LINUX_CURSOR_SHAPE_H_
|
||||
#define CROSSDESK_COMMON_LINUX_CURSOR_SHAPE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <remote_cursor_shape.h>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
struct LinuxCursorAlias {
|
||||
const char* name;
|
||||
RemoteCursorShape shape;
|
||||
};
|
||||
|
||||
const LinuxCursorAlias* LinuxCursorAliases(size_t* count);
|
||||
RemoteCursorShape ShapeFromLinuxCursorName(const std::string& name);
|
||||
|
||||
struct LinuxCursorImageView {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t xhot = 0;
|
||||
uint32_t yhot = 0;
|
||||
const uint32_t* argb = nullptr;
|
||||
};
|
||||
|
||||
// Matches a compositor-provided cursor bitmap against the active Xcursor
|
||||
// theme. This is needed on Wayland, where XFixes only observes XWayland
|
||||
// cursors and cannot report cursors owned by native Wayland surfaces.
|
||||
class LinuxCursorThemeMatcher {
|
||||
public:
|
||||
LinuxCursorThemeMatcher();
|
||||
~LinuxCursorThemeMatcher();
|
||||
|
||||
LinuxCursorThemeMatcher(const LinuxCursorThemeMatcher&) = delete;
|
||||
LinuxCursorThemeMatcher& operator=(const LinuxCursorThemeMatcher&) = delete;
|
||||
|
||||
RemoteCursorShape Match(const LinuxCursorImageView& image);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif // CROSSDESK_COMMON_LINUX_CURSOR_SHAPE_H_
|
||||
@@ -0,0 +1,279 @@
|
||||
#include "wayland_portal_shared.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
|
||||
#include <dbus/dbus.h>
|
||||
#endif
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex& SharedSessionMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
SharedWaylandPortalSessionInfo& SharedSessionInfo() {
|
||||
static SharedWaylandPortalSessionInfo info;
|
||||
return info;
|
||||
}
|
||||
|
||||
bool& SharedSessionActive() {
|
||||
static bool active = false;
|
||||
return active;
|
||||
}
|
||||
|
||||
int& SharedSessionRefs() {
|
||||
static int refs = 0;
|
||||
return refs;
|
||||
}
|
||||
|
||||
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
|
||||
constexpr const char* kPortalBusName = "org.freedesktop.portal.Desktop";
|
||||
constexpr const char* kPortalSessionInterface =
|
||||
"org.freedesktop.portal.Session";
|
||||
constexpr int kPortalCloseWaitMs = 100;
|
||||
|
||||
void LogCloseDbusError(const char* action, DBusError* error) {
|
||||
if (error && dbus_error_is_set(error)) {
|
||||
LOG_ERROR("{} failed: {} ({})", action,
|
||||
error->message ? error->message : "unknown",
|
||||
error->name ? error->name : "unknown");
|
||||
} else {
|
||||
LOG_ERROR("{} failed", action);
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionClosedState {
|
||||
std::string session_handle;
|
||||
bool received = false;
|
||||
};
|
||||
|
||||
DBusHandlerResult HandleSessionClosedSignal(DBusConnection* connection,
|
||||
DBusMessage* message,
|
||||
void* user_data) {
|
||||
(void)connection;
|
||||
auto* state = static_cast<SessionClosedState*>(user_data);
|
||||
if (!state || !message) {
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
if (!dbus_message_is_signal(message, kPortalSessionInterface, "Closed")) {
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
const char* path = dbus_message_get_path(message);
|
||||
if (!path || state->session_handle != path) {
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
state->received = true;
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
|
||||
bool BeginSessionClosedWatch(DBusConnection* connection,
|
||||
const std::string& session_handle,
|
||||
SessionClosedState* state,
|
||||
std::string* match_rule_out) {
|
||||
if (!connection || session_handle.empty() || !state || !match_rule_out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state->session_handle = session_handle;
|
||||
state->received = false;
|
||||
DBusError error;
|
||||
dbus_error_init(&error);
|
||||
const std::string match_rule =
|
||||
"type='signal',interface='" + std::string(kPortalSessionInterface) +
|
||||
"',member='Closed',path='" + session_handle + "'";
|
||||
dbus_bus_add_match(connection, match_rule.c_str(), &error);
|
||||
if (dbus_error_is_set(&error)) {
|
||||
LogCloseDbusError("dbus_bus_add_match(Session.Closed)", &error);
|
||||
dbus_error_free(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
dbus_connection_add_filter(connection, HandleSessionClosedSignal, state,
|
||||
nullptr);
|
||||
*match_rule_out = match_rule;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EndSessionClosedWatch(DBusConnection* connection, SessionClosedState* state,
|
||||
const std::string& match_rule) {
|
||||
if (!connection || !state || match_rule.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dbus_connection_remove_filter(connection, HandleSessionClosedSignal, state);
|
||||
|
||||
DBusError remove_error;
|
||||
dbus_error_init(&remove_error);
|
||||
dbus_bus_remove_match(connection, match_rule.c_str(), &remove_error);
|
||||
if (dbus_error_is_set(&remove_error)) {
|
||||
dbus_error_free(&remove_error);
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForSessionClosed(DBusConnection* connection, SessionClosedState* state,
|
||||
int timeout_ms = kPortalCloseWaitMs) {
|
||||
if (!connection || !state) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto deadline =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
|
||||
while (!state->received && std::chrono::steady_clock::now() < deadline) {
|
||||
dbus_connection_read_write(connection, 100);
|
||||
while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
bool PublishSharedWaylandPortalSession(
|
||||
const SharedWaylandPortalSessionInfo& info) {
|
||||
if (!info.connection || info.session_handle.empty() || info.stream_id == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(SharedSessionMutex());
|
||||
if (SharedSessionActive()) {
|
||||
const auto& active_info = SharedSessionInfo();
|
||||
if (active_info.session_handle != info.session_handle &&
|
||||
SharedSessionRefs() > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool same_session =
|
||||
SharedSessionActive() &&
|
||||
SharedSessionInfo().session_handle == info.session_handle;
|
||||
SharedSessionInfo() = info;
|
||||
SharedSessionActive() = true;
|
||||
if (!same_session || SharedSessionRefs() <= 0) {
|
||||
SharedSessionRefs() = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AcquireSharedWaylandPortalSession(bool require_pointer,
|
||||
SharedWaylandPortalSessionInfo* out) {
|
||||
if (!out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(SharedSessionMutex());
|
||||
if (!SharedSessionActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& info = SharedSessionInfo();
|
||||
if (require_pointer && !info.pointer_granted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++SharedSessionRefs();
|
||||
*out = info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReleaseSharedWaylandPortalSession(DBusConnection** connection_out,
|
||||
std::string* session_handle_out) {
|
||||
if (connection_out) {
|
||||
*connection_out = nullptr;
|
||||
}
|
||||
if (session_handle_out) {
|
||||
session_handle_out->clear();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(SharedSessionMutex());
|
||||
if (!SharedSessionActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SharedSessionRefs() > 0) {
|
||||
--SharedSessionRefs();
|
||||
}
|
||||
|
||||
if (SharedSessionRefs() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (connection_out) {
|
||||
*connection_out = SharedSessionInfo().connection;
|
||||
}
|
||||
if (session_handle_out) {
|
||||
*session_handle_out = SharedSessionInfo().session_handle;
|
||||
}
|
||||
|
||||
SharedSessionInfo() = SharedWaylandPortalSessionInfo{};
|
||||
SharedSessionActive() = false;
|
||||
SharedSessionRefs() = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CloseWaylandPortalSessionAndConnection(DBusConnection* connection,
|
||||
const std::string& session_handle,
|
||||
const char* close_action) {
|
||||
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session_handle.empty()) {
|
||||
SessionClosedState close_state;
|
||||
std::string close_match_rule;
|
||||
const bool watching_closed = BeginSessionClosedWatch(
|
||||
connection, session_handle, &close_state, &close_match_rule);
|
||||
|
||||
DBusMessage* message = dbus_message_new_method_call(
|
||||
kPortalBusName, session_handle.c_str(), kPortalSessionInterface,
|
||||
"Close");
|
||||
if (message) {
|
||||
DBusError error;
|
||||
dbus_error_init(&error);
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(
|
||||
connection, message, 1000, &error);
|
||||
if (!reply && dbus_error_is_set(&error)) {
|
||||
LogCloseDbusError(close_action, &error);
|
||||
dbus_error_free(&error);
|
||||
}
|
||||
if (reply) {
|
||||
dbus_message_unref(reply);
|
||||
}
|
||||
dbus_message_unref(message);
|
||||
}
|
||||
|
||||
if (watching_closed) {
|
||||
WaitForSessionClosed(connection, &close_state);
|
||||
if (!close_state.received) {
|
||||
LOG_WARN("Timed out waiting for portal session to close: {}",
|
||||
session_handle);
|
||||
LOG_WARN("Forcing local teardown without waiting for Session.Closed: {}",
|
||||
session_handle);
|
||||
EndSessionClosedWatch(connection, &close_state, close_match_rule);
|
||||
} else {
|
||||
EndSessionClosedWatch(connection, &close_state, close_match_rule);
|
||||
LOG_INFO("Portal session closed: {}", session_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbus_connection_close(connection);
|
||||
dbus_connection_unref(connection);
|
||||
#else
|
||||
(void)connection;
|
||||
(void)session_handle;
|
||||
(void)close_action;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Shared Wayland portal session state used by the Linux Wayland capturer and
|
||||
* mouse controller so they can reuse one RemoteDesktop session.
|
||||
*/
|
||||
|
||||
#ifndef _WAYLAND_PORTAL_SHARED_H_
|
||||
#define _WAYLAND_PORTAL_SHARED_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
struct DBusConnection;
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
struct SharedWaylandPortalSessionInfo {
|
||||
DBusConnection* connection = nullptr;
|
||||
std::string session_handle;
|
||||
uint32_t stream_id = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool pointer_granted = false;
|
||||
};
|
||||
|
||||
bool PublishSharedWaylandPortalSession(
|
||||
const SharedWaylandPortalSessionInfo& info);
|
||||
bool AcquireSharedWaylandPortalSession(bool require_pointer,
|
||||
SharedWaylandPortalSessionInfo* out);
|
||||
bool ReleaseSharedWaylandPortalSession(DBusConnection** connection_out,
|
||||
std::string* session_handle_out);
|
||||
void CloseWaylandPortalSessionAndConnection(DBusConnection* connection,
|
||||
const std::string& session_handle,
|
||||
const char* close_action);
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user