mirror of
https://github.com/rustdesk/hbb_common.git
synced 2026-10-11 19:23:59 +00:00
Merge pull request #598 from rustdesk/move-rustdesk-only-modules
move the modules only rustdesk uses out to the base crate
This commit is contained in:
-19
@@ -9,9 +9,6 @@ edition = "2018"
|
||||
[features]
|
||||
default = []
|
||||
webrtc = ["dep:webrtc"]
|
||||
# The isolated Wayland socket-probe fallback (src/platform/linux/wayland_probe.rs). Off by default
|
||||
# so the base Wayland enumeration is untouched; the DRM login-screen build (scrap/drm) turns it on.
|
||||
wayland_probe = []
|
||||
|
||||
[dependencies]
|
||||
# new flexi_logger failed on rustc 1.75
|
||||
@@ -35,12 +32,10 @@ serde_json = "1.0"
|
||||
lazy_static = "1.5"
|
||||
confy = { git = "https://github.com/rustdesk-org/confy" }
|
||||
dirs-next = "2.0"
|
||||
filetime = "0.2"
|
||||
sodiumoxide = "0.2"
|
||||
regex = "1.11"
|
||||
tokio-socks = { git = "https://github.com/rustdesk-org/tokio-socks" }
|
||||
chrono = "0.4"
|
||||
backtrace = "0.3"
|
||||
libc = "0.2"
|
||||
dlopen = "0.1"
|
||||
toml = "0.7"
|
||||
@@ -88,21 +83,7 @@ protobuf-codegen = { version = "3.7" }
|
||||
clap = "4.5.51"
|
||||
webrtc = "0.13.0" # keep in lockstep with [dependencies] webrtc (rustc 1.75 pin, see above)
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = [
|
||||
"winuser",
|
||||
"synchapi",
|
||||
"pdh",
|
||||
"memoryapi",
|
||||
"sysinfoapi",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
osascript = "0.3"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.20.0", default-features = false, features = [
|
||||
"calloop",
|
||||
] }
|
||||
users = { version = "0.11" }
|
||||
x11 = "2.21"
|
||||
|
||||
@@ -6,7 +6,7 @@ fn main() {
|
||||
protobuf_codegen::Codegen::new()
|
||||
.pure()
|
||||
.out_dir(out_dir)
|
||||
.inputs(["protos/rendezvous.proto", "protos/message.proto"])
|
||||
.inputs(["protos/rendezvous.proto"])
|
||||
.include("protos")
|
||||
.customize(protobuf_codegen::Customize::default().tokio_bytes(true))
|
||||
.run()
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
extern crate hbb_common;
|
||||
#[cfg(target_os = "linux")]
|
||||
use hbb_common::platform::linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
use hbb_common::platform::macos;
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_os = "linux")]
|
||||
let res = linux::system_message("test title", "test message", true);
|
||||
#[cfg(target_os = "macos")]
|
||||
let res = macos::alert(
|
||||
"System Preferences".to_owned(),
|
||||
"warning".to_owned(),
|
||||
"test title".to_owned(),
|
||||
"test message".to_owned(),
|
||||
["Ok".to_owned()].to_vec(),
|
||||
);
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
println!("result {:?}", &res);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,15 @@
|
||||
syntax = "proto3";
|
||||
package hbb;
|
||||
|
||||
message IdPk {
|
||||
string id = 1;
|
||||
bytes pk = 2;
|
||||
// DTLS certificate fingerprint of the signer's WebRTC endpoint, signed together with id/pk so
|
||||
// a WebRTC peer's DTLS channel can be bound to its verified identity (defeats a rendezvous/relay
|
||||
// that swaps SDP fingerprints). Empty for non-WebRTC handshakes.
|
||||
string dtls_fingerprint = 3;
|
||||
}
|
||||
|
||||
message RegisterPeer {
|
||||
string id = 1;
|
||||
int32 serial = 2;
|
||||
|
||||
+6
-370
@@ -471,10 +471,10 @@ fn patch(path: PathBuf) -> PathBuf {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if _tmp == "/root" {
|
||||
if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") {
|
||||
if let Ok(user) = crate::sh::run_cmds_trim_newline("whoami") {
|
||||
if user != "root" {
|
||||
let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user);
|
||||
if let Ok(output) = crate::platform::linux::run_cmds_trim_newline(&cmd) {
|
||||
if let Ok(output) = crate::sh::run_cmds_trim_newline(&cmd) {
|
||||
return output.into();
|
||||
}
|
||||
return format!("/home/{user}").into();
|
||||
@@ -755,9 +755,9 @@ impl Config {
|
||||
///
|
||||
/// **DO NOT use this function in privileged contexts** (e.g., code executed via
|
||||
/// `gtk_sudo` or system services running as root). For privileged operations on
|
||||
/// Linux, use `crate::platform::linux::get_home_dir_trusted()` which bypasses
|
||||
/// the `$HOME` environment variable and queries the system password database
|
||||
/// directly via `getpwuid`.
|
||||
/// Linux, use `get_home_dir_trusted()` -- a `getpwuid`-based lookup that
|
||||
/// bypasses the `$HOME` environment variable and queries the system password
|
||||
/// database directly. It lives with the platform code in the client tree.
|
||||
///
|
||||
/// Using `$HOME` in privileged contexts creates a confused-deputy vulnerability
|
||||
/// where an attacker can manipulate the environment variable to inject malicious
|
||||
@@ -2848,21 +2848,10 @@ pub fn allow_insecure_tls_fallback() -> bool {
|
||||
}
|
||||
|
||||
pub mod keys {
|
||||
pub const OPTION_VIEW_ONLY: &str = "view_only";
|
||||
pub const OPTION_SHOW_MONITORS_TOOLBAR: &str = "show_monitors_toolbar";
|
||||
// Only the keys hbb_common itself references.
|
||||
pub const OPTION_COLLAPSE_TOOLBAR: &str = "collapse_toolbar";
|
||||
pub const OPTION_SHOW_REMOTE_CURSOR: &str = "show_remote_cursor";
|
||||
pub const OPTION_FOLLOW_REMOTE_CURSOR: &str = "follow_remote_cursor";
|
||||
pub const OPTION_FOLLOW_REMOTE_WINDOW: &str = "follow_remote_window";
|
||||
pub const OPTION_ZOOM_CURSOR: &str = "zoom-cursor";
|
||||
pub const OPTION_SHOW_QUALITY_MONITOR: &str = "show_quality_monitor";
|
||||
pub const OPTION_DISABLE_AUDIO: &str = "disable_audio";
|
||||
pub const OPTION_ENABLE_REMOTE_PRINTER: &str = "enable-remote-printer";
|
||||
pub const OPTION_ENABLE_FILE_COPY_PASTE: &str = "enable-file-copy-paste";
|
||||
pub const OPTION_DISABLE_CLIPBOARD: &str = "disable_clipboard";
|
||||
pub const OPTION_LOCK_AFTER_SESSION_END: &str = "lock_after_session_end";
|
||||
pub const OPTION_PRIVACY_MODE: &str = "privacy_mode";
|
||||
pub const OPTION_TOUCH_MODE: &str = "touch-mode";
|
||||
pub const OPTION_I444: &str = "i444";
|
||||
pub const OPTION_REVERSE_MOUSE_WHEEL: &str = "reverse_mouse_wheel";
|
||||
pub const OPTION_SWAP_LEFT_RIGHT_MOUSE: &str = "swap-left-right-mouse";
|
||||
@@ -2876,380 +2865,27 @@ pub mod keys {
|
||||
pub const OPTION_CUSTOM_IMAGE_QUALITY: &str = "custom_image_quality";
|
||||
pub const OPTION_CUSTOM_FPS: &str = "custom-fps";
|
||||
pub const OPTION_CODEC_PREFERENCE: &str = "codec-preference";
|
||||
pub const OPTION_SYNC_INIT_CLIPBOARD: &str = "sync-init-clipboard";
|
||||
pub const OPTION_THEME: &str = "theme";
|
||||
pub const OPTION_LANGUAGE: &str = "lang";
|
||||
pub const OPTION_REMOTE_MENUBAR_DRAG_LEFT: &str = "remote-menubar-drag-left";
|
||||
pub const OPTION_REMOTE_MENUBAR_DRAG_RIGHT: &str = "remote-menubar-drag-right";
|
||||
pub const OPTION_HIDE_AB_TAGS_PANEL: &str = "hideAbTagsPanel";
|
||||
pub const OPTION_ENABLE_CONFIRM_CLOSING_TABS: &str = "enable-confirm-closing-tabs";
|
||||
pub const OPTION_ENABLE_OPEN_NEW_CONNECTIONS_IN_TABS: &str =
|
||||
"enable-open-new-connections-in-tabs";
|
||||
pub const OPTION_TEXTURE_RENDER: &str = "use-texture-render";
|
||||
// Internal health record written by the texture-render watchdog/probe;
|
||||
// "failed-*" flips the texture-render default to opt-in on this machine.
|
||||
pub const OPTION_TEXTURE_RENDER_HEALTH: &str = "texture-render-health";
|
||||
pub const OPTION_ALLOW_D3D_RENDER: &str = "allow-d3d-render";
|
||||
pub const OPTION_ENABLE_CHECK_UPDATE: &str = "enable-check-update";
|
||||
pub const OPTION_ALLOW_AUTO_UPDATE: &str = "allow-auto-update";
|
||||
pub const OPTION_SYNC_AB_WITH_RECENT_SESSIONS: &str = "sync-ab-with-recent-sessions";
|
||||
pub const OPTION_SYNC_AB_TAGS: &str = "sync-ab-tags";
|
||||
pub const OPTION_FILTER_AB_BY_INTERSECTION: &str = "filter-ab-by-intersection";
|
||||
pub const OPTION_ACCESS_MODE: &str = "access-mode";
|
||||
pub const OPTION_ENABLE_KEYBOARD: &str = "enable-keyboard";
|
||||
pub const OPTION_ENABLE_CLIPBOARD: &str = "enable-clipboard";
|
||||
pub const OPTION_ENABLE_FILE_TRANSFER: &str = "enable-file-transfer";
|
||||
pub const OPTION_ENABLE_CAMERA: &str = "enable-camera";
|
||||
pub const OPTION_ENABLE_TERMINAL: &str = "enable-terminal";
|
||||
pub const OPTION_TERMINAL_PERSISTENT: &str = "terminal-persistent";
|
||||
pub const OPTION_ENABLE_AUDIO: &str = "enable-audio";
|
||||
pub const OPTION_ENABLE_TUNNEL: &str = "enable-tunnel";
|
||||
pub const OPTION_ENABLE_REMOTE_RESTART: &str = "enable-remote-restart";
|
||||
pub const OPTION_ENABLE_RECORD_SESSION: &str = "enable-record-session";
|
||||
pub const OPTION_ENABLE_BLOCK_INPUT: &str = "enable-block-input";
|
||||
pub const OPTION_ENABLE_PRIVACY_MODE: &str = "enable-privacy-mode";
|
||||
pub const OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW: &str =
|
||||
"enable-perm-change-in-accept-window";
|
||||
pub const OPTION_ALLOW_SCOPE_VIOLATION_CLOSE: &str = "allow-scope-violation-close";
|
||||
pub const OPTION_ALLOW_SCOPE_VIOLATION_ALARM: &str = "allow-scope-violation-alarm";
|
||||
pub const OPTION_ALLOW_REMOTE_CONFIG_MODIFICATION: &str = "allow-remote-config-modification";
|
||||
pub const OPTION_ALLOW_NUMERNIC_ONE_TIME_PASSWORD: &str = "allow-numeric-one-time-password";
|
||||
pub const OPTION_ENABLE_LAN_DISCOVERY: &str = "enable-lan-discovery";
|
||||
pub const OPTION_DIRECT_SERVER: &str = "direct-server";
|
||||
pub const OPTION_DIRECT_ACCESS_PORT: &str = "direct-access-port";
|
||||
pub const OPTION_WHITELIST: &str = "whitelist";
|
||||
pub const OPTION_ID_WHITELIST: &str = "id-whitelist";
|
||||
pub const OPTION_ALLOW_AUTO_DISCONNECT: &str = "allow-auto-disconnect";
|
||||
pub const OPTION_AUTO_DISCONNECT_TIMEOUT: &str = "auto-disconnect-timeout";
|
||||
pub const OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN: &str = "allow-only-conn-window-open";
|
||||
pub const OPTION_ALLOW_AUTO_RECORD_INCOMING: &str = "allow-auto-record-incoming";
|
||||
pub const OPTION_ALLOW_AUTO_RECORD_OUTGOING: &str = "allow-auto-record-outgoing";
|
||||
pub const OPTION_HIDE_RECORDING_BUTTON: &str = "hide-recording-button";
|
||||
pub const OPTION_WINDOWS_SERVICE_VIDEO_SAVE_DIRECTORY: &str =
|
||||
"windows-service-video-save-directory";
|
||||
pub const OPTION_VIDEO_SAVE_DIRECTORY: &str = "video-save-directory";
|
||||
pub const OPTION_ENABLE_ABR: &str = "enable-abr";
|
||||
pub const OPTION_ALLOW_REMOVE_WALLPAPER: &str = "allow-remove-wallpaper";
|
||||
pub const OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER: &str = "allow-always-software-render";
|
||||
pub const OPTION_ENABLE_HWCODEC: &str = "enable-hwcodec";
|
||||
pub const OPTION_APPROVE_MODE: &str = "approve-mode";
|
||||
pub const OPTION_VERIFICATION_METHOD: &str = "verification-method";
|
||||
pub const OPTION_TEMPORARY_PASSWORD_LENGTH: &str = "temporary-password-length";
|
||||
pub const OPTION_CUSTOM_RENDEZVOUS_SERVER: &str = "custom-rendezvous-server";
|
||||
pub const OPTION_API_SERVER: &str = "api-server";
|
||||
pub const OPTION_KEY: &str = "key";
|
||||
pub const OPTION_ALLOW_WEBSOCKET: &str = "allow-websocket";
|
||||
pub const OPTION_PRESET_ADDRESS_BOOK_NAME: &str = "preset-address-book-name";
|
||||
pub const OPTION_PRESET_ADDRESS_BOOK_TAG: &str = "preset-address-book-tag";
|
||||
pub const OPTION_PRESET_ADDRESS_BOOK_ALIAS: &str = "preset-address-book-alias";
|
||||
pub const OPTION_PRESET_ADDRESS_BOOK_PASSWORD: &str = "preset-address-book-password";
|
||||
pub const OPTION_PRESET_ADDRESS_BOOK_NOTE: &str = "preset-address-book-note";
|
||||
pub const OPTION_PRESET_DEVICE_USERNAME: &str = "preset-device-username";
|
||||
pub const OPTION_PRESET_DEVICE_NAME: &str = "preset-device-name";
|
||||
pub const OPTION_PRESET_NOTE: &str = "preset-note";
|
||||
pub const OPTION_ENABLE_DIRECTX_CAPTURE: &str = "enable-directx-capture";
|
||||
pub const OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE: &str =
|
||||
"enable-android-software-encoding-half-scale";
|
||||
pub const OPTION_ENABLE_TRUSTED_DEVICES: &str = "enable-trusted-devices";
|
||||
pub const OPTION_AV1_TEST: &str = "av1-test";
|
||||
pub const OPTION_TRACKPAD_SPEED: &str = "trackpad-speed";
|
||||
pub const OPTION_REGISTER_DEVICE: &str = "register-device";
|
||||
pub const OPTION_RELAY_SERVER: &str = "relay-server";
|
||||
pub const OPTION_ICE_SERVERS: &str = "ice-servers";
|
||||
/// Maximum number of files allowed during a single file transfer request.
|
||||
///
|
||||
/// Key: `file-transfer-max-files`.
|
||||
/// Unit: number of files (not bytes).
|
||||
///
|
||||
/// Behaviour:
|
||||
/// - If set to a positive integer N, at most N files are allowed.
|
||||
/// - If set to 0, a safe built-in default is used (see DEFAULT_MAX_VALIDATED_FILES).
|
||||
/// - If unset, negative, or non-integer, no explicit limit is enforced for backward compatibility.
|
||||
pub const OPTION_FILE_TRANSFER_MAX_FILES: &str = "file-transfer-max-files";
|
||||
pub const OPTION_DISABLE_UDP: &str = "disable-udp";
|
||||
pub const OPTION_ALLOW_INSECURE_TLS_FALLBACK: &str = "allow-insecure-tls-fallback";
|
||||
pub const OPTION_SHOW_VIRTUAL_MOUSE: &str = "show-virtual-mouse";
|
||||
// joystick is the virtual mouse.
|
||||
// So `OPTION_SHOW_VIRTUAL_MOUSE` should also be set if `OPTION_SHOW_VIRTUAL_JOYSTICK` is set.
|
||||
pub const OPTION_SHOW_VIRTUAL_JOYSTICK: &str = "show-virtual-joystick";
|
||||
pub const OPTION_ENABLE_FLUTTER_HTTP_ON_RUST: &str = "enable-flutter-http-on-rust";
|
||||
pub const OPTION_ALLOW_ASK_FOR_NOTE: &str = "allow-ask-for-note";
|
||||
|
||||
// built-in options
|
||||
pub const OPTION_DISPLAY_NAME: &str = "display-name";
|
||||
pub const OPTION_AVATAR: &str = "avatar";
|
||||
pub const OPTION_PRESET_DEVICE_GROUP_NAME: &str = "preset-device-group-name";
|
||||
pub const OPTION_PRESET_USERNAME: &str = "preset-user-name";
|
||||
pub const OPTION_PRESET_STRATEGY_NAME: &str = "preset-strategy-name";
|
||||
pub const OPTION_REMOVE_PRESET_PASSWORD_WARNING: &str = "remove-preset-password-warning";
|
||||
pub const OPTION_HIDE_GENERAL_SETTINGS: &str = "hide-general-settings";
|
||||
pub const OPTION_HIDE_SECURITY_SETTINGS: &str = "hide-security-settings";
|
||||
pub const OPTION_HIDE_NETWORK_SETTINGS: &str = "hide-network-settings";
|
||||
pub const OPTION_HIDE_SERVER_SETTINGS: &str = "hide-server-settings";
|
||||
pub const OPTION_HIDE_PROXY_SETTINGS: &str = "hide-proxy-settings";
|
||||
pub const OPTION_HIDE_REMOTE_PRINTER_SETTINGS: &str = "hide-remote-printer-settings";
|
||||
pub const OPTION_HIDE_WEBSOCKET_SETTINGS: &str = "hide-websocket-settings";
|
||||
pub const OPTION_HIDE_STOP_SERVICE: &str = "hide-stop-service";
|
||||
pub const OPTION_ALLOW_COMMAND_LINE_SETTINGS_WHEN_SETTINGS_DISABLED: &str =
|
||||
"allow-command-line-settings-when-settings-disabled";
|
||||
|
||||
// Connection punch-through / port-forward options
|
||||
pub const OPTION_ENABLE_TCP_PUNCH: &str = "enable-tcp-punch";
|
||||
pub const OPTION_ENABLE_UDP_PUNCH: &str = "enable-udp-punch";
|
||||
pub const OPTION_ENABLE_IPV6_PUNCH: &str = "enable-ipv6-punch";
|
||||
pub const OPTION_ENABLE_PORT_FORWARD_MUX: &str = "enable-port-forward-mux";
|
||||
pub const OPTION_ENABLE_WEBRTC: &str = "enable-webrtc";
|
||||
pub const OPTION_ALLOW_KCP_CC: &str = "allow-kcp-congestion-control";
|
||||
pub const OPTION_ALLOW_WEBRTC_CC: &str = "allow-webrtc-congestion-control";
|
||||
pub const OPTION_HIDE_USERNAME_ON_CARD: &str = "hide-username-on-card";
|
||||
pub const OPTION_HIDE_HELP_CARDS: &str = "hide-help-cards";
|
||||
pub const OPTION_DEFAULT_CONNECT_PASSWORD: &str = "default-connect-password";
|
||||
pub const OPTION_HIDE_TRAY: &str = "hide-tray";
|
||||
pub const OPTION_ONE_WAY_CLIPBOARD_REDIRECTION: &str = "one-way-clipboard-redirection";
|
||||
pub const OPTION_ALLOW_LOGON_SCREEN_PASSWORD: &str = "allow-logon-screen-password";
|
||||
pub const OPTION_ALLOW_DEEP_LINK_PASSWORD: &str = "allow-deep-link-password";
|
||||
pub const OPTION_ALLOW_DEEP_LINK_SERVER_SETTINGS: &str = "allow-deep-link-server-settings";
|
||||
pub const OPTION_ONE_WAY_FILE_TRANSFER: &str = "one-way-file-transfer";
|
||||
pub const OPTION_ALLOW_HTTPS_21114: &str = "allow-https-21114";
|
||||
pub const OPTION_USE_RAW_TCP_FOR_API: &str = "use-raw-tcp-for-api";
|
||||
pub const OPTION_ALLOW_HOSTNAME_AS_ID: &str = "allow-hostname-as-id";
|
||||
pub const OPTION_HIDE_POWERED_BY_ME: &str = "hide-powered-by-me";
|
||||
pub const OPTION_MAIN_WINDOW_ALWAYS_ON_TOP: &str = "main-window-always-on-top";
|
||||
pub const OPTION_DISABLE_CHANGE_PERMANENT_PASSWORD: &str = "disable-change-permanent-password";
|
||||
pub const OPTION_DISABLE_CHANGE_ID: &str = "disable-change-id";
|
||||
pub const OPTION_DISABLE_UNLOCK_PIN: &str = "disable-unlock-pin";
|
||||
|
||||
// flutter local options
|
||||
pub const OPTION_FLUTTER_REMOTE_MENUBAR_STATE: &str = "remoteMenubarState";
|
||||
pub const OPTION_FLUTTER_PEER_SORTING: &str = "peer-sorting";
|
||||
pub const OPTION_FLUTTER_PEER_TAB_INDEX: &str = "peer-tab-index";
|
||||
pub const OPTION_FLUTTER_PEER_TAB_ORDER: &str = "peer-tab-order";
|
||||
pub const OPTION_FLUTTER_PEER_TAB_VISIBLE: &str = "peer-tab-visible";
|
||||
pub const OPTION_FLUTTER_PEER_CARD_UI_TYLE: &str = "peer-card-ui-type";
|
||||
pub const OPTION_FLUTTER_CURRENT_AB_NAME: &str = "current-ab-name";
|
||||
pub const OPTION_ALLOW_REMOTE_CM_MODIFICATION: &str = "allow-remote-cm-modification";
|
||||
pub const OPTION_ALLOW_SYNC_CLIPBOARD_BETWEEN_SESSIONS: &str =
|
||||
"allow-sync-clipboard-between-sessions";
|
||||
|
||||
pub const OPTION_PRINTER_INCOMING_JOB_ACTION: &str = "printer-incomming-job-action";
|
||||
pub const OPTION_PRINTER_ALLOW_AUTO_PRINT: &str = "allow-printer-auto-print";
|
||||
pub const OPTION_PRINTER_SELECTED_NAME: &str = "printer-selected-name";
|
||||
|
||||
// android floating window options
|
||||
pub const OPTION_DISABLE_FLOATING_WINDOW: &str = "disable-floating-window";
|
||||
pub const OPTION_FLOATING_WINDOW_SIZE: &str = "floating-window-size";
|
||||
pub const OPTION_FLOATING_WINDOW_UNTOUCHABLE: &str = "floating-window-untouchable";
|
||||
pub const OPTION_FLOATING_WINDOW_TRANSPARENCY: &str = "floating-window-transparency";
|
||||
pub const OPTION_FLOATING_WINDOW_SVG: &str = "floating-window-svg";
|
||||
|
||||
// android keep screen on
|
||||
pub const OPTION_KEEP_SCREEN_ON: &str = "keep-screen-on";
|
||||
|
||||
// Server-side: keep host system awake during incoming sessions (Security setting)
|
||||
pub const OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS: &str =
|
||||
"keep-awake-during-incoming-sessions";
|
||||
|
||||
// Client-side: keep client system awake during outgoing sessions (General setting)
|
||||
pub const OPTION_KEEP_AWAKE_DURING_OUTGOING_SESSIONS: &str =
|
||||
"keep-awake-during-outgoing-sessions";
|
||||
|
||||
pub const OPTION_DISABLE_GROUP_PANEL: &str = "disable-group-panel";
|
||||
pub const OPTION_DISABLE_DISCOVERY_PANEL: &str = "disable-discovery-panel";
|
||||
pub const OPTION_PRE_ELEVATE_SERVICE: &str = "pre-elevate-service";
|
||||
|
||||
// proxy settings
|
||||
// The following options are not real keys, they are just used for custom client advanced settings.
|
||||
// The real keys are in Config2::socks.
|
||||
pub const OPTION_PROXY_URL: &str = "proxy-url";
|
||||
pub const OPTION_PROXY_USERNAME: &str = "proxy-username";
|
||||
pub const OPTION_PROXY_PASSWORD: &str = "proxy-password";
|
||||
|
||||
// DEFAULT_DISPLAY_SETTINGS, OVERWRITE_DISPLAY_SETTINGS
|
||||
pub const KEYS_DISPLAY_SETTINGS: &[&str] = &[
|
||||
OPTION_VIEW_ONLY,
|
||||
OPTION_SHOW_MONITORS_TOOLBAR,
|
||||
OPTION_COLLAPSE_TOOLBAR,
|
||||
OPTION_SHOW_REMOTE_CURSOR,
|
||||
OPTION_FOLLOW_REMOTE_CURSOR,
|
||||
OPTION_FOLLOW_REMOTE_WINDOW,
|
||||
OPTION_ZOOM_CURSOR,
|
||||
OPTION_SHOW_QUALITY_MONITOR,
|
||||
OPTION_DISABLE_AUDIO,
|
||||
OPTION_ENABLE_FILE_COPY_PASTE,
|
||||
OPTION_DISABLE_CLIPBOARD,
|
||||
OPTION_LOCK_AFTER_SESSION_END,
|
||||
OPTION_PRIVACY_MODE,
|
||||
OPTION_TOUCH_MODE,
|
||||
OPTION_I444,
|
||||
OPTION_REVERSE_MOUSE_WHEEL,
|
||||
OPTION_SWAP_LEFT_RIGHT_MOUSE,
|
||||
OPTION_DISPLAYS_AS_INDIVIDUAL_WINDOWS,
|
||||
OPTION_USE_ALL_MY_DISPLAYS_FOR_THE_REMOTE_SESSION,
|
||||
OPTION_VIEW_STYLE,
|
||||
OPTION_TERMINAL_PERSISTENT,
|
||||
OPTION_SCROLL_STYLE,
|
||||
OPTION_EDGE_SCROLL_EDGE_THICKNESS,
|
||||
OPTION_IMAGE_QUALITY,
|
||||
OPTION_CUSTOM_IMAGE_QUALITY,
|
||||
OPTION_CUSTOM_FPS,
|
||||
OPTION_CODEC_PREFERENCE,
|
||||
OPTION_SYNC_INIT_CLIPBOARD,
|
||||
OPTION_TRACKPAD_SPEED,
|
||||
];
|
||||
// DEFAULT_LOCAL_SETTINGS, OVERWRITE_LOCAL_SETTINGS
|
||||
pub const KEYS_LOCAL_SETTINGS: &[&str] = &[
|
||||
OPTION_THEME,
|
||||
OPTION_LANGUAGE,
|
||||
OPTION_ENABLE_CONFIRM_CLOSING_TABS,
|
||||
OPTION_ENABLE_OPEN_NEW_CONNECTIONS_IN_TABS,
|
||||
OPTION_TEXTURE_RENDER,
|
||||
OPTION_ALLOW_D3D_RENDER,
|
||||
OPTION_SYNC_AB_WITH_RECENT_SESSIONS,
|
||||
OPTION_SYNC_AB_TAGS,
|
||||
OPTION_FILTER_AB_BY_INTERSECTION,
|
||||
OPTION_REMOTE_MENUBAR_DRAG_LEFT,
|
||||
OPTION_REMOTE_MENUBAR_DRAG_RIGHT,
|
||||
OPTION_HIDE_AB_TAGS_PANEL,
|
||||
OPTION_FLUTTER_REMOTE_MENUBAR_STATE,
|
||||
OPTION_FLUTTER_PEER_SORTING,
|
||||
OPTION_FLUTTER_PEER_TAB_INDEX,
|
||||
OPTION_FLUTTER_PEER_TAB_ORDER,
|
||||
OPTION_FLUTTER_PEER_TAB_VISIBLE,
|
||||
OPTION_FLUTTER_PEER_CARD_UI_TYLE,
|
||||
OPTION_FLUTTER_CURRENT_AB_NAME,
|
||||
OPTION_DISABLE_FLOATING_WINDOW,
|
||||
OPTION_FLOATING_WINDOW_SIZE,
|
||||
OPTION_FLOATING_WINDOW_UNTOUCHABLE,
|
||||
OPTION_FLOATING_WINDOW_TRANSPARENCY,
|
||||
OPTION_FLOATING_WINDOW_SVG,
|
||||
OPTION_KEEP_SCREEN_ON,
|
||||
// Client-side: keep client system awake during outgoing sessions (General setting)
|
||||
OPTION_KEEP_AWAKE_DURING_OUTGOING_SESSIONS,
|
||||
OPTION_DISABLE_GROUP_PANEL,
|
||||
OPTION_DISABLE_DISCOVERY_PANEL,
|
||||
OPTION_PRE_ELEVATE_SERVICE,
|
||||
OPTION_ALLOW_REMOTE_CM_MODIFICATION,
|
||||
OPTION_ALLOW_SYNC_CLIPBOARD_BETWEEN_SESSIONS,
|
||||
OPTION_ENABLE_CHECK_UPDATE,
|
||||
OPTION_PRINTER_INCOMING_JOB_ACTION,
|
||||
OPTION_PRINTER_ALLOW_AUTO_PRINT,
|
||||
OPTION_PRINTER_SELECTED_NAME,
|
||||
OPTION_ALLOW_AUTO_RECORD_OUTGOING,
|
||||
OPTION_HIDE_RECORDING_BUTTON,
|
||||
OPTION_VIDEO_SAVE_DIRECTORY,
|
||||
OPTION_ENABLE_TCP_PUNCH,
|
||||
OPTION_ENABLE_UDP_PUNCH,
|
||||
OPTION_ENABLE_IPV6_PUNCH,
|
||||
OPTION_ENABLE_PORT_FORWARD_MUX,
|
||||
OPTION_ENABLE_WEBRTC,
|
||||
OPTION_TOUCH_MODE,
|
||||
OPTION_SHOW_VIRTUAL_MOUSE,
|
||||
OPTION_SHOW_VIRTUAL_JOYSTICK,
|
||||
OPTION_ENABLE_FLUTTER_HTTP_ON_RUST,
|
||||
OPTION_ALLOW_ASK_FOR_NOTE,
|
||||
];
|
||||
// DEFAULT_SETTINGS, OVERWRITE_SETTINGS
|
||||
pub const KEYS_SETTINGS: &[&str] = &[
|
||||
OPTION_ACCESS_MODE,
|
||||
OPTION_ENABLE_KEYBOARD,
|
||||
OPTION_ENABLE_CLIPBOARD,
|
||||
OPTION_ENABLE_FILE_TRANSFER,
|
||||
OPTION_ENABLE_CAMERA,
|
||||
OPTION_ENABLE_TERMINAL,
|
||||
OPTION_ENABLE_REMOTE_PRINTER,
|
||||
OPTION_ENABLE_AUDIO,
|
||||
OPTION_ENABLE_TUNNEL,
|
||||
OPTION_ENABLE_REMOTE_RESTART,
|
||||
OPTION_ENABLE_RECORD_SESSION,
|
||||
OPTION_ENABLE_BLOCK_INPUT,
|
||||
OPTION_ENABLE_PRIVACY_MODE,
|
||||
OPTION_ALLOW_SCOPE_VIOLATION_CLOSE,
|
||||
OPTION_ALLOW_SCOPE_VIOLATION_ALARM,
|
||||
OPTION_ALLOW_REMOTE_CONFIG_MODIFICATION,
|
||||
OPTION_ALLOW_NUMERNIC_ONE_TIME_PASSWORD,
|
||||
OPTION_ENABLE_LAN_DISCOVERY,
|
||||
OPTION_DIRECT_SERVER,
|
||||
OPTION_DIRECT_ACCESS_PORT,
|
||||
OPTION_WHITELIST,
|
||||
OPTION_ID_WHITELIST,
|
||||
OPTION_ALLOW_AUTO_DISCONNECT,
|
||||
OPTION_AUTO_DISCONNECT_TIMEOUT,
|
||||
OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN,
|
||||
OPTION_ALLOW_AUTO_RECORD_INCOMING,
|
||||
OPTION_WINDOWS_SERVICE_VIDEO_SAVE_DIRECTORY,
|
||||
OPTION_ENABLE_ABR,
|
||||
OPTION_ALLOW_REMOVE_WALLPAPER,
|
||||
OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER,
|
||||
OPTION_ENABLE_HWCODEC,
|
||||
OPTION_APPROVE_MODE,
|
||||
OPTION_VERIFICATION_METHOD,
|
||||
OPTION_TEMPORARY_PASSWORD_LENGTH,
|
||||
OPTION_PROXY_URL,
|
||||
OPTION_PROXY_USERNAME,
|
||||
OPTION_PROXY_PASSWORD,
|
||||
OPTION_CUSTOM_RENDEZVOUS_SERVER,
|
||||
OPTION_API_SERVER,
|
||||
OPTION_KEY,
|
||||
OPTION_ALLOW_WEBSOCKET,
|
||||
OPTION_PRESET_ADDRESS_BOOK_NAME,
|
||||
OPTION_PRESET_ADDRESS_BOOK_TAG,
|
||||
OPTION_PRESET_ADDRESS_BOOK_ALIAS,
|
||||
OPTION_PRESET_ADDRESS_BOOK_PASSWORD,
|
||||
OPTION_PRESET_ADDRESS_BOOK_NOTE,
|
||||
OPTION_PRESET_DEVICE_USERNAME,
|
||||
OPTION_PRESET_DEVICE_NAME,
|
||||
OPTION_PRESET_NOTE,
|
||||
OPTION_ENABLE_DIRECTX_CAPTURE,
|
||||
OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE,
|
||||
OPTION_ENABLE_TRUSTED_DEVICES,
|
||||
OPTION_RELAY_SERVER,
|
||||
OPTION_ICE_SERVERS,
|
||||
OPTION_DISABLE_UDP,
|
||||
OPTION_ALLOW_INSECURE_TLS_FALLBACK,
|
||||
OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS,
|
||||
OPTION_ALLOW_AUTO_UPDATE,
|
||||
OPTION_ALLOW_KCP_CC,
|
||||
OPTION_ALLOW_WEBRTC_CC,
|
||||
];
|
||||
|
||||
// BUILDIN_SETTINGS
|
||||
pub const KEYS_BUILDIN_SETTINGS: &[&str] = &[
|
||||
OPTION_DISPLAY_NAME,
|
||||
OPTION_AVATAR,
|
||||
OPTION_PRESET_DEVICE_GROUP_NAME,
|
||||
OPTION_PRESET_USERNAME,
|
||||
OPTION_PRESET_STRATEGY_NAME,
|
||||
OPTION_REMOVE_PRESET_PASSWORD_WARNING,
|
||||
OPTION_HIDE_GENERAL_SETTINGS,
|
||||
OPTION_HIDE_SECURITY_SETTINGS,
|
||||
OPTION_HIDE_NETWORK_SETTINGS,
|
||||
OPTION_HIDE_SERVER_SETTINGS,
|
||||
OPTION_HIDE_PROXY_SETTINGS,
|
||||
OPTION_HIDE_REMOTE_PRINTER_SETTINGS,
|
||||
OPTION_HIDE_WEBSOCKET_SETTINGS,
|
||||
OPTION_HIDE_STOP_SERVICE,
|
||||
OPTION_HIDE_USERNAME_ON_CARD,
|
||||
OPTION_HIDE_HELP_CARDS,
|
||||
OPTION_DEFAULT_CONNECT_PASSWORD,
|
||||
OPTION_HIDE_TRAY,
|
||||
OPTION_ONE_WAY_CLIPBOARD_REDIRECTION,
|
||||
OPTION_ALLOW_LOGON_SCREEN_PASSWORD,
|
||||
OPTION_ALLOW_DEEP_LINK_PASSWORD,
|
||||
OPTION_ALLOW_DEEP_LINK_SERVER_SETTINGS,
|
||||
OPTION_ONE_WAY_FILE_TRANSFER,
|
||||
OPTION_ALLOW_HTTPS_21114,
|
||||
OPTION_ALLOW_HOSTNAME_AS_ID,
|
||||
OPTION_REGISTER_DEVICE,
|
||||
OPTION_HIDE_POWERED_BY_ME,
|
||||
OPTION_MAIN_WINDOW_ALWAYS_ON_TOP,
|
||||
OPTION_FILE_TRANSFER_MAX_FILES,
|
||||
OPTION_DISABLE_CHANGE_PERMANENT_PASSWORD,
|
||||
OPTION_DISABLE_CHANGE_ID,
|
||||
OPTION_DISABLE_UNLOCK_PIN,
|
||||
OPTION_USE_RAW_TCP_FOR_API,
|
||||
OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
|
||||
OPTION_ALLOW_COMMAND_LINE_SETTINGS_WHEN_SETTINGS_DISABLED,
|
||||
];
|
||||
}
|
||||
|
||||
pub fn common_load<
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
use std::{fmt, slice::Iter, str::FromStr};
|
||||
|
||||
use crate::protos::message::KeyboardMode;
|
||||
|
||||
impl fmt::Display for KeyboardMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
KeyboardMode::Legacy => write!(f, "legacy"),
|
||||
KeyboardMode::Map => write!(f, "map"),
|
||||
KeyboardMode::Translate => write!(f, "translate"),
|
||||
KeyboardMode::Auto => write!(f, "auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for KeyboardMode {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"legacy" => Ok(KeyboardMode::Legacy),
|
||||
"map" => Ok(KeyboardMode::Map),
|
||||
"translate" => Ok(KeyboardMode::Translate),
|
||||
"auto" => Ok(KeyboardMode::Auto),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardMode {
|
||||
pub fn iter() -> Iter<'static, KeyboardMode> {
|
||||
static KEYBOARD_MODES: [KeyboardMode; 4] = [
|
||||
KeyboardMode::Legacy,
|
||||
KeyboardMode::Map,
|
||||
KeyboardMode::Translate,
|
||||
KeyboardMode::Auto,
|
||||
];
|
||||
KEYBOARD_MODES.iter()
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -1,11 +1,9 @@
|
||||
pub mod compress;
|
||||
pub mod platform;
|
||||
pub mod protos;
|
||||
pub use bytes;
|
||||
use config::Config;
|
||||
pub use futures;
|
||||
pub use protobuf;
|
||||
pub use protos::message as message_proto;
|
||||
pub use protos::rendezvous as rendezvous_proto;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -27,7 +25,6 @@ pub mod bytes_codec;
|
||||
pub use anyhow::{self, bail};
|
||||
pub use futures_util;
|
||||
pub mod config;
|
||||
pub mod fs;
|
||||
pub mod mem;
|
||||
pub use lazy_static;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -42,7 +39,8 @@ pub mod password_security;
|
||||
pub use chrono;
|
||||
pub use directories_next;
|
||||
pub use libc;
|
||||
pub mod keyboard;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod sh;
|
||||
pub use base64;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use dlopen;
|
||||
|
||||
@@ -1,671 +0,0 @@
|
||||
use crate::ResultType;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use users::{get_current_uid, get_user_by_uid, os::unix::UserExt};
|
||||
|
||||
use sctk::{
|
||||
output::OutputData,
|
||||
output::{OutputHandler, OutputState},
|
||||
reexports::client::protocol::wl_output::WlOutput,
|
||||
reexports::client::{globals, Proxy},
|
||||
reexports::client::{Connection, QueueHandle},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref DISTRO: Distro = Distro::new();
|
||||
}
|
||||
|
||||
// to-do: There seems to be some runtime issue that causes the audit logs to be generated.
|
||||
// We may need to fix this and remove this workaround in the future.
|
||||
//
|
||||
// We use the pre-search method to find the command path to avoid the audit logs on some systems.
|
||||
// No idea why the audit logs happen.
|
||||
// Though the audit logs may disappear after rebooting.
|
||||
//
|
||||
// See https://github.com/rustdesk/rustdesk/discussions/11959
|
||||
//
|
||||
// `ausearch -x /usr/share/rustdesk/rustdesk` will return
|
||||
// ...
|
||||
// time->Tue Jun 24 10:40:43 2025
|
||||
// type=PROCTITLE msg=audit(1750776043.446:192757): proctitle=2F7573722F62696E2F727573746465736B002D2D73657276696365
|
||||
// type=PATH msg=audit(1750776043.446:192757): item=0 name="/usr/local/bin/sh" nametype=UNKNOWN cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
|
||||
// type=CWD msg=audit(1750776043.446:192757): cwd="/"
|
||||
// type=SYSCALL msg=audit(1750776043.446:192757): arch=c000003e syscall=59 success=no exit=-2 a0=7fb7dbd22da0 a1=1d65f2c0 a2=7ffc25193360 a3=7ffc25194ec0 items=1 ppid=172208 pid=267565 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="rustdesk" exe="/usr/share/rustdesk/rustdesk" subj=unconfined key="processos_criados"
|
||||
// ----
|
||||
// time->Tue Jun 24 10:40:43 2025
|
||||
// type=PROCTITLE msg=audit(1750776043.446:192758): proctitle=2F7573722F62696E2F727573746465736B002D2D73657276696365
|
||||
// type=PATH msg=audit(1750776043.446:192758): item=0 name="/usr/sbin/sh" nametype=UNKNOWN cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
|
||||
// ...
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CMD_LOGINCTL: String = find_cmd_path("loginctl");
|
||||
pub static ref CMD_PS: String = find_cmd_path("ps");
|
||||
pub static ref CMD_SH: String = find_cmd_path("sh");
|
||||
}
|
||||
|
||||
pub const DISPLAY_SERVER_WAYLAND: &str = "wayland";
|
||||
pub const DISPLAY_SERVER_X11: &str = "x11";
|
||||
pub const DISPLAY_DESKTOP_KDE: &str = "KDE";
|
||||
|
||||
pub const XDG_CURRENT_DESKTOP: &str = "XDG_CURRENT_DESKTOP";
|
||||
|
||||
pub struct Distro {
|
||||
pub name: String,
|
||||
pub version_id: String,
|
||||
}
|
||||
|
||||
impl Distro {
|
||||
fn new() -> Self {
|
||||
let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.to_string();
|
||||
let version_id = run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.to_string();
|
||||
Self { name, version_id }
|
||||
}
|
||||
}
|
||||
|
||||
fn find_cmd_path(cmd: &'static str) -> String {
|
||||
let test_cmd = format!("/bin/{}", cmd);
|
||||
if std::path::Path::new(&test_cmd).exists() {
|
||||
return test_cmd;
|
||||
}
|
||||
let test_cmd = format!("/usr/bin/{}", cmd);
|
||||
if std::path::Path::new(&test_cmd).exists() {
|
||||
return test_cmd;
|
||||
}
|
||||
if let Ok(output) = Command::new("which").arg(cmd).output() {
|
||||
if output.status.success() {
|
||||
return String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
}
|
||||
}
|
||||
cmd.to_string()
|
||||
}
|
||||
|
||||
// Deprecated. Use `hbb_common::platform::linux::is_kde_session()` instead for now.
|
||||
// Or we need to set the correct environment variable in the server process.
|
||||
#[inline]
|
||||
pub fn is_kde() -> bool {
|
||||
if let Ok(env) = std::env::var(XDG_CURRENT_DESKTOP) {
|
||||
env == DISPLAY_DESKTOP_KDE
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use `hbb_common::platform::linux::is_kde()` here.
|
||||
// It's not correct in the server process.
|
||||
pub fn is_kde_session() -> bool {
|
||||
std::process::Command::new(CMD_SH.as_str())
|
||||
.arg("-c")
|
||||
.arg("pgrep -f kded[0-9]+")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.output()
|
||||
.map(|o| !o.stdout.is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_gdm_user(username: &str) -> bool {
|
||||
username == "gdm" || username == "sddm"
|
||||
// || username == "lightgdm"
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_desktop_wayland() -> bool {
|
||||
get_display_server() == DISPLAY_SERVER_WAYLAND
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_x11_or_headless() -> bool {
|
||||
!is_desktop_wayland()
|
||||
}
|
||||
|
||||
// -1
|
||||
const INVALID_SESSION: &str = "4294967295";
|
||||
|
||||
pub fn get_display_server() -> String {
|
||||
// Check for forced display server environment variable first
|
||||
if let Ok(forced_display) = std::env::var("RUSTDESK_FORCED_DISPLAY_SERVER") {
|
||||
return forced_display;
|
||||
}
|
||||
|
||||
// Check if `loginctl` can be called successfully
|
||||
if run_loginctl(None).is_err() {
|
||||
return DISPLAY_SERVER_X11.to_owned();
|
||||
}
|
||||
|
||||
let mut session = get_values_of_seat0(&[0])[0].clone();
|
||||
if session.is_empty() {
|
||||
// loginctl has not given the expected output. try something else.
|
||||
if let Ok(sid) = std::env::var("XDG_SESSION_ID") {
|
||||
// could also execute "cat /proc/self/sessionid"
|
||||
session = sid;
|
||||
}
|
||||
if session.is_empty() {
|
||||
session = run_cmds("cat /proc/self/sessionid").unwrap_or_default();
|
||||
if session == INVALID_SESSION {
|
||||
session = "".to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
if session.is_empty() {
|
||||
std::env::var("XDG_SESSION_TYPE").unwrap_or("x11".to_owned())
|
||||
} else {
|
||||
get_display_server_of_session(&session)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_display_server_of_session(session: &str) -> String {
|
||||
let mut display_server = if let Ok(output) =
|
||||
run_loginctl(Some(vec!["show-session", "-p", "Type", session]))
|
||||
// Check session type of the session
|
||||
{
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
.replace("Type=", "")
|
||||
.trim_end()
|
||||
.into()
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
if display_server.is_empty() || display_server == "tty" || display_server == "unspecified" {
|
||||
if let Ok(sestype) = std::env::var("XDG_SESSION_TYPE") {
|
||||
if !sestype.is_empty() {
|
||||
return sestype.to_lowercase();
|
||||
}
|
||||
}
|
||||
display_server = "x11".to_owned();
|
||||
}
|
||||
display_server.to_lowercase()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn line_values(indices: &[usize], line: &str) -> Vec<String> {
|
||||
indices
|
||||
.into_iter()
|
||||
.map(|idx| line.split_whitespace().nth(*idx).unwrap_or("").to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_values_of_seat0(indices: &[usize]) -> Vec<String> {
|
||||
_get_values_of_seat0(indices, true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_values_of_seat0_with_gdm_wayland(indices: &[usize]) -> Vec<String> {
|
||||
_get_values_of_seat0(indices, false)
|
||||
}
|
||||
|
||||
// Ignore "3 sessions listed."
|
||||
fn ignore_loginctl_line(line: &str) -> bool {
|
||||
line.contains("sessions") || line.split(" ").count() < 4
|
||||
}
|
||||
|
||||
fn _get_values_of_seat0(indices: &[usize], ignore_gdm_wayland: bool) -> Vec<String> {
|
||||
if let Ok(output) = run_loginctl(None) {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if ignore_loginctl_line(line) {
|
||||
continue;
|
||||
}
|
||||
if line.contains("seat0") {
|
||||
if let Some(sid) = line.split_whitespace().next() {
|
||||
if is_active(sid) {
|
||||
if ignore_gdm_wayland {
|
||||
if is_gdm_user(line.split_whitespace().nth(2).unwrap_or(""))
|
||||
&& get_display_server_of_session(sid) == DISPLAY_SERVER_WAYLAND
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return line_values(indices, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if ignore_loginctl_line(line) {
|
||||
continue;
|
||||
}
|
||||
if let Some(sid) = line.split_whitespace().next() {
|
||||
if is_active(sid) {
|
||||
let d = get_display_server_of_session(sid);
|
||||
if ignore_gdm_wayland {
|
||||
if is_gdm_user(line.split_whitespace().nth(2).unwrap_or(""))
|
||||
&& d == DISPLAY_SERVER_WAYLAND
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if d == "tty" || d == "unspecified" {
|
||||
continue;
|
||||
}
|
||||
return line_values(indices, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line_values(indices, "")
|
||||
}
|
||||
|
||||
pub fn is_active(sid: &str) -> bool {
|
||||
if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid])) {
|
||||
String::from_utf8_lossy(&output.stdout).contains("active")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_active_and_seat0(sid: &str) -> bool {
|
||||
if let Ok(output) = run_loginctl(Some(vec!["show-session", sid])) {
|
||||
String::from_utf8_lossy(&output.stdout).contains("State=active")
|
||||
&& String::from_utf8_lossy(&output.stdout).contains("Seat=seat0")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Check both "Lock" and "Switch user"
|
||||
pub fn is_session_locked(sid: &str) -> bool {
|
||||
if let Ok(output) = run_loginctl(Some(vec!["show-session", sid, "--property=LockedHint"])) {
|
||||
String::from_utf8_lossy(&output.stdout).contains("LockedHint=yes")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// **Note** that the return value here, the last character is '\n'.
|
||||
// Use `run_cmds_trim_newline()` if you want to remove '\n' at the end.
|
||||
pub fn run_cmds(cmds: &str) -> ResultType<String> {
|
||||
let output = std::process::Command::new(CMD_SH.as_str())
|
||||
.args(vec!["-c", cmds])
|
||||
.output()?;
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
pub fn run_cmds_trim_newline(cmds: &str) -> ResultType<String> {
|
||||
let output = std::process::Command::new(CMD_SH.as_str())
|
||||
.args(vec!["-c", cmds])
|
||||
.output()?;
|
||||
let out = String::from_utf8_lossy(&output.stdout);
|
||||
Ok(if out.ends_with('\n') {
|
||||
out[..out.len() - 1].to_string()
|
||||
} else {
|
||||
out.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
|
||||
if std::env::var("FLATPAK_ID").is_ok() {
|
||||
let mut l_args = CMD_LOGINCTL.to_string();
|
||||
if let Some(a) = args.as_ref() {
|
||||
l_args = format!("{} {}", l_args, a.join(" "));
|
||||
}
|
||||
let res = std::process::Command::new("flatpak-spawn")
|
||||
.args(vec![String::from("--host"), l_args])
|
||||
.output();
|
||||
if res.is_ok() {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
let mut cmd = std::process::Command::new(CMD_LOGINCTL.as_str());
|
||||
if let Some(a) = args {
|
||||
return cmd.args(a).output();
|
||||
}
|
||||
cmd.output()
|
||||
}
|
||||
|
||||
/// forever: may not work
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn system_message(title: &str, msg: &str, forever: bool) -> ResultType<()> {
|
||||
let cmds: HashMap<&str, Vec<&str>> = HashMap::from([
|
||||
("notify-send", [title, msg].to_vec()),
|
||||
(
|
||||
"zenity",
|
||||
[
|
||||
"--info",
|
||||
"--timeout",
|
||||
if forever { "0" } else { "3" },
|
||||
"--title",
|
||||
title,
|
||||
"--text",
|
||||
msg,
|
||||
]
|
||||
.to_vec(),
|
||||
),
|
||||
("kdialog", ["--title", title, "--msgbox", msg].to_vec()),
|
||||
(
|
||||
"xmessage",
|
||||
[
|
||||
"-center",
|
||||
"-timeout",
|
||||
if forever { "0" } else { "3" },
|
||||
title,
|
||||
msg,
|
||||
]
|
||||
.to_vec(),
|
||||
),
|
||||
]);
|
||||
for (k, v) in cmds {
|
||||
if Command::new(k).args(v).spawn().is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
crate::bail!("failed to post system message");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde_derive::Serialize, serde_derive::Deserialize)]
|
||||
pub struct WaylandDisplayInfo {
|
||||
pub name: String,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub logical_size: Option<(i32, i32)>,
|
||||
pub refresh_rate: i32,
|
||||
/// Output rotation in degrees (0/90/180/270), from `wl_output.geometry`. The mode keeps its
|
||||
/// unrotated dimensions and `logical_size` arrives already swapped, so without this field a
|
||||
/// rotated output is indistinguishable from a scaled one. Flipped variants map to their
|
||||
/// rotation. Defaulted so a serialized snapshot from an older probe child still deserializes.
|
||||
#[serde(default)]
|
||||
pub transform: i32,
|
||||
}
|
||||
|
||||
/// The isolated socket-probe fallback, in its own file and behind the `wayland_probe` feature so
|
||||
/// the base Wayland path never compiles it. The DRM login-screen build turns it on.
|
||||
#[cfg(feature = "wayland_probe")]
|
||||
pub mod wayland_probe;
|
||||
#[cfg(feature = "wayland_probe")]
|
||||
pub use wayland_probe::{wayland_display_probe_child_main, WAYLAND_DISPLAY_PROBE_ARG};
|
||||
|
||||
// Retrieves information about all connected displays via the Wayland protocol.
|
||||
pub fn get_wayland_displays() -> ResultType<Vec<WaylandDisplayInfo>> {
|
||||
// Read before connecting: `connect_to_env` consumes `WAYLAND_SOCKET`. Only the probe fallback
|
||||
// needs this, so it is computed only when that feature is compiled in.
|
||||
#[cfg(feature = "wayland_probe")]
|
||||
let named_endpoint = wayland_probe::env_names_wayland_endpoint();
|
||||
match Connection::connect_to_env() {
|
||||
Ok(conn) => collect_wayland_displays(&conn),
|
||||
// Without the feature, the connect error is final, exactly as before this fallback existed.
|
||||
#[cfg(not(feature = "wayland_probe"))]
|
||||
Err(err) => Err(err.into()),
|
||||
#[cfg(feature = "wayland_probe")]
|
||||
Err(err) => wayland_probe::wayland_displays_from_runtime_dir(named_endpoint)
|
||||
.map_err(|fallback_err| anyhow::anyhow!("{err}; {fallback_err}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// `wl_output::Transform` as degrees. Flipped variants report their rotation ONLY: wayland
|
||||
/// defines them as a vertical-axis mirror followed by the rotation, and the mirror half is
|
||||
/// dropped here - a consumer correcting frames by this value serves a flipped output mirrored.
|
||||
/// Said once in the log rather than silently, because no compositor of ours produces a flipped
|
||||
/// output to measure the mirror half against; carrying it must wait for a measured producer.
|
||||
fn transform_degrees(t: sctk::reexports::client::protocol::wl_output::Transform) -> i32 {
|
||||
use sctk::reexports::client::protocol::wl_output::Transform;
|
||||
match t {
|
||||
Transform::Normal => 0,
|
||||
Transform::_90 => 90,
|
||||
Transform::_180 => 180,
|
||||
Transform::_270 => 270,
|
||||
Transform::Flipped | Transform::Flipped90 | Transform::Flipped180
|
||||
| Transform::Flipped270 => {
|
||||
static FLIPPED_WARNED: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
if !FLIPPED_WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||||
log::warn!(
|
||||
"an output reports a flipped transform ({t:?}); only its rotation is \
|
||||
corrected, the mirror is not"
|
||||
);
|
||||
}
|
||||
match t {
|
||||
Transform::Flipped90 => 90,
|
||||
Transform::Flipped180 => 180,
|
||||
Transform::Flipped270 => 270,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_wayland_displays(conn: &Connection) -> ResultType<Vec<WaylandDisplayInfo>> {
|
||||
struct WaylandEnv {
|
||||
registry_state: RegistryState,
|
||||
output_state: OutputState,
|
||||
}
|
||||
|
||||
impl OutputHandler for WaylandEnv {
|
||||
fn output_state(&mut self) -> &mut OutputState {
|
||||
&mut self.output_state
|
||||
}
|
||||
|
||||
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
|
||||
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
|
||||
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
|
||||
}
|
||||
|
||||
impl ProvidesRegistryState for WaylandEnv {
|
||||
fn registry(&mut self) -> &mut RegistryState {
|
||||
&mut self.registry_state
|
||||
}
|
||||
|
||||
sctk::registry_handlers![OutputState];
|
||||
}
|
||||
|
||||
sctk::delegate_output!(WaylandEnv);
|
||||
sctk::delegate_registry!(WaylandEnv);
|
||||
|
||||
let (globals, mut event_queue) = globals::registry_queue_init(conn)?;
|
||||
let queue_handle = event_queue.handle();
|
||||
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
let output_state = OutputState::new(&globals, &queue_handle);
|
||||
|
||||
let mut environment = WaylandEnv {
|
||||
registry_state,
|
||||
output_state,
|
||||
};
|
||||
|
||||
event_queue.roundtrip(&mut environment)?;
|
||||
|
||||
let outputs: Vec<_> = environment.output_state.outputs().collect();
|
||||
let mut display_infos = Vec::new();
|
||||
|
||||
for output in outputs {
|
||||
if let Some(output_data) = output.data::<OutputData>() {
|
||||
output_data.with_output_info(|info| {
|
||||
if let Some(mode) = info.modes.iter().find(|m| m.current) {
|
||||
// wlroots compositors leave wl_output.geometry at (0, 0) for every output and
|
||||
// publish the real layout only through xdg-output, so taking `location` there
|
||||
// stacks the whole desktop on the origin. Mutter fills both, so this stays a
|
||||
// no-op on GNOME.
|
||||
let (x, y) = info.logical_position.unwrap_or(info.location);
|
||||
let (width, height) = mode.dimensions;
|
||||
let refresh_rate = mode.refresh_rate;
|
||||
let name = info.name.clone().unwrap_or_default();
|
||||
let logical_size = info.logical_size;
|
||||
let transform = transform_degrees(info.transform);
|
||||
display_infos.push(WaylandDisplayInfo {
|
||||
name,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
logical_size,
|
||||
refresh_rate,
|
||||
transform,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(display_infos)
|
||||
}
|
||||
|
||||
/// Escape a string for safe use in shell commands by wrapping in single quotes.
|
||||
///
|
||||
/// This function handles the edge case of single quotes within the string by:
|
||||
/// 1. Ending the current single-quoted section
|
||||
/// 2. Adding an escaped single quote
|
||||
/// 3. Starting a new single-quoted section
|
||||
///
|
||||
/// Example: "it's here" -> "'it'\''s here'"
|
||||
#[inline]
|
||||
pub fn shell_quote(s: &str) -> String {
|
||||
format!("'{}'", s.replace("'", "'\\''"))
|
||||
}
|
||||
|
||||
/// Get the current user's home directory via getpwuid (trusted source).
|
||||
///
|
||||
/// This function uses the system's password database (via `getpwuid`) to retrieve
|
||||
/// the home directory, avoiding the security risk of relying on the `HOME`
|
||||
/// environment variable which can be manipulated by untrusted input.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Some(PathBuf)` if the home directory was found and exists
|
||||
/// - `None` if the user lookup failed or the directory doesn't exist
|
||||
///
|
||||
/// # Security
|
||||
/// This function is designed to be safe against confused-deputy attacks where
|
||||
/// an attacker might manipulate environment variables to influence privileged
|
||||
/// operations.
|
||||
pub fn get_home_dir_trusted() -> Option<PathBuf> {
|
||||
let uid = get_current_uid();
|
||||
match get_user_by_uid(uid) {
|
||||
Some(user) => {
|
||||
let home = user.home_dir();
|
||||
if Path::is_dir(home) {
|
||||
Some(PathBuf::from(home))
|
||||
} else {
|
||||
log::warn!(
|
||||
"Home directory for uid {} does not exist or is not a directory: {:?}",
|
||||
uid,
|
||||
home
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("Failed to get user info for uid {}", uid);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_transform_degrees_maps_all_eight_variants() {
|
||||
use sctk::reexports::client::protocol::wl_output::Transform;
|
||||
// Flipped variants report their rotation: the frame still needs that turn to read
|
||||
// upright, and the mirror half has no producer among desktop compositors to test.
|
||||
for (t, deg) in [
|
||||
(Transform::Normal, 0),
|
||||
(Transform::_90, 90),
|
||||
(Transform::_180, 180),
|
||||
(Transform::_270, 270),
|
||||
(Transform::Flipped, 0),
|
||||
(Transform::Flipped90, 90),
|
||||
(Transform::Flipped180, 180),
|
||||
(Transform::Flipped270, 270),
|
||||
] {
|
||||
assert_eq!(transform_degrees(t), deg, "{t:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_info_without_transform_defaults_to_zero() {
|
||||
// A snapshot serialized by an older probe child carries no transform field; it must
|
||||
// deserialize with 0 rather than fail, or a greeter-side child update becomes a
|
||||
// lockstep upgrade.
|
||||
let old = r#"{"name":"HDMI-1","x":0,"y":0,"width":1920,"height":1080,"logical_size":null,"refresh_rate":60}"#;
|
||||
let info: WaylandDisplayInfo = serde_json::from_str(old).unwrap();
|
||||
assert_eq!(info.transform, 0);
|
||||
let roundtrip: WaylandDisplayInfo =
|
||||
serde_json::from_str(&serde_json::to_string(&info).unwrap()).unwrap();
|
||||
assert_eq!(roundtrip.transform, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_cmds_trim_newline() {
|
||||
assert_eq!(run_cmds_trim_newline("echo -n 123").unwrap(), "123");
|
||||
assert_eq!(run_cmds_trim_newline("echo 123").unwrap(), "123");
|
||||
assert_eq!(
|
||||
run_cmds_trim_newline("whoami").unwrap() + "\n",
|
||||
run_cmds("whoami").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
/// Test get_home_dir_trusted: returns valid path and ignores HOME env var
|
||||
#[test]
|
||||
fn test_get_home_dir_trusted() {
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
|
||||
// Set HOME to a fake/malicious path
|
||||
std::env::set_var("HOME", "/tmp/fake_malicious_home");
|
||||
let result = get_home_dir_trusted();
|
||||
|
||||
// Restore original HOME
|
||||
match original_home {
|
||||
Some(home) => std::env::set_var("HOME", home),
|
||||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
|
||||
// Verify: returns valid path that is NOT the fake HOME
|
||||
if let Some(path) = result {
|
||||
assert!(path.is_absolute(), "Path should be absolute: {:?}", path);
|
||||
assert!(path.is_dir(), "Path should be a directory: {:?}", path);
|
||||
assert_ne!(
|
||||
path.to_string_lossy(),
|
||||
"/tmp/fake_malicious_home",
|
||||
"Should not use HOME env var"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test shell_quote with normal strings
|
||||
#[test]
|
||||
fn test_shell_quote_normal() {
|
||||
assert_eq!(shell_quote("hello"), "'hello'");
|
||||
assert_eq!(shell_quote("/home/user"), "'/home/user'");
|
||||
}
|
||||
|
||||
/// Test shell_quote with spaces
|
||||
#[test]
|
||||
fn test_shell_quote_spaces() {
|
||||
assert_eq!(shell_quote("/home/my user/file"), "'/home/my user/file'");
|
||||
assert_eq!(shell_quote("path with spaces"), "'path with spaces'");
|
||||
}
|
||||
|
||||
/// Test shell_quote with single quotes (the tricky case)
|
||||
#[test]
|
||||
fn test_shell_quote_single_quotes() {
|
||||
assert_eq!(shell_quote("it's"), "'it'\\''s'");
|
||||
assert_eq!(shell_quote("don't stop"), "'don'\\''t stop'");
|
||||
}
|
||||
|
||||
/// Test shell_quote with shell metacharacters
|
||||
#[test]
|
||||
fn test_shell_quote_metacharacters() {
|
||||
// These should all be safely quoted
|
||||
assert_eq!(shell_quote("test;rm -rf /"), "'test;rm -rf /'");
|
||||
assert_eq!(shell_quote("$(whoami)"), "'$(whoami)'");
|
||||
assert_eq!(shell_quote("`id`"), "'`id`'");
|
||||
assert_eq!(shell_quote("a && b"), "'a && b'");
|
||||
assert_eq!(shell_quote("a | b"), "'a | b'");
|
||||
}
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
//! Isolated Wayland display probe: enumerates a compositor over a runtime-directory socket when
|
||||
//! the environment names no endpoint (a greeter's `--server` and the root service are given no
|
||||
//! compositor variables). Gated behind the `wayland_probe` feature so the base Wayland path is
|
||||
//! untouched — a consumer that does not build the DRM login-screen backend never compiles this,
|
||||
//! and `get_wayland_displays` keeps its original behavior of returning the connect error.
|
||||
|
||||
use super::{collect_wayland_displays, get_values_of_seat0_with_gdm_wayland, WaylandDisplayInfo};
|
||||
use crate::{bail, ResultType};
|
||||
use sctk::reexports::client::Connection;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const RUNTIME_DIR_PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
|
||||
|
||||
/// The argument the consumer binary must dispatch to `wayland_display_probe_child_main` before
|
||||
/// any other startup work; see that function for why the probe is its own process.
|
||||
pub const WAYLAND_DISPLAY_PROBE_ARG: &str = "--wayland-display-probe";
|
||||
|
||||
/// First stdout line of a probe child. A binary that does not dispatch the arg never prints it.
|
||||
const WAYLAND_PROBE_MAGIC: &str = "wayland-display-probe-v1";
|
||||
|
||||
/// Latched on a failed handshake: a consumer that does not dispatch the probe arg runs its NORMAL
|
||||
/// startup instead, and this path re-enters every enumeration cycle.
|
||||
static PROBE_UNSUPPORTED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
static RUNTIME_DIR_PROBE_BUSY: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
/// Clears the in-flight flag on every exit path of the parent, error arms included.
|
||||
struct ProbeBusyGuard;
|
||||
|
||||
impl Drop for ProbeBusyGuard {
|
||||
fn drop(&mut self) {
|
||||
RUNTIME_DIR_PROBE_BUSY.store(false, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point of the isolated probe process. The consumer binary dispatches
|
||||
/// `WAYLAND_DISPLAY_PROBE_ARG` here first, before config, logging or any other startup work.
|
||||
///
|
||||
/// Its own process because the release profile builds with panic=abort: sctk panics on malformed
|
||||
/// protocol bytes, and in-process that abort takes the whole server down. Here it takes down only
|
||||
/// this child, which the parent reports as a failed probe. The seat0 lookup also runs in here, so
|
||||
/// the parent's single deadline bounds the loginctl reads too.
|
||||
pub fn wayland_display_probe_child_main() -> ! {
|
||||
use std::io::Write;
|
||||
// The handshake first, so the parent can tell this entry point ran and not a consumer binary
|
||||
// that fell through to its normal startup.
|
||||
println!("{WAYLAND_PROBE_MAGIC}");
|
||||
let _ = std::io::stdout().flush();
|
||||
let code = match seat0_runtime_dir()
|
||||
.and_then(|dir| {
|
||||
drop_to_dir_owner(&dir)?;
|
||||
probe_runtime_dir(&dir)
|
||||
})
|
||||
.and_then(|displays| serde_json::to_string(&displays).map_err(anyhow::Error::from))
|
||||
{
|
||||
Ok(json) => {
|
||||
println!("{json}");
|
||||
0
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{err:#}");
|
||||
1
|
||||
}
|
||||
};
|
||||
let _ = std::io::stdout().flush();
|
||||
std::process::exit(code)
|
||||
}
|
||||
|
||||
static ENDPOINT_WAS_NAMED: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
/// Whether the environment ever named a wayland endpoint in this process. Empty is not a name.
|
||||
///
|
||||
/// Read before `connect_to_env`, which removes `WAYLAND_SOCKET` from the environment on both its
|
||||
/// success and its bad-fd path; and latched, so a consumed variable cannot turn a process that WAS
|
||||
/// pointed at a compositor into one that is free to go looking for another.
|
||||
pub(super) fn env_names_wayland_endpoint() -> bool {
|
||||
use std::sync::atomic::Ordering;
|
||||
let named = ["WAYLAND_DISPLAY", "WAYLAND_SOCKET"]
|
||||
.iter()
|
||||
.any(|key| std::env::var_os(key).is_some_and(|value| !value.is_empty()));
|
||||
if named {
|
||||
ENDPOINT_WAS_NAMED.store(true, Ordering::Release);
|
||||
}
|
||||
ENDPOINT_WAS_NAMED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// The probe parses compositor-controlled protocol data; a root service must not do that as
|
||||
/// root. Before touching the socket, become the runtime directory's owner — and refuse to probe
|
||||
/// at all if the drop fails, since staying root is the one unacceptable outcome.
|
||||
fn drop_to_dir_owner(dir: &Path) -> ResultType<()> {
|
||||
if unsafe { libc::geteuid() } != 0 {
|
||||
return Ok(());
|
||||
}
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let meta = std::fs::metadata(dir)?;
|
||||
let (uid, gid) = (meta.uid(), meta.gid());
|
||||
if uid == 0 {
|
||||
// Root's own session: there is no boundary to cross and nothing to drop to.
|
||||
return Ok(());
|
||||
}
|
||||
unsafe {
|
||||
if libc::setgroups(0, std::ptr::null()) != 0
|
||||
|| libc::setgid(gid) != 0
|
||||
|| libc::setuid(uid) != 0
|
||||
|| libc::setuid(0) == 0
|
||||
{
|
||||
bail!("could not drop privileges for the socket probe");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `/run/user/<uid>` of the active seat0 session, a greeter included.
|
||||
///
|
||||
/// Derived from the uid rather than read from `XDG_RUNTIME_DIR`: the root service is given no such
|
||||
/// variable, and `get_home_dir_trusted` refuses to trust the environment for the same reason.
|
||||
fn seat0_runtime_dir() -> ResultType<PathBuf> {
|
||||
let uid = get_values_of_seat0_with_gdm_wayland(&[1]).remove(0);
|
||||
if uid.is_empty() || !uid.bytes().all(|b| b.is_ascii_digit()) {
|
||||
bail!("no active seat0 session to take a runtime directory from");
|
||||
}
|
||||
Ok(PathBuf::from(format!("/run/user/{uid}")))
|
||||
}
|
||||
|
||||
/// The wayland sockets present in `dir`, lowest display number first.
|
||||
///
|
||||
/// Scanned rather than guessed: `wl_display_add_socket_auto` takes the first FREE name up to
|
||||
/// `wayland-32`, and a greeter is where leftovers accumulate across compositor restarts. Only that
|
||||
/// name pattern, because the same directory holds pipewire and dbus sockets.
|
||||
fn wayland_sockets_in(dir: &Path) -> Vec<PathBuf> {
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
let mut paths: Vec<PathBuf> = match std::fs::read_dir(dir) {
|
||||
Ok(entries) => entries
|
||||
.flatten()
|
||||
.filter(|entry| {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
name.starts_with("wayland-")
|
||||
&& !name.ends_with(".lock")
|
||||
&& entry.file_type().map(|t| t.is_socket()).unwrap_or(false)
|
||||
})
|
||||
.map(|entry| entry.path())
|
||||
.collect(),
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
paths.sort_by_key(|path| {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.and_then(|name| name.strip_prefix("wayland-"))
|
||||
.and_then(|number| number.parse::<u32>().ok())
|
||||
.unwrap_or(u32::MAX)
|
||||
});
|
||||
paths
|
||||
}
|
||||
|
||||
/// Enumerate through a socket in the seat0 runtime directory, for the case where nothing named an
|
||||
/// endpoint: a greeter's `--server` and the root service are given no compositor variables, so
|
||||
/// nothing tells the enumerator where a compositor that IS running lives. An endpoint that WAS
|
||||
/// named and failed must not silently reattach to a different compositor.
|
||||
///
|
||||
/// In a subprocess and bounded, because the caller holds a process-wide lock across the call while
|
||||
/// `connect(2)` parks on a full backlog and sctk's roundtrip polls without a deadline; and because
|
||||
/// sctk panics on malformed output events, which the release profile's panic=abort turns into an
|
||||
/// abort of the whole server. A child dies alone, and on the deadline it is killed instead of
|
||||
/// leaking a thread. The seat0 lookup runs inside the child, under the same deadline.
|
||||
pub(super) fn wayland_displays_from_runtime_dir(
|
||||
named_endpoint: bool,
|
||||
) -> ResultType<Vec<WaylandDisplayInfo>> {
|
||||
use std::sync::atomic::Ordering;
|
||||
if named_endpoint {
|
||||
bail!("an explicit wayland endpoint is set and did not connect");
|
||||
}
|
||||
if PROBE_UNSUPPORTED.load(Ordering::Acquire) {
|
||||
bail!("this binary does not dispatch {WAYLAND_DISPLAY_PROBE_ARG}");
|
||||
}
|
||||
if RUNTIME_DIR_PROBE_BUSY.swap(true, Ordering::AcqRel) {
|
||||
bail!("an earlier probe has not returned");
|
||||
}
|
||||
let _busy = ProbeBusyGuard;
|
||||
let exe = std::env::current_exe()?;
|
||||
// Its own process group, so the deadline can kill loginctl descendants along with the child,
|
||||
// and so no surviving descendant can hold the pipes open past the reads below.
|
||||
use std::os::unix::process::CommandExt;
|
||||
let mut child = std::process::Command::new(exe)
|
||||
.arg(WAYLAND_DISPLAY_PROBE_ARG)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.process_group(0)
|
||||
.spawn()?;
|
||||
let probe_pgid = child.id() as libc::pid_t;
|
||||
let kill_probe_group = || unsafe {
|
||||
let _ = libc::kill(-probe_pgid, libc::SIGKILL);
|
||||
};
|
||||
let deadline = std::time::Instant::now() + RUNTIME_DIR_PROBE_TIMEOUT;
|
||||
let status = loop {
|
||||
match child.try_wait()? {
|
||||
Some(status) => {
|
||||
kill_probe_group();
|
||||
break status;
|
||||
}
|
||||
None if std::time::Instant::now() >= deadline => {
|
||||
kill_probe_group();
|
||||
// The direct pid too, not only its group: if the child left the group its own
|
||||
// kill would miss it, and the wait below would then block on a live child. A
|
||||
// pid-targeted SIGKILL is uncatchable, so wait() is bounded either way.
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
// An unwired binary runs its normal startup, and a long-running one (the
|
||||
// server itself) lands HERE rather than at the handshake check below — latch
|
||||
// on this path too, or every enumeration cycle spawns a full consumer
|
||||
// process. Judged by what the child already wrote: a real probe prints the
|
||||
// magic line first and flushes, so its absence after a whole deadline means
|
||||
// this is not a probe. Only buffered bytes are read — a blocking read could
|
||||
// hang on a grandchild that inherited the write end.
|
||||
match first_buffered_line(child.stdout.take()) {
|
||||
// The pipe could not be inspected at all: no evidence, no latch.
|
||||
None => {
|
||||
bail!("the wayland socket probe timed out and its output was uninspectable")
|
||||
}
|
||||
Some(head) if head.as_deref() == Some(WAYLAND_PROBE_MAGIC) => {
|
||||
bail!("the wayland socket probe did not answer and was killed");
|
||||
}
|
||||
Some(_) => {
|
||||
PROBE_UNSUPPORTED.store(true, Ordering::Release);
|
||||
bail!("the wayland socket probe timed out without the handshake; probe disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
None => std::thread::sleep(std::time::Duration::from_millis(25)),
|
||||
}
|
||||
};
|
||||
// Drained non-blocking, not read_to_string: the child exited so its output is already
|
||||
// buffered, but a descendant that escaped the process group could still hold a write end open
|
||||
// and an EOF-seeking read would then hang here forever.
|
||||
let stdout = drain_nonblocking(child.stdout.take()).unwrap_or_default();
|
||||
let stderr = drain_nonblocking(child.stderr.take()).unwrap_or_default();
|
||||
let mut lines = stdout.lines();
|
||||
if lines.next() != Some(WAYLAND_PROBE_MAGIC) {
|
||||
// Not a probe: the binary ran its normal startup. Latch, or this path would spawn one
|
||||
// full consumer process per enumeration cycle.
|
||||
PROBE_UNSUPPORTED.store(true, Ordering::Release);
|
||||
bail!("this binary does not dispatch {WAYLAND_DISPLAY_PROBE_ARG}; probe disabled");
|
||||
}
|
||||
if !status.success() {
|
||||
let detail = stderr.trim();
|
||||
if detail.is_empty() {
|
||||
// panic=abort or a signal leaves stderr empty; the status is then the only cause.
|
||||
bail!("wayland socket probe failed: {status}");
|
||||
}
|
||||
bail!("wayland socket probe failed ({status}): {detail}");
|
||||
}
|
||||
let displays: Vec<WaylandDisplayInfo> =
|
||||
match serde_json::from_str(lines.next().unwrap_or_default()) {
|
||||
Ok(displays) => displays,
|
||||
Err(err) => bail!("wayland socket probe answered a malformed list: {err}"),
|
||||
};
|
||||
// The child already refuses an empty list; refuse it here too, so a truncated pipe cannot
|
||||
// become a cached-for-life empty enumeration.
|
||||
if displays.is_empty() {
|
||||
bail!("wayland socket probe returned no outputs");
|
||||
}
|
||||
log::debug!(
|
||||
"wayland: {} output(s) via the probe subprocess",
|
||||
displays.len()
|
||||
);
|
||||
Ok(displays)
|
||||
}
|
||||
|
||||
/// Everything already buffered in the pipe, read strictly non-blocking and capped: a descendant
|
||||
/// that escaped the probe's process group can hold a write end open, so a blocking read (even
|
||||
/// after the child exits) could hang the enumeration forever. `None` means the pipe could not be
|
||||
/// INSPECTED (missing handle or fcntl failure) and must not be read as evidence of anything;
|
||||
/// `Some` is whatever bytes were buffered, whether or not EOF arrived.
|
||||
fn drain_nonblocking<R: std::io::Read + std::os::fd::AsRawFd>(pipe: Option<R>) -> Option<String> {
|
||||
let mut pipe = pipe?;
|
||||
let fd = pipe.as_raw_fd();
|
||||
unsafe {
|
||||
let flags = libc::fcntl(fd, libc::F_GETFL);
|
||||
if flags < 0 || libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Capped so a descendant that keeps writing cannot spin this read forever.
|
||||
const CAP: usize = 64 * 1024;
|
||||
let mut out = Vec::new();
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
match pipe.read(&mut buf) {
|
||||
Ok(0) => break, // EOF: the write end is fully closed
|
||||
Ok(n) => {
|
||||
out.extend_from_slice(&buf[..n]);
|
||||
if out.len() >= CAP {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue,
|
||||
// WouldBlock: what is buffered is drained (a descendant may still hold the writer).
|
||||
// Any other error: stop with what we have.
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
Some(String::from_utf8_lossy(&out).into_owned())
|
||||
}
|
||||
|
||||
/// The first line the child buffered, for the timeout latch decision. `Some(None)` is an
|
||||
/// inspected-but-empty buffer (genuine absence of the handshake); outer `None` is uninspectable.
|
||||
fn first_buffered_line(pipe: Option<std::process::ChildStdout>) -> Option<Option<String>> {
|
||||
drain_nonblocking(pipe).map(|s| s.lines().next().map(str::to_owned))
|
||||
}
|
||||
|
||||
fn probe_runtime_dir(dir: &Path) -> ResultType<Vec<WaylandDisplayInfo>> {
|
||||
use std::os::unix::net::UnixStream;
|
||||
let mut errs = Vec::new();
|
||||
for path in wayland_sockets_in(dir) {
|
||||
match UnixStream::connect(&path)
|
||||
.map_err(anyhow::Error::from)
|
||||
.and_then(|s| Connection::from_socket(s).map_err(anyhow::Error::from))
|
||||
.and_then(|conn| collect_wayland_displays(&conn))
|
||||
{
|
||||
// The caller caches an empty list as ground truth for the process lifetime, and a
|
||||
// compositor still probing its monitors is exactly what this path connects to.
|
||||
Ok(displays) if displays.is_empty() => {
|
||||
errs.push(format!("{}: no outputs yet", path.display()))
|
||||
}
|
||||
Ok(displays) => {
|
||||
// Which socket answered, when nothing in the environment named one.
|
||||
log::debug!(
|
||||
"wayland: {} output(s) from {}, found by scanning",
|
||||
displays.len(),
|
||||
path.display()
|
||||
);
|
||||
return Ok(displays);
|
||||
}
|
||||
Err(err) => errs.push(format!("{}: {err}", path.display())),
|
||||
}
|
||||
}
|
||||
bail!(
|
||||
"no usable wayland socket in {} ({})",
|
||||
dir.display(),
|
||||
if errs.is_empty() {
|
||||
"none present".to_owned()
|
||||
} else {
|
||||
errs.join("; ")
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use crate::ResultType;
|
||||
use osascript;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AlertParams {
|
||||
title: String,
|
||||
message: String,
|
||||
alert_type: String,
|
||||
buttons: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AlertResult {
|
||||
#[serde(rename = "buttonReturned")]
|
||||
button: String,
|
||||
}
|
||||
|
||||
/// Firstly run the specified app, then alert a dialog. Return the clicked button value.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `app` - The app to execute the script.
|
||||
/// * `alert_type` - Alert type. . informational, warning, critical
|
||||
/// * `title` - The alert title.
|
||||
/// * `message` - The alert message.
|
||||
/// * `buttons` - The buttons to show.
|
||||
pub fn alert(
|
||||
app: String,
|
||||
alert_type: String,
|
||||
title: String,
|
||||
message: String,
|
||||
buttons: Vec<String>,
|
||||
) -> ResultType<String> {
|
||||
let script = osascript::JavaScript::new(&format!(
|
||||
"
|
||||
var App = Application('{}');
|
||||
App.includeStandardAdditions = true;
|
||||
return App.displayAlert($params.title, {{
|
||||
message: $params.message,
|
||||
'as': $params.alert_type,
|
||||
buttons: $params.buttons,
|
||||
}});
|
||||
",
|
||||
app
|
||||
));
|
||||
|
||||
let result: AlertResult = script.execute_with_params(AlertParams {
|
||||
title,
|
||||
message,
|
||||
alert_type,
|
||||
buttons,
|
||||
})?;
|
||||
Ok(result.button)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
use crate::{config::Config, log};
|
||||
#[cfg(not(debug_assertions))]
|
||||
use std::process::exit;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
static mut GLOBAL_CALLBACK: Option<Box<dyn Fn()>> = None;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
extern "C" fn breakdown_signal_handler(sig: i32) {
|
||||
let mut stack = vec![];
|
||||
backtrace::trace(|frame| {
|
||||
backtrace::resolve_frame(frame, |symbol| {
|
||||
if let Some(name) = symbol.name() {
|
||||
stack.push(name.to_string());
|
||||
}
|
||||
});
|
||||
true // keep going to the next frame
|
||||
});
|
||||
let mut info = String::default();
|
||||
if stack.iter().any(|s| {
|
||||
s.contains(&"nouveau_pushbuf_kick")
|
||||
|| s.to_lowercase().contains("nvidia")
|
||||
|| s.contains("gdk_window_end_draw_frame")
|
||||
|| s.contains("glGetString")
|
||||
}) {
|
||||
Config::set_option("allow-always-software-render".to_string(), "Y".to_string());
|
||||
info = "Always use software rendering will be set.".to_string();
|
||||
log::info!("{}", info);
|
||||
}
|
||||
if stack.iter().any(|s| {
|
||||
s.to_lowercase().contains("nvidia")
|
||||
|| s.to_lowercase().contains("amf")
|
||||
|| s.to_lowercase().contains("mfx")
|
||||
|| s.contains("cuProfilerStop")
|
||||
}) {
|
||||
Config::set_option("enable-hwcodec".to_string(), "N".to_string());
|
||||
info = "Perhaps hwcodec causing the crash, disable it first".to_string();
|
||||
log::info!("{}", info);
|
||||
}
|
||||
log::error!(
|
||||
"Got signal {} and exit. stack:\n{}",
|
||||
sig,
|
||||
stack.join("\n").to_string()
|
||||
);
|
||||
if !info.is_empty() {
|
||||
#[cfg(target_os = "linux")]
|
||||
linux::system_message(
|
||||
"RustDesk",
|
||||
&format!("Got signal {} and exit.{}", sig, info),
|
||||
true,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
if let Some(callback) = &GLOBAL_CALLBACK {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn register_breakdown_handler<T>(callback: T)
|
||||
where
|
||||
T: Fn() + 'static,
|
||||
{
|
||||
unsafe {
|
||||
GLOBAL_CALLBACK = Some(Box::new(callback));
|
||||
libc::signal(libc::SIGSEGV, breakdown_signal_handler as _);
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
use winapi::{
|
||||
shared::minwindef::{DWORD, FALSE, TRUE},
|
||||
um::{
|
||||
handleapi::CloseHandle,
|
||||
pdh::{
|
||||
PdhAddEnglishCounterA, PdhCloseQuery, PdhCollectQueryData, PdhCollectQueryDataEx,
|
||||
PdhGetFormattedCounterValue, PdhOpenQueryA, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE,
|
||||
PDH_HCOUNTER, PDH_HQUERY,
|
||||
},
|
||||
synchapi::{CreateEventA, WaitForSingleObject},
|
||||
sysinfoapi::VerSetConditionMask,
|
||||
winbase::{VerifyVersionInfoW, INFINITE, WAIT_OBJECT_0},
|
||||
winnt::{
|
||||
HANDLE, OSVERSIONINFOEXW, VER_BUILDNUMBER, VER_GREATER_EQUAL, VER_MAJORVERSION,
|
||||
VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CPU_USAGE_ONE_MINUTE: Arc<Mutex<Option<(f64, Instant)>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
// https://github.com/mgostIH/process_list/blob/master/src/windows/mod.rs
|
||||
#[repr(transparent)]
|
||||
pub struct RAIIHandle(pub HANDLE);
|
||||
|
||||
impl Drop for RAIIHandle {
|
||||
fn drop(&mut self) {
|
||||
// This never gives problem except when running under a debugger.
|
||||
unsafe { CloseHandle(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(self) struct RAIIPDHQuery(pub PDH_HQUERY);
|
||||
|
||||
impl Drop for RAIIPDHQuery {
|
||||
fn drop(&mut self) {
|
||||
unsafe { PdhCloseQuery(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_cpu_performance_monitor() {
|
||||
// Code from:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/perfctrs/collecting-performance-data
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/pdh/nf-pdh-pdhcollectquerydataex
|
||||
// Why value lower than taskManager:
|
||||
// https://aaron-margosis.medium.com/task-managers-cpu-numbers-are-all-but-meaningless-2d165b421e43
|
||||
// Therefore we should compare with Precess Explorer rather than taskManager
|
||||
|
||||
let f = || unsafe {
|
||||
// load avg or cpu usage, test with prime95.
|
||||
// Prefer cpu usage because we can get accurate value from Precess Explorer.
|
||||
// const COUNTER_PATH: &'static str = "\\System\\Processor Queue Length\0";
|
||||
const COUNTER_PATH: &'static str = "\\Processor(_total)\\% Processor Time\0";
|
||||
const SAMPLE_INTERVAL: DWORD = 2; // 2 second
|
||||
|
||||
let mut ret;
|
||||
let mut query: PDH_HQUERY = std::mem::zeroed();
|
||||
ret = PdhOpenQueryA(std::ptr::null() as _, 0, &mut query);
|
||||
if ret != 0 {
|
||||
log::error!("PdhOpenQueryA failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
let _query = RAIIPDHQuery(query);
|
||||
let mut counter: PDH_HCOUNTER = std::mem::zeroed();
|
||||
ret = PdhAddEnglishCounterA(query, COUNTER_PATH.as_ptr() as _, 0, &mut counter);
|
||||
if ret != 0 {
|
||||
log::error!("PdhAddEnglishCounterA failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
ret = PdhCollectQueryData(query);
|
||||
if ret != 0 {
|
||||
log::error!("PdhCollectQueryData failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
let mut _counter_type: DWORD = 0;
|
||||
let mut counter_value: PDH_FMT_COUNTERVALUE = std::mem::zeroed();
|
||||
let event = CreateEventA(std::ptr::null_mut(), FALSE, FALSE, std::ptr::null() as _);
|
||||
if event.is_null() {
|
||||
log::error!("CreateEventA failed");
|
||||
return;
|
||||
}
|
||||
let _event: RAIIHandle = RAIIHandle(event);
|
||||
ret = PdhCollectQueryDataEx(query, SAMPLE_INTERVAL, event);
|
||||
if ret != 0 {
|
||||
log::error!("PdhCollectQueryDataEx failed: 0x{:X}", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut queue: VecDeque<f64> = VecDeque::new();
|
||||
let mut recent_valid: VecDeque<bool> = VecDeque::new();
|
||||
loop {
|
||||
// latest one minute
|
||||
if queue.len() == 31 {
|
||||
queue.pop_front();
|
||||
}
|
||||
if recent_valid.len() == 31 {
|
||||
recent_valid.pop_front();
|
||||
}
|
||||
// allow get value within one minute
|
||||
if queue.len() > 0 && recent_valid.iter().filter(|v| **v).count() > queue.len() / 2 {
|
||||
let sum: f64 = queue.iter().map(|f| f.to_owned()).sum();
|
||||
let avg = sum / (queue.len() as f64);
|
||||
*CPU_USAGE_ONE_MINUTE.lock().unwrap() = Some((avg, Instant::now()));
|
||||
} else {
|
||||
*CPU_USAGE_ONE_MINUTE.lock().unwrap() = None;
|
||||
}
|
||||
if WAIT_OBJECT_0 != WaitForSingleObject(event, INFINITE) {
|
||||
recent_valid.push_back(false);
|
||||
continue;
|
||||
}
|
||||
if PdhGetFormattedCounterValue(
|
||||
counter,
|
||||
PDH_FMT_DOUBLE,
|
||||
&mut _counter_type,
|
||||
&mut counter_value,
|
||||
) != 0
|
||||
|| counter_value.CStatus != 0
|
||||
{
|
||||
recent_valid.push_back(false);
|
||||
continue;
|
||||
}
|
||||
queue.push_back(counter_value.u.doubleValue().clone());
|
||||
recent_valid.push_back(true);
|
||||
}
|
||||
};
|
||||
use std::sync::Once;
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
std::thread::spawn(f);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn cpu_uage_one_minute() -> Option<f64> {
|
||||
let v = CPU_USAGE_ONE_MINUTE.lock().unwrap().clone();
|
||||
if let Some((v, instant)) = v {
|
||||
if instant.elapsed().as_secs() < 30 {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn sync_cpu_usage(cpu_usage: Option<f64>) {
|
||||
let v = match cpu_usage {
|
||||
Some(cpu_usage) => Some((cpu_usage, Instant::now())),
|
||||
None => None,
|
||||
};
|
||||
*CPU_USAGE_ONE_MINUTE.lock().unwrap() = v;
|
||||
log::info!("cpu usage synced: {:?}", cpu_usage);
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
// https://github.com/nodejs/node-convergence-archive/blob/e11fe0c2777561827cdb7207d46b0917ef3c42a7/deps/uv/src/win/util.c#L780
|
||||
pub fn is_windows_version_or_greater(
|
||||
os_major: u32,
|
||||
os_minor: u32,
|
||||
build_number: u32,
|
||||
service_pack_major: u32,
|
||||
service_pack_minor: u32,
|
||||
) -> bool {
|
||||
let mut osvi: OSVERSIONINFOEXW = unsafe { std::mem::zeroed() };
|
||||
osvi.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as DWORD;
|
||||
osvi.dwMajorVersion = os_major as _;
|
||||
osvi.dwMinorVersion = os_minor as _;
|
||||
osvi.dwBuildNumber = build_number as _;
|
||||
osvi.wServicePackMajor = service_pack_major as _;
|
||||
osvi.wServicePackMinor = service_pack_minor as _;
|
||||
|
||||
let result = unsafe {
|
||||
let mut condition_mask = 0;
|
||||
let op = VER_GREATER_EQUAL;
|
||||
condition_mask = VerSetConditionMask(condition_mask, VER_MAJORVERSION, op);
|
||||
condition_mask = VerSetConditionMask(condition_mask, VER_MINORVERSION, op);
|
||||
condition_mask = VerSetConditionMask(condition_mask, VER_BUILDNUMBER, op);
|
||||
condition_mask = VerSetConditionMask(condition_mask, VER_SERVICEPACKMAJOR, op);
|
||||
condition_mask = VerSetConditionMask(condition_mask, VER_SERVICEPACKMINOR, op);
|
||||
|
||||
VerifyVersionInfoW(
|
||||
&mut osvi as *mut OSVERSIONINFOEXW,
|
||||
VER_MAJORVERSION
|
||||
| VER_MINORVERSION
|
||||
| VER_BUILDNUMBER
|
||||
| VER_SERVICEPACKMAJOR
|
||||
| VER_SERVICEPACKMINOR,
|
||||
condition_mask,
|
||||
)
|
||||
};
|
||||
|
||||
result == TRUE
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Shell and command-path helpers.
|
||||
//!
|
||||
//! These live here rather than with the rest of the platform code because
|
||||
//! `config::patch()` resolves a home directory through them.
|
||||
|
||||
use crate::ResultType;
|
||||
use std::process::Command;
|
||||
|
||||
// to-do: There seems to be some runtime issue that causes the audit logs to be generated.
|
||||
// We may need to fix this and remove this workaround in the future.
|
||||
//
|
||||
// We use the pre-search method to find the command path to avoid the audit logs on some systems.
|
||||
// No idea why the audit logs happen.
|
||||
// Though the audit logs may disappear after rebooting.
|
||||
//
|
||||
// See https://github.com/rustdesk/rustdesk/discussions/11959
|
||||
//
|
||||
// `ausearch -x /usr/share/rustdesk/rustdesk` will return
|
||||
// ...
|
||||
// time->Tue Jun 24 10:40:43 2025
|
||||
// type=PROCTITLE msg=audit(1750776043.446:192757): proctitle=2F7573722F62696E2F727573746465736B002D2D73657276696365
|
||||
// type=PATH msg=audit(1750776043.446:192757): item=0 name="/usr/local/bin/sh" nametype=UNKNOWN cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
|
||||
// type=CWD msg=audit(1750776043.446:192757): cwd="/"
|
||||
// type=SYSCALL msg=audit(1750776043.446:192757): arch=c000003e syscall=59 success=no exit=-2 a0=7fb7dbd22da0 a1=1d65f2c0 a2=7ffc25193360 a3=7ffc25194ec0 items=1 ppid=172208 pid=267565 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="rustdesk" exe="/usr/share/rustdesk/rustdesk" subj=unconfined key="processos_criados"
|
||||
// ----
|
||||
// time->Tue Jun 24 10:40:43 2025
|
||||
// type=PROCTITLE msg=audit(1750776043.446:192758): proctitle=2F7573722F62696E2F727573746465736B002D2D73657276696365
|
||||
// type=PATH msg=audit(1750776043.446:192758): item=0 name="/usr/sbin/sh" nametype=UNKNOWN cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
|
||||
// ...
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CMD_LOGINCTL: String = find_cmd_path("loginctl");
|
||||
pub static ref CMD_PS: String = find_cmd_path("ps");
|
||||
pub static ref CMD_SH: String = find_cmd_path("sh");
|
||||
}
|
||||
|
||||
fn find_cmd_path(cmd: &'static str) -> String {
|
||||
let test_cmd = format!("/bin/{}", cmd);
|
||||
if std::path::Path::new(&test_cmd).exists() {
|
||||
return test_cmd;
|
||||
}
|
||||
let test_cmd = format!("/usr/bin/{}", cmd);
|
||||
if std::path::Path::new(&test_cmd).exists() {
|
||||
return test_cmd;
|
||||
}
|
||||
if let Ok(output) = Command::new("which").arg(cmd).output() {
|
||||
if output.status.success() {
|
||||
return String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
}
|
||||
}
|
||||
cmd.to_string()
|
||||
}
|
||||
|
||||
pub fn run_cmds_trim_newline(cmds: &str) -> ResultType<String> {
|
||||
let output = std::process::Command::new(CMD_SH.as_str())
|
||||
.args(vec!["-c", cmds])
|
||||
.output()?;
|
||||
let out = String::from_utf8_lossy(&output.stdout);
|
||||
Ok(if out.ends_with('\n') {
|
||||
out[..out.len() - 1].to_string()
|
||||
} else {
|
||||
out.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// `config::patch()` resolves the root home directory through this, so the
|
||||
// trailing-newline strip has to keep working in this crate on its own.
|
||||
#[test]
|
||||
fn trims_one_trailing_newline() {
|
||||
assert_eq!(run_cmds_trim_newline("printf 123").unwrap(), "123");
|
||||
assert_eq!(run_cmds_trim_newline("echo 123").unwrap(), "123");
|
||||
assert_eq!(run_cmds_trim_newline("printf '1\\n\\n'").unwrap(), "1\n");
|
||||
assert_eq!(run_cmds_trim_newline("printf ''").unwrap(), "");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user