mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-24 13:48:51 +08:00
[fix] scope sleep inhibition to active screen sharing, refs #123
This commit is contained in:
@@ -1225,6 +1225,9 @@ bool GuiApplication::InitializeSDL() {
|
||||
"windows while retaining Wayland portal capture and input");
|
||||
}
|
||||
#endif
|
||||
// SDL disables the screensaver by default. A background remote desktop app
|
||||
// should leave automatic display sleep under the user's system settings.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
|
||||
LOG_ERROR("SDL initialization failed: {}", SDL_GetError());
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
#include "features/devices/screen_sharing_power.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif defined(__linux__) && !defined(__OHOS__)
|
||||
#include <dbus/dbus.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <SDL3/SDL.h>
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
namespace {
|
||||
|
||||
constexpr const char* kApplication = "CrossDesk";
|
||||
constexpr const char* kReason = "Sharing the desktop with a remote controller";
|
||||
constexpr int kDbusTimeoutMs = 1000;
|
||||
|
||||
struct ConnectionCloser {
|
||||
void operator()(DBusConnection* connection) const {
|
||||
dbus_connection_close(connection);
|
||||
dbus_connection_unref(connection);
|
||||
}
|
||||
};
|
||||
using BusConnection = std::unique_ptr<DBusConnection, ConnectionCloser>;
|
||||
using BusMessage = std::unique_ptr<DBusMessage, decltype(&dbus_message_unref)>;
|
||||
|
||||
BusConnection OpenBus(DBusBusType type) {
|
||||
DBusError error;
|
||||
dbus_error_init(&error);
|
||||
BusConnection connection(dbus_bus_get_private(type, &error));
|
||||
if (connection) {
|
||||
dbus_connection_set_exit_on_disconnect(connection.get(), false);
|
||||
} else {
|
||||
LOG_WARN("Cannot connect to power management bus: {}",
|
||||
error.message ? error.message : "unknown error");
|
||||
}
|
||||
dbus_error_free(&error);
|
||||
return connection;
|
||||
}
|
||||
|
||||
BusMessage Call(DBusConnection* connection, DBusMessage* message) {
|
||||
return BusMessage(dbus_connection_send_with_reply_and_block(
|
||||
connection, message, kDbusTimeoutMs, nullptr),
|
||||
dbus_message_unref);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
struct ScreenSharingPower::Impl {
|
||||
#ifdef _WIN32
|
||||
HANDLE request = INVALID_HANDLE_VALUE;
|
||||
bool display = false;
|
||||
bool system = false;
|
||||
|
||||
void Start() {
|
||||
wchar_t reason_text[] = L"CrossDesk is sharing the desktop";
|
||||
REASON_CONTEXT reason{};
|
||||
reason.Version = POWER_REQUEST_CONTEXT_VERSION;
|
||||
reason.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
|
||||
reason.Reason.SimpleReasonString = reason_text;
|
||||
request = PowerCreateRequest(&reason);
|
||||
if (request == INVALID_HANDLE_VALUE) {
|
||||
LOG_WARN("Cannot create screen sharing power request: {}", GetLastError());
|
||||
return;
|
||||
}
|
||||
// Handle-based requests also work in the unattended host, which has no
|
||||
// SDL video subsystem, and do not depend on the calling thread's lifetime.
|
||||
system = PowerSetRequest(request, PowerRequestSystemRequired) != 0;
|
||||
if (!system)
|
||||
LOG_WARN("Cannot prevent system idle sleep: {}", GetLastError());
|
||||
display = PowerSetRequest(request, PowerRequestDisplayRequired) != 0;
|
||||
if (!display)
|
||||
LOG_WARN("Cannot prevent display idle sleep: {}", GetLastError());
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (request == INVALID_HANDLE_VALUE) return;
|
||||
if (display) PowerClearRequest(request, PowerRequestDisplayRequired);
|
||||
if (system) PowerClearRequest(request, PowerRequestSystemRequired);
|
||||
CloseHandle(request);
|
||||
request = INVALID_HANDLE_VALUE;
|
||||
display = system = false;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
IOPMAssertionID display = kIOPMNullAssertionID;
|
||||
IOPMAssertionID system = kIOPMNullAssertionID;
|
||||
|
||||
void Start() {
|
||||
const auto reason = CFSTR("CrossDesk is sharing the desktop");
|
||||
auto result = IOPMAssertionCreateWithName(
|
||||
kIOPMAssertionTypePreventUserIdleSystemSleep, kIOPMAssertionLevelOn,
|
||||
reason, &system);
|
||||
if (result != kIOReturnSuccess)
|
||||
LOG_WARN("Cannot prevent system idle sleep: {}", result);
|
||||
result = IOPMAssertionCreateWithName(
|
||||
kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn,
|
||||
reason, &display);
|
||||
if (result != kIOReturnSuccess)
|
||||
LOG_WARN("Cannot prevent display idle sleep: {}", result);
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (display != kIOPMNullAssertionID) IOPMAssertionRelease(display);
|
||||
if (system != kIOPMNullAssertionID) IOPMAssertionRelease(system);
|
||||
display = system = kIOPMNullAssertionID;
|
||||
}
|
||||
#elif defined(__linux__) && !defined(__OHOS__)
|
||||
BusConnection session;
|
||||
dbus_uint32_t cookie = 0;
|
||||
bool desktop_inhibited = false;
|
||||
bool gnome = false;
|
||||
int sleep_fd = -1;
|
||||
|
||||
bool InhibitDesktop(bool use_gnome) {
|
||||
const char* service = use_gnome ? "org.gnome.SessionManager"
|
||||
: "org.freedesktop.ScreenSaver";
|
||||
const char* path = use_gnome ? "/org/gnome/SessionManager"
|
||||
: "/org/freedesktop/ScreenSaver";
|
||||
BusMessage message(dbus_message_new_method_call(service, path, service,
|
||||
"Inhibit"),
|
||||
dbus_message_unref);
|
||||
if (!message) return false;
|
||||
dbus_bool_t appended;
|
||||
if (use_gnome) {
|
||||
const dbus_uint32_t window = 0;
|
||||
const dbus_uint32_t flags = 4 | 8; // Suspend and session idle.
|
||||
appended = dbus_message_append_args(
|
||||
message.get(), DBUS_TYPE_STRING, &kApplication, DBUS_TYPE_UINT32,
|
||||
&window, DBUS_TYPE_STRING, &kReason, DBUS_TYPE_UINT32, &flags,
|
||||
DBUS_TYPE_INVALID);
|
||||
} else {
|
||||
appended = dbus_message_append_args(
|
||||
message.get(), DBUS_TYPE_STRING, &kApplication, DBUS_TYPE_STRING,
|
||||
&kReason, DBUS_TYPE_INVALID);
|
||||
}
|
||||
if (!appended) return false;
|
||||
auto reply = Call(session.get(), message.get());
|
||||
if (!reply || !dbus_message_get_args(reply.get(), nullptr, DBUS_TYPE_UINT32,
|
||||
&cookie, DBUS_TYPE_INVALID))
|
||||
return false;
|
||||
gnome = use_gnome;
|
||||
desktop_inhibited = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Start() {
|
||||
if (!dbus_threads_init_default()) {
|
||||
LOG_WARN("Cannot initialize D-Bus for screen sharing power management");
|
||||
return;
|
||||
}
|
||||
// These session APIs work without an SDL window, including native Wayland
|
||||
// and a Slint window hidden in the tray. GNOME's API inhibits both kinds of
|
||||
// idle sleep; other desktops use ScreenSaver plus logind below.
|
||||
session = OpenBus(DBUS_BUS_SESSION);
|
||||
if (session && InhibitDesktop(true)) return;
|
||||
if (!session || !InhibitDesktop(false))
|
||||
LOG_WARN("Cannot inhibit desktop idle while sharing the screen");
|
||||
|
||||
auto system_bus = OpenBus(DBUS_BUS_SYSTEM);
|
||||
if (!system_bus) return;
|
||||
BusMessage message(dbus_message_new_method_call(
|
||||
"org.freedesktop.login1", "/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager", "Inhibit"),
|
||||
dbus_message_unref);
|
||||
const char* what = "sleep:idle";
|
||||
const char* mode = "block";
|
||||
if (!message || !dbus_message_append_args(
|
||||
message.get(), DBUS_TYPE_STRING, &what,
|
||||
DBUS_TYPE_STRING, &kApplication, DBUS_TYPE_STRING,
|
||||
&kReason, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID)) {
|
||||
LOG_WARN("Cannot create system sleep inhibition request");
|
||||
return;
|
||||
}
|
||||
auto reply = Call(system_bus.get(), message.get());
|
||||
// get_args duplicates the returned fd with CLOEXEC. Holding this fd keeps
|
||||
// the inhibitor alive independently of the reply or D-Bus connection.
|
||||
if (!reply || !dbus_message_get_args(reply.get(), nullptr, DBUS_TYPE_UNIX_FD,
|
||||
&sleep_fd, DBUS_TYPE_INVALID))
|
||||
LOG_WARN("Cannot inhibit system sleep while sharing the screen");
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (sleep_fd >= 0) {
|
||||
close(sleep_fd);
|
||||
sleep_fd = -1;
|
||||
}
|
||||
if (session && desktop_inhibited &&
|
||||
dbus_connection_get_is_connected(session.get())) {
|
||||
const char* service = gnome ? "org.gnome.SessionManager"
|
||||
: "org.freedesktop.ScreenSaver";
|
||||
const char* path = gnome ? "/org/gnome/SessionManager"
|
||||
: "/org/freedesktop/ScreenSaver";
|
||||
BusMessage message(dbus_message_new_method_call(
|
||||
service, path, service,
|
||||
gnome ? "Uninhibit" : "UnInhibit"),
|
||||
dbus_message_unref);
|
||||
if (message && dbus_message_append_args(
|
||||
message.get(), DBUS_TYPE_UINT32, &cookie,
|
||||
DBUS_TYPE_INVALID)) {
|
||||
dbus_message_set_no_reply(message.get(), true);
|
||||
dbus_connection_send(session.get(), message.get(), nullptr);
|
||||
dbus_connection_flush(session.get());
|
||||
}
|
||||
}
|
||||
// A private connection also revokes the desktop inhibitor if UnInhibit
|
||||
// could not be sent. Nothing is retained by unrelated D-Bus clients.
|
||||
session.reset();
|
||||
desktop_inhibited = false;
|
||||
gnome = false;
|
||||
cookie = 0;
|
||||
}
|
||||
#else
|
||||
void Start() {
|
||||
if (!SDL_DisableScreenSaver())
|
||||
LOG_WARN("Cannot prevent display idle sleep: {}", SDL_GetError());
|
||||
}
|
||||
void Stop() { SDL_EnableScreenSaver(); }
|
||||
#endif
|
||||
};
|
||||
|
||||
ScreenSharingPower::ScreenSharingPower() : impl_(std::make_unique<Impl>()) {}
|
||||
|
||||
ScreenSharingPower::~ScreenSharingPower() { SetSharing(false); }
|
||||
|
||||
void ScreenSharingPower::SetSharing(bool sharing) {
|
||||
if (sharing_ == sharing) return;
|
||||
sharing_ = sharing;
|
||||
if (sharing) {
|
||||
impl_->Start();
|
||||
LOG_INFO("Requested display and system idle-sleep inhibition for screen sharing");
|
||||
} else {
|
||||
impl_->Stop();
|
||||
LOG_INFO("Released screen sharing power inhibitors");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-10-24
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_SHARING_POWER_H_
|
||||
#define _SCREEN_SHARING_POWER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
// Owns temporary display/system idle-sleep inhibitors for the shared capture
|
||||
// source. Call on the UI thread; repeated updates do not acquire extra requests.
|
||||
class ScreenSharingPower {
|
||||
public:
|
||||
ScreenSharingPower();
|
||||
~ScreenSharingPower();
|
||||
ScreenSharingPower(const ScreenSharingPower&) = delete;
|
||||
ScreenSharingPower& operator=(const ScreenSharingPower&) = delete;
|
||||
|
||||
void SetSharing(bool sharing);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
bool sharing_ = false;
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -298,6 +298,21 @@ bool SessionDeviceManager::ShouldCaptureCursor() const {
|
||||
});
|
||||
}
|
||||
|
||||
bool SessionDeviceManager::HasConnectedController() const {
|
||||
std::shared_lock lock(owner_.connection_status_mutex_);
|
||||
return std::any_of(owner_.connection_status_.begin(),
|
||||
owner_.connection_status_.end(), [](const auto& entry) {
|
||||
return entry.second == ConnectionStatus::Connected;
|
||||
});
|
||||
}
|
||||
|
||||
void SessionDeviceManager::UpdateScreenSharingPower() {
|
||||
// Wayland keeps capture warm after the last disconnect. Only live viewers
|
||||
// justify keeping the host awake; an outgoing viewing session does not.
|
||||
screen_sharing_power_.SetSharing(owner_.screen_capturer_is_started_ &&
|
||||
HasConnectedController());
|
||||
}
|
||||
|
||||
int SessionDeviceManager::StartScreenCapturer() {
|
||||
#ifdef __APPLE__
|
||||
if (!owner_.EnsureMacScreenRecordingPermission()) {
|
||||
@@ -317,8 +332,10 @@ int SessionDeviceManager::StartScreenCapturer() {
|
||||
cursor_capture_dirty_.store(false, std::memory_order_relaxed);
|
||||
const bool show_cursor = ShouldCaptureCursor();
|
||||
LOG_INFO("Start screen capturer, show cursor: {}", show_cursor);
|
||||
screen_sharing_power_.SetSharing(HasConnectedController());
|
||||
const int ret = screen_capturer_->Start(show_cursor);
|
||||
if (ret != 0) {
|
||||
screen_sharing_power_.SetSharing(false);
|
||||
LOG_ERROR("Start screen capturer failed: {}", ret);
|
||||
}
|
||||
return ret;
|
||||
@@ -329,6 +346,8 @@ int SessionDeviceManager::StopScreenCapturer() {
|
||||
LOG_INFO("Stop screen capturer");
|
||||
screen_capturer_->Stop();
|
||||
}
|
||||
owner_.screen_capturer_is_started_ = false;
|
||||
screen_sharing_power_.SetSharing(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -647,6 +666,8 @@ void SessionDeviceManager::PushAudio(const std::string& remote_id,
|
||||
}
|
||||
|
||||
void SessionDeviceManager::UpdateInteractions() {
|
||||
// Release even when pending remote-input cleanup makes this tick return early.
|
||||
UpdateScreenSharingPower();
|
||||
#ifdef _WIN32
|
||||
if (owner_.screen_capturer_is_started_ &&
|
||||
cursor_capture_dirty_.load(std::memory_order_relaxed)) {
|
||||
@@ -680,6 +701,8 @@ void SessionDeviceManager::UpdateInteractions() {
|
||||
owner_.screen_capturer_is_started_ = false;
|
||||
}
|
||||
|
||||
UpdateScreenSharingPower();
|
||||
|
||||
if (owner_.start_mouse_controller_ && !owner_.mouse_controller_is_started_) {
|
||||
if (StartMouseController() == 0) {
|
||||
owner_.mouse_controller_is_started_ = true;
|
||||
@@ -823,6 +846,8 @@ std::vector<HostDisplay> SessionDeviceManager::host_display_list() const {
|
||||
}
|
||||
|
||||
void SessionDeviceManager::DestroyDevices() {
|
||||
owner_.screen_capturer_is_started_ = false;
|
||||
screen_sharing_power_.SetSharing(false);
|
||||
speaker_capture_.Shutdown();
|
||||
StopMouseController(true);
|
||||
if (screen_capturer_) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "display_info.h"
|
||||
#include "features/input/captured_keyboard_queue.h"
|
||||
#include "features/input/remote_mouse_buttons.h"
|
||||
#include "screen_sharing_power.h"
|
||||
#include "speaker_capture_controller.h"
|
||||
|
||||
struct SDL_AudioStream;
|
||||
@@ -105,6 +106,8 @@ private:
|
||||
bool ShouldSendCapturedFrame(std::chrono::steady_clock::time_point now,
|
||||
int fps);
|
||||
bool ShouldCaptureCursor() const;
|
||||
bool HasConnectedController() const;
|
||||
void UpdateScreenSharingPower();
|
||||
bool SyncDisplayInfo();
|
||||
void SetDisplayInfoList(std::vector<DisplayInfo> displays);
|
||||
void RecordCaptureCadence(std::chrono::steady_clock::time_point now, int fps,
|
||||
@@ -115,6 +118,7 @@ private:
|
||||
std::unordered_map<std::string, SDL_AudioStream*> output_streams_;
|
||||
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
|
||||
ScreenCapturer *screen_capturer_ = nullptr;
|
||||
ScreenSharingPower screen_sharing_power_;
|
||||
std::atomic<bool> cursor_capture_dirty_{true};
|
||||
SpeakerCaptureController speaker_capture_;
|
||||
DeviceControllerFactory *device_controller_factory_ = nullptr;
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace crossdesk {
|
||||
|
||||
int RunSlintRendererProbe() {
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||
// Renderer probing should not inhibit automatic display sleep either.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,16 @@ function setup_platform_settings()
|
||||
"/usr/local/include/freetype2"
|
||||
}, {system = true})
|
||||
|
||||
-- Screen sharing power inhibitors also use D-Bus in X11-only builds.
|
||||
add_links("dbus-1")
|
||||
add_existing_include_dirs({
|
||||
"/usr/include/dbus-1.0",
|
||||
"/usr/local/include/dbus-1.0"
|
||||
}, {system = true})
|
||||
for _, include_dir in ipairs(collect_dbus_arch_include_dirs()) do
|
||||
add_includedirs(include_dir, {system = true})
|
||||
end
|
||||
|
||||
if is_config("USE_DRM", true) then
|
||||
add_links("drm")
|
||||
add_defines("CROSSDESK_HAS_DRM=1")
|
||||
@@ -69,11 +79,8 @@ function setup_platform_settings()
|
||||
end
|
||||
|
||||
if is_config("USE_WAYLAND", true) then
|
||||
add_links("dbus-1")
|
||||
add_defines("CROSSDESK_HAS_WAYLAND_CAPTURER=1")
|
||||
add_existing_include_dirs({
|
||||
"/usr/include/dbus-1.0",
|
||||
"/usr/local/include/dbus-1.0",
|
||||
"/usr/include/pipewire-0.3",
|
||||
"/usr/local/include/pipewire-0.3",
|
||||
"/usr/include/pipewire",
|
||||
@@ -85,9 +92,6 @@ function setup_platform_settings()
|
||||
"/usr/local/include/spa",
|
||||
"/opt/crossdesk-pipewire-sdk/include/spa-0.2"
|
||||
}, {system = true})
|
||||
for _, include_dir in ipairs(collect_dbus_arch_include_dirs()) do
|
||||
add_includedirs(include_dir, {system = true})
|
||||
end
|
||||
else
|
||||
add_defines("CROSSDESK_HAS_WAYLAND_CAPTURER=0")
|
||||
end
|
||||
@@ -98,7 +102,7 @@ function setup_platform_settings()
|
||||
add_ldflags("-Wl,-ld_classic")
|
||||
end
|
||||
add_cxflags("-Wno-unused-variable")
|
||||
add_frameworks("Cocoa", "CoreGraphics", "Metal", "QuartzCore",
|
||||
add_frameworks("Cocoa", "CoreGraphics", "Metal", "QuartzCore", "IOKit",
|
||||
"IOSurface",
|
||||
"ScreenCaptureKit", "AVFoundation", "CoreMedia", "CoreVideo",
|
||||
"CoreAudio", "AudioToolbox")
|
||||
|
||||
Reference in New Issue
Block a user