add the base crate and repoint the moved modules at it

`libs/base` (crate `base`) takes the parts of hbb_common that only this app
uses: `fs`, `platform`, `keyboard`, `message.proto`, and 145 of the 177
`config::keys` constants. hbb_common keeps what the server names, and the 32
keys it reads itself are re-exported from `base::config::keys` so call sites
still see the full set through one path.

Sources move verbatim. The only edits inside them are `crate::` prefixes that
now have to say `hbb_common::`; `keyboard.rs` and `platform/windows.rs` are
byte-identical. The crate stays on edition 2018, the edition the moved code was
written under. `log`, `lazy_static` and `anyhow` become direct dependencies so
the bare paths in that code resolve exactly as before, and its winapi features
are spelled out rather than left to feature unification.

Two call sites outside Rust and Cargo had to follow the move: the Android
protobuf source dir, which still pointed at hbb_common/protos for message.proto,
and the three AGENTS.md entries that named hbb_common for options, protos and
file transfer.

`scrap`'s `drm` feature now forwards to `base/wayland_probe`. Left pointing at
hbb_common it would still have compiled, silently dropping the Wayland
socket-probe fallback, so that forward is verified by a build with and without
the feature.

`config::keys` carries a test asserting its names stay disjoint from the ones
hbb_common kept: the glob re-export and the local constants share a namespace,
and Rust prefers the local item silently, so a name added to both sides would
otherwise let client and server disagree with no diagnostic.

Verified: macOS and Linux, debug and release, `--all-targets`; the 177 key
constants diffed name-for-name and value-for-value; the generated protobuf types
compared before and after; every `#[cfg]` gate on a moved import checked against
its original; and every file that was `rustfmt`-clean before this change still
is, compared against master file by file. Windows is checked by inspection only
-- it cannot be compiled here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
rustdesk
2026-09-07 19:17:51 +08:00
parent e8eead5715
commit c7c2f63323
79 changed files with 4936 additions and 214 deletions

View File

@@ -8,18 +8,24 @@
* `src/platform/` platform-specific code
* `src/ui/` legacy Sciter UI (deprecated)
* `flutter/` current UI
* `libs/hbb_common/` config / proto / shared utils
* `libs/hbb_common/` shared with the server: rendezvous proto, sockets, `Config` core
* `libs/base/` (crate `base`) client-only: option keys, message proto, file transfer, platform code
* `libs/scrap/` screen capture
* `libs/enigo/` input control
* `libs/clipboard/` clipboard
* `libs/hbb_common/src/config.rs` all options
* `libs/base/src/config/keys.rs` the single import path for all options
### Key Components
- **Remote Desktop Protocol**: Custom protocol implemented in `src/rendezvous_mediator.rs` for communicating with rustdesk-server
- **Screen Capture**: Platform-specific screen capture in `libs/scrap/`
- **Input Handling**: Cross-platform input simulation in `libs/enigo/`
- **Audio/Video Services**: Real-time audio/video streaming in `src/server/`
- **File Transfer**: Secure file transfer implementation in `libs/hbb_common/`
- **File Transfer**: Secure file transfer implementation in `libs/base/src/fs.rs`
`hbb_common` is a git submodule shared with the server, so changing it costs a
round-trip. Put client-only code in `libs/base` instead; it is a normal
workspace member. `base::config::keys` re-exports the handful of keys
`hbb_common` still reads, so callers get the whole set from that one path.
### UI Architecture
- **Legacy UI**: Sciter-based (deprecated) - files in `src/ui/`

33
Cargo.lock generated
View File

@@ -648,6 +648,30 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base"
version = "0.1.0"
dependencies = [
"anyhow",
"backtrace",
"bytes",
"filetime",
"hbb_common",
"lazy_static",
"libc",
"log",
"osascript",
"protobuf",
"protobuf-codegen",
"serde 1.0.228",
"serde_derive",
"serde_json 1.0.118",
"smithay-client-toolkit 0.20.0",
"tokio",
"users",
"winapi 0.3.9",
]
[[package]]
name = "base16ct"
version = "0.2.0"
@@ -1251,6 +1275,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
name = "clipboard"
version = "0.1.0"
dependencies = [
"base",
"cacao",
"cc",
"dashmap 5.5.3",
@@ -2477,6 +2502,7 @@ dependencies = [
name = "enigo"
version = "0.0.14"
dependencies = [
"base",
"core-graphics 0.22.3",
"hbb_common",
"libxdo-sys",
@@ -3664,7 +3690,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion",
"backtrace",
"base64 0.22.1",
"bytes",
"chrono",
@@ -3675,7 +3700,6 @@ dependencies = [
"dirs-next",
"dlopen",
"env_logger 0.11.6",
"filetime",
"flexi_logger",
"futures",
"futures-util",
@@ -3686,7 +3710,6 @@ dependencies = [
"log",
"mac_address",
"machine-uid",
"osascript",
"percent-encoding",
"protobuf",
"protobuf-codegen",
@@ -3699,7 +3722,6 @@ dependencies = [
"serde_derive",
"serde_json 1.0.118",
"sha2",
"smithay-client-toolkit 0.20.0",
"socket2 0.3.19",
"sodiumoxide",
"sysinfo",
@@ -3718,7 +3740,6 @@ dependencies = [
"webpki-roots 1.0.9",
"webrtc",
"whoami",
"winapi 0.3.9",
"x11 2.21.0",
"zstd",
]
@@ -7076,6 +7097,7 @@ dependencies = [
"arboard",
"async-process",
"async-trait",
"base",
"bytemuck",
"bytes",
"cc",
@@ -7396,6 +7418,7 @@ name = "scrap"
version = "0.5.0"
dependencies = [
"android_logger",
"base",
"bindgen 0.72.1",
"block",
"cfg-if 1.0.0",

View File

@@ -53,6 +53,7 @@ screencapturekit = ["cpal/screencapturekit"]
async-trait = "0.1"
scrap = { path = "libs/scrap", features = ["wayland"] }
hbb_common = { path = "libs/hbb_common", features = ["webrtc"] }
base = { path = "libs/base" }
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
@@ -208,7 +209,7 @@ jni = "0.21"
android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"]
members = ["libs/scrap", "libs/hbb_common", "libs/base", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"]
exclude = ["vdi/host"]
# Patch libxdo-sys to use a stub implementation that doesn't require libxdo

View File

@@ -158,7 +158,8 @@ Please ensure that you run these commands from the root of the RustDesk reposito
## File Structure
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, and some other utility functions shared with the server
- **[libs/base](https://github.com/rustdesk/rustdesk/tree/master/libs/base)**: protobuf, fs functions for file transfer, keyboard and platform code used only by this app
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: file copy and paste implementation for Windows, Linux, macOS.

View File

@@ -87,7 +87,7 @@ android {
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
main.proto.srcDirs += '../../../libs/hbb_common/protos'
main.proto.srcDirs += '../../../libs/base/protos'
main.proto.includes += "message.proto"
}

57
libs/base/Cargo.toml Normal file
View File

@@ -0,0 +1,57 @@
[package]
name = "base"
version = "0.1.0"
authors = ["rustdesk <info@rustdesk.com>"]
edition = "2018"
# Code that only RustDesk itself uses. `hbb_common` stays the crate shared with
# the server, so anything the server never touches belongs here instead.
[features]
default = []
# 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]
hbb_common = { path = "../hbb_common" }
protobuf = { version = "3.7", features = ["with-bytes"] }
# the generated protobuf code refers to `::bytes::Bytes` (tokio_bytes codegen)
bytes = { version = "1.10", features = ["serde"] }
tokio = { version = "1.44", features = ["full"] }
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
filetime = "0.2"
libc = "0.2"
backtrace = "0.3"
log = "0.4"
lazy_static = "1.5"
anyhow = "1.0"
[build-dependencies]
protobuf-codegen = { version = "3.7" }
[target.'cfg(target_os = "windows")'.dependencies]
# Every module the moved sources name, spelled out rather than left to feature
# unification with the root crate.
winapi = { version = "0.3", features = [
"fileapi",
"handleapi",
"minwindef",
"pdh",
"synchapi",
"sysinfoapi",
"winbase",
"winnt",
] }
[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" }

14
libs/base/build.rs Normal file
View File

@@ -0,0 +1,14 @@
fn main() {
let out_dir = format!("{}/protos", std::env::var("OUT_DIR").unwrap());
std::fs::create_dir_all(&out_dir).unwrap();
protobuf_codegen::Codegen::new()
.pure()
.out_dir(out_dir)
.inputs(["protos/message.proto"])
.include("protos")
.customize(protobuf_codegen::Customize::default().tokio_bytes(true))
.run()
.expect("Codegen failed.");
}

View File

@@ -0,0 +1,20 @@
extern crate base;
#[cfg(target_os = "linux")]
use base::platform::linux;
#[cfg(target_os = "macos")]
use base::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

View File

@@ -0,0 +1,403 @@
//! Option keys shared across the app.
//!
//! The handful that `hbb_common` itself reads stay defined there and are
//! re-exported here, so callers always use this one path.
pub use hbb_common::config::keys::*;
pub const OPTION_VIEW_ONLY: &str = "view_only";
pub const OPTION_SHOW_MONITORS_TOOLBAR: &str = "show_monitors_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_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_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_SYNC_INIT_CLIPBOARD: &str = "sync-init-clipboard";
pub const OPTION_THEME: &str = "theme";
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_ENABLE_LAN_DISCOVERY: &str = "enable-lan-discovery";
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_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";
/// 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_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_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_HIDE_POWERED_BY_ME: &str = "hide-powered-by-me";
pub const OPTION_MAIN_WINDOW_ALWAYS_ON_TOP: &str = "main-window-always-on-top";
// 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";
// 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,
];
#[cfg(test)]
mod tests {
/// The glob above and the constants below share one namespace, and Rust
/// silently prefers the explicit item over a glob import. A key defined on
/// both sides would therefore compile, with the client and the server
/// disagreeing about its string value and nothing to signal it. Keep the
/// two sets apart.
#[test]
fn key_names_do_not_collide_with_hbb_common() {
fn names(src: &str) -> Vec<&str> {
src.lines()
.filter_map(|l| l.trim().strip_prefix("pub const "))
.filter_map(|l| l.split(':').next())
.map(str::trim)
.filter(|n| n.starts_with("OPTION_") || n.starts_with("KEYS_"))
.collect()
}
let here = names(include_str!("keys.rs"));
let there = names(include_str!("../../../hbb_common/src/config.rs"));
assert!(
!here.is_empty() && !there.is_empty(),
"key parsing found nothing"
);
let both: Vec<_> = here.iter().filter(|n| there.contains(n)).collect();
assert!(
both.is_empty(),
"defined in both crates, so the local one shadows hbb_common's \
with no diagnostic: {:?}",
both
);
}
}

View File

@@ -0,0 +1 @@
pub mod keys;

1808
libs/base/src/fs.rs Normal file

File diff suppressed because it is too large Load Diff

39
libs/base/src/keyboard.rs Normal file
View File

@@ -0,0 +1,39 @@
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()
}
}

7
libs/base/src/lib.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod config;
pub mod fs;
pub mod keyboard;
pub mod platform;
pub mod protos;
pub use protos::message as message_proto;

View File

@@ -0,0 +1,618 @@
use hbb_common::ResultType;
// Kept in hbb_common because `config::patch()` needs the shell lookup; re-exported
// here so the long-standing `platform::linux::CMD_SH` paths are unchanged.
pub use hbb_common::sh::{run_cmds_trim_newline, CMD_LOGINCTL, CMD_PS, CMD_SH};
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();
}
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 }
}
}
// Deprecated. Use `base::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 `base::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())
}
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(());
}
}
hbb_common::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'");
}
}

View File

@@ -0,0 +1,349 @@
//! 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 hbb_common::{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("; ")
}
)
}

View File

@@ -0,0 +1,55 @@
use hbb_common::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)
}

View File

@@ -0,0 +1,82 @@
#[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 hbb_common::{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 _);
}
}

View File

@@ -0,0 +1,198 @@
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
}

View File

@@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));

View File

@@ -30,6 +30,7 @@ lazy_static = "1.4"
serde = "1.0"
serde_derive = "1.0"
hbb_common = { path = "../hbb_common" }
base = { path = "../base" }
parking_lot = {version = "0.12"}
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]

View File

@@ -60,10 +60,8 @@ pub(super) fn validate_file_name(name: &str) -> Result<(), CliprdrError> {
description: "clipboard file name is not a normalized relative path".to_string(),
});
}
hbb_common::fs::validate_file_name_no_traversal(name).map_err(|error| {
CliprdrError::InvalidRequest {
description: error.to_string(),
}
base::fs::validate_file_name_no_traversal(name).map_err(|error| CliprdrError::InvalidRequest {
description: error.to_string(),
})
}

View File

@@ -2,7 +2,8 @@ use crate::{
platform::unix::{FileDescription, FileType, BLOCK_SIZE},
send_data, ClipboardFile, CliprdrError, ProgressPercent,
};
use hbb_common::{allow_err, fs::join_validated_path, log, tokio::time::Instant};
use base::fs::join_validated_path;
use hbb_common::{allow_err, log, tokio::time::Instant};
use std::{
cmp::min,
fs::{File, FileTimes, OpenOptions},

View File

@@ -25,6 +25,7 @@ log = "0.4"
rdev = { git = "https://github.com/rustdesk-org/rdev" }
tfc = { git = "https://github.com/rustdesk-org/The-Fat-Controller", branch = "history/rebase_upstream_20240722" }
hbb_common = { path = "../hbb_common" }
base = { path = "../base" }
[features]
with_serde = ["serde", "serde_derive"]

View File

@@ -122,7 +122,7 @@ impl Enigo {
impl Default for Enigo {
fn default() -> Self {
let is_x11 = hbb_common::platform::linux::is_x11_or_headless();
let is_x11 = base::platform::linux::is_x11_or_headless();
Self {
is_x11,
tfc: if is_x11 {

View File

@@ -20,7 +20,7 @@ wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing", "
# Depends on `wayland`: the three drm modules live inside the `#[cfg(feature = "wayland")]` arm of
# common/mod.rs, so `scrap/drm` on its own would compile nothing. The root crate happens to always
# enable `scrap/wayland`, which is what hid this.
drm = ["wayland", "hbb_common/wayland_probe"]
drm = ["wayland", "base/wayland_probe"]
mediacodec = ["ndk"]
linux-pkg-config = ["dep:pkg-config"]
hwcodec = ["dep:hwcodec"]
@@ -31,6 +31,7 @@ cfg-if = "1.0"
num_cpus = "1.15"
lazy_static = "1.4"
hbb_common = { path = "../hbb_common" }
base = { path = "../base" }
webm = { git = "https://github.com/rustdesk-org/rust-webm" }
serde = {version="1.0", features=["derive"]}

View File

@@ -9,7 +9,8 @@ use jni::{
JavaVM,
};
use hbb_common::{message_proto::MultiClipboards, protobuf::Message};
use base::message_proto::MultiClipboards;
use hbb_common::protobuf::Message;
use jni::errors::{Error as JniError, Result as JniResult};
use lazy_static::lazy_static;
use serde::Deserialize;

View File

@@ -13,10 +13,9 @@ use crate::{EncodeInput, EncodeYuvFormat, Pixfmt};
use hbb_common::{
anyhow::{anyhow, Context},
bytes::Bytes,
log,
message_proto::{Chroma, EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
ResultType,
log, ResultType,
};
use base::message_proto::{Chroma, EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
use std::{ptr, slice};
generate_call_macro!(call_aom, false);

View File

@@ -11,7 +11,7 @@ use nokhwa::{
Camera,
};
use hbb_common::message_proto::{DisplayInfo, Resolution};
use base::message_proto::{DisplayInfo, Resolution};
#[cfg(feature = "vram")]
use crate::AdapterDevice;

View File

@@ -18,6 +18,10 @@ use crate::{
CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, ImageTexture,
};
use base::message_proto::{
supported_decoding::PreferCodec, video_frame, Chroma, CodecAbility, EncodedVideoFrames,
SupportedDecoding, SupportedEncoding, VideoFrame,
};
#[cfg(any(
feature = "hwcodec",
feature = "mediacodec",
@@ -30,10 +34,6 @@ use hbb_common::{
bail,
config::{Config, PeerConfig},
lazy_static, log,
message_proto::{
supported_decoding::PreferCodec, video_frame, Chroma, CodecAbility, EncodedVideoFrames,
SupportedDecoding, SupportedEncoding, VideoFrame,
},
sysinfo::System,
ResultType,
};
@@ -269,7 +269,7 @@ impl Encoder {
let preference = most_frequent.enum_value_or(PreferCodec::Auto);
// auto: h265 > h264 > av1/vp9/vp8
let av1_test = Config::get_option(hbb_common::config::keys::OPTION_AV1_TEST) != "N";
let av1_test = Config::get_option(base::config::keys::OPTION_AV1_TEST) != "N";
let mut auto_codec = if av1_useable && av1_test {
CodecFormat::AV1
} else {
@@ -849,7 +849,7 @@ impl Decoder {
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
pub fn enable_hwcodec_option() -> bool {
use hbb_common::config::keys::OPTION_ENABLE_HWCODEC;
use base::config::keys::OPTION_ENABLE_HWCODEC;
if !cfg!(target_os = "ios") {
return option2bool(
@@ -861,7 +861,7 @@ pub fn enable_hwcodec_option() -> bool {
}
#[cfg(feature = "vram")]
pub fn enable_vram_option(encode: bool) -> bool {
use hbb_common::config::keys::OPTION_ENABLE_HWCODEC;
use base::config::keys::OPTION_ENABLE_HWCODEC;
if cfg!(windows) {
let enable = option2bool(
@@ -880,13 +880,13 @@ pub fn enable_vram_option(encode: bool) -> bool {
#[cfg(windows)]
pub fn enable_directx_capture() -> bool {
use hbb_common::config::keys::OPTION_ENABLE_DIRECTX_CAPTURE as OPTION;
use base::config::keys::OPTION_ENABLE_DIRECTX_CAPTURE as OPTION;
option2bool(OPTION, &Config::get_option(OPTION))
}
#[cfg(windows)]
pub fn allow_d3d_render() -> bool {
use hbb_common::config::keys::OPTION_ALLOW_D3D_RENDER as OPTION;
use base::config::keys::OPTION_ALLOW_D3D_RENDER as OPTION;
option2bool(OPTION, &hbb_common::config::LocalConfig::get_option(OPTION))
}
@@ -980,7 +980,7 @@ pub fn codec_thread_num(limit: usize) -> usize {
#[cfg(windows)]
{
res = 0;
let percent = hbb_common::platform::windows::cpu_uage_one_minute();
let percent = base::platform::windows::cpu_uage_one_minute();
info = format!("cpu usage: {:?}", percent);
if let Some(pecent) = percent {
if pecent < 100.0 {
@@ -1038,7 +1038,7 @@ fn disable_av1() -> bool {
#[cfg(not(target_os = "ios"))]
pub fn test_av1() {
use hbb_common::config::keys::OPTION_AV1_TEST;
use base::config::keys::OPTION_AV1_TEST;
use hbb_common::rand::Rng;
use std::{sync::Once, time::Duration};

View File

@@ -3,11 +3,11 @@ use crate::{
convert::*,
CodecFormat, EncodeInput, ImageFormat, ImageRgb, Pixfmt, HW_STRIDE_ALIGN,
};
use base::message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
use hbb_common::{
anyhow::{anyhow, bail, Context},
bytes::Bytes,
log,
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
serde_derive::{Deserialize, Serialize},
serde_json, ResultType,
};

View File

@@ -1,9 +1,6 @@
pub use self::vpxcodec::*;
use hbb_common::{
bail, log,
message_proto::{video_frame, Chroma, VideoFrame},
ResultType,
};
use base::message_proto::{video_frame, Chroma, VideoFrame};
use hbb_common::{bail, log, ResultType};
use std::{ffi::c_void, slice};
cfg_if! {
@@ -268,7 +265,7 @@ pub struct EncodeYuvFormat {
#[cfg(x11)]
#[inline]
pub fn is_x11() -> bool {
hbb_common::platform::linux::is_x11_or_headless()
base::platform::linux::is_x11_or_headless()
}
#[cfg(x11)]

View File

@@ -1,11 +1,8 @@
use crate::CodecFormat;
use base::message_proto::{message, video_frame, EncodedVideoFrame, Message};
#[cfg(feature = "hwcodec")]
use hbb_common::anyhow::anyhow;
use hbb_common::{
bail, chrono, log,
message_proto::{message, video_frame, EncodedVideoFrame, Message},
ResultType,
};
use hbb_common::{bail, chrono, log, ResultType};
#[cfg(feature = "hwcodec")]
use hwcodec::mux::{MuxContext, Muxer};
use std::{

View File

@@ -5,8 +5,8 @@
use hbb_common::anyhow::{anyhow, Context};
use hbb_common::log;
use hbb_common::message_proto::{Chroma, EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
use hbb_common::ResultType;
use base::message_proto::{Chroma, EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
use crate::codec::{base_bitrate, codec_thread_num, EncoderApi};
use crate::{EncodeInput, EncodeYuvFormat, GoogleImage, Pixfmt, STRIDE_ALIGN};

View File

@@ -9,12 +9,11 @@ use crate::{
hwcodec::HwCodecConfig,
AdapterDevice, CodecFormat, EncodeInput, EncodeYuvFormat, Pixfmt,
};
use base::message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
use hbb_common::{
anyhow::{anyhow, bail, Context},
bytes::Bytes,
log,
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
ResultType,
log, ResultType,
};
use hwcodec::{
common::{DataFormat, Driver, MAX_GOP},
@@ -98,7 +97,7 @@ impl EncoderApi for VRamEncoder {
&mut self,
frame: EncodeInput,
ms: i64,
) -> ResultType<hbb_common::message_proto::VideoFrame> {
) -> ResultType<base::message_proto::VideoFrame> {
let (texture, rotation) = frame.texture()?;
if rotation != 0 {
// to-do: support rotation

View File

@@ -8,7 +8,7 @@ use std::{
};
use tracing::warn;
use hbb_common::platform::linux::{get_wayland_displays, WaylandDisplayInfo};
use base::platform::linux::{get_wayland_displays, WaylandDisplayInfo};
lazy_static! {
static ref DISPLAYS: Mutex<Option<Arc<Displays>>> = Mutex::new(None);
@@ -105,7 +105,7 @@ fn try_xrandr_primary() -> Option<String> {
}
fn try_kscreen_primary() -> Option<String> {
if !hbb_common::platform::linux::is_kde_session() {
if !base::platform::linux::is_kde_session() {
return None;
}

View File

@@ -23,7 +23,8 @@ use gstreamer_app::AppSink;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use hbb_common::{bail, config, platform::linux::CMD_SH, serde_json, tokio, ResultType};
use base::platform::linux::CMD_SH;
use hbb_common::{bail, config, serde_json, tokio, ResultType};
use super::capturable::PixelProvider;
use super::capturable::{Capturable, Recorder};

View File

@@ -47,13 +47,11 @@ use hbb_common::{
anyhow::{anyhow, Context},
bail,
config::{
self, keys, use_ws, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution,
self, use_ws, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution,
CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_PORT, RENDEZVOUS_SERVERS,
},
fs::JobType,
futures::future::{select_ok, BoxFuture, FutureExt},
get_version_number, log,
message_proto::{option_message::BoolOption, *},
protobuf::{Message as _, MessageField},
rand,
rendezvous_proto::*,
@@ -73,6 +71,11 @@ use hbb_common::{
webrtc::WebRTCStream,
AddrMangle, ResultType, Stream,
};
use base::{
config::keys,
fs::JobType,
message_proto::{option_message::BoolOption, *},
};
pub use helper::*;
use scrap::{
codec::Decoder,
@@ -4020,7 +4023,7 @@ async fn do_sync_cpu_usage() {
if let Ok(Some(data)) = conn.next_timeout(50).await {
match data {
Data::SyncWinCpuUsage(cpu_usage) => {
hbb_common::platform::windows::sync_cpu_usage(cpu_usage);
base::platform::windows::sync_cpu_usage(cpu_usage);
}
_ => {}
}
@@ -4680,7 +4683,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
}
}
fn swap_modifier_mouse(&self, _msg: &mut hbb_common::protos::message::MouseEvent) {}
fn swap_modifier_mouse(&self, _msg: &mut base::protos::message::MouseEvent) {}
fn update_direct(&self, direct: Option<bool>) {
self.get_lch().write().unwrap().direct = direct;

View File

@@ -1,4 +1,5 @@
use hbb_common::{fs, log, message_proto::*};
use hbb_common::log;
use base::{fs, message_proto::*};
use super::{Data, Interface};

View File

@@ -1,7 +1,5 @@
use hbb_common::{
get_time,
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
};
use base::message_proto::{Message, VoiceCallRequest, VoiceCallResponse};
use hbb_common::get_time;
use scrap::CodecFormat;
use std::collections::HashMap;

View File

@@ -17,6 +17,14 @@ const RESTART_REMOTE_DEVICE_NO_DATA_TIMEOUT: Duration = Duration::from_secs(5);
const KCP_CLOSE_REASON_FLUSH_DELAY: Duration = Duration::from_millis(30);
#[cfg(feature = "unix-file-copy-paste")]
use crate::{clipboard::try_empty_clipboard_files, clipboard_file::unix_file_clip};
use base::{
config::keys,
fs::{
self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm,
DigestCheckResult, RemoveJobMeta,
},
message_proto::{permission_info::Permission, *},
};
#[cfg(any(
target_os = "windows",
all(target_os = "macos", feature = "unix-file-copy-paste")
@@ -28,12 +36,7 @@ use hbb_common::tokio::sync::mpsc::error::TryRecvError;
use hbb_common::{
allow_err,
config::{self, LocalConfig, PeerConfig, TransferSerde},
fs::{
self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm,
DigestCheckResult, RemoveJobMeta,
},
get_time, log,
message_proto::{permission_info::Permission, *},
protobuf::Message as _,
rendezvous_proto::ConnType,
timeout,
@@ -2036,9 +2039,8 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(target_os = "windows")]
Ok(file_transfer_send_request::FileType::Printer) => {
#[cfg(feature = "flutter")]
let action = LocalConfig::get_option(
config::keys::OPTION_PRINTER_INCOMING_JOB_ACTION,
);
let action =
LocalConfig::get_option(keys::OPTION_PRINTER_INCOMING_JOB_ACTION);
#[cfg(not(feature = "flutter"))]
let action = "";
if action == "dismiss" {
@@ -2047,7 +2049,7 @@ impl<T: InvokeUiSession> Remote<T> {
let id = fs::get_next_job_id();
#[cfg(feature = "flutter")]
let allow_auto_print = LocalConfig::get_bool_option(
config::keys::OPTION_PRINTER_ALLOW_AUTO_PRINT,
keys::OPTION_PRINTER_ALLOW_AUTO_PRINT,
);
#[cfg(not(feature = "flutter"))]
let allow_auto_print = false;
@@ -2055,9 +2057,7 @@ impl<T: InvokeUiSession> Remote<T> {
let printer_name = if action == "" {
"".to_string()
} else {
LocalConfig::get_option(
config::keys::OPTION_PRINTER_SELECTED_NAME,
)
LocalConfig::get_option(keys::OPTION_PRINTER_SELECTED_NAME)
};
self.handler.printer_response(id, _s.path, printer_name);
} else {
@@ -2123,7 +2123,7 @@ impl<T: InvokeUiSession> Remote<T> {
.handle_screenshot_resp(response.sid, response.msg);
}
Some(message::Union::TerminalResponse(response)) => {
use hbb_common::message_proto::terminal_response::Union;
use base::message_proto::terminal_response::Union;
if let Some(Union::Opened(opened)) = &response.union {
if opened.success && !opened.service_id.is_empty() {
let mut lc = self.handler.lc.write().unwrap();
@@ -2347,14 +2347,10 @@ impl<T: InvokeUiSession> Remote<T> {
}
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
async fn handle_cliprdr_msg(
&mut self,
clip: hbb_common::message_proto::Cliprdr,
_peer: &mut Stream,
) {
async fn handle_cliprdr_msg(&mut self, clip: base::message_proto::Cliprdr, _peer: &mut Stream) {
log::debug!("handling cliprdr msg from server peer");
#[cfg(feature = "flutter")]
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
if let Some(base::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
if self.client_conn_id
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_peer_id()).unwrap_or(0)
{
@@ -2464,8 +2460,7 @@ impl<T: InvokeUiSession> Remote<T> {
);
self.video_threads.insert(display, video_thread);
if self.video_threads.len() == 1 {
let auto_record =
LocalConfig::get_bool_option(config::keys::OPTION_ALLOW_AUTO_RECORD_OUTGOING);
let auto_record = LocalConfig::get_bool_option(keys::OPTION_ALLOW_AUTO_RECORD_OUTGOING);
self.handler.lc.write().unwrap().record_state = auto_record;
self.update_record_state();
}

View File

@@ -1,6 +1,7 @@
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::clipboard::{update_clipboard, ClipboardSide};
use hbb_common::{message_proto::*, ResultType};
use base::message_proto::*;
use hbb_common::ResultType;
use std::sync::Mutex;
lazy_static::lazy_static! {

View File

@@ -2,7 +2,8 @@
use arboard::{ClipboardData, ClipboardFormat};
#[cfg(target_os = "linux")]
use arboard::{LinuxClipboardKind, SetExtLinux};
use hbb_common::{bail, log, message_proto::*, ResultType};
use hbb_common::{bail, log, ResultType};
use base::message_proto::*;
use std::{
sync::{Arc, Mutex},
time::Duration,
@@ -515,10 +516,10 @@ impl ClipboardContext {
// The host-side clear file clipboard `let _ = self.inner.clear();`,
// does not work on KDE Plasma for the installed version.
// Don't use `hbb_common::platform::linux::is_kde()` here.
// Don't use `base::platform::linux::is_kde()` here.
// It's not correct in the server process.
#[cfg(target_os = "linux")]
let is_kde_x11 = hbb_common::platform::linux::is_kde_session()
let is_kde_x11 = base::platform::linux::is_kde_session()
&& crate::platform::linux::is_x11();
#[cfg(target_os = "macos")]
let is_kde_x11 = false;
@@ -581,7 +582,7 @@ pub fn get_current_clipboard_msg(
multi_clipboards
.clipboards
.iter()
.find(|c| c.format.enum_value() == Ok(hbb_common::message_proto::ClipboardFormat::Text))
.find(|c| c.format.enum_value() == Ok(base::message_proto::ClipboardFormat::Text))
.map(|c| {
let mut msg = Message::new();
msg.set_clipboard(c.clone());
@@ -629,8 +630,8 @@ mod proto {
use arboard::ClipboardData;
use hbb_common::{
compress::{compress as compress_func, decompress},
message_proto::{Clipboard, ClipboardFormat, Message, MultiClipboards},
};
use base::message_proto::{Clipboard, ClipboardFormat, Message, MultiClipboards};
fn plain_to_proto(s: String, format: ClipboardFormat) -> Clipboard {
let compressed = compress_func(s.as_bytes());

View File

@@ -1,5 +1,5 @@
use clipboard::ClipboardFile;
use hbb_common::message_proto::*;
use base::message_proto::*;
pub fn clip_2_msg(clip: ClipboardFile) -> Message {
match clip {

View File

@@ -8,6 +8,7 @@ use std::{
use serde_json::{json, Map, Value};
use base::{config::keys, message_proto::*};
#[cfg(not(target_os = "ios"))]
use hbb_common::whoami;
use hbb_common::{
@@ -16,13 +17,10 @@ use hbb_common::{
async_recursion::async_recursion,
bail, base64,
bytes::Bytes,
config::{
self, keys, use_ws, Config, LocalConfig, CONNECT_TIMEOUT, READ_TIMEOUT, RENDEZVOUS_PORT,
},
config::{self, use_ws, Config, LocalConfig, CONNECT_TIMEOUT, READ_TIMEOUT, RENDEZVOUS_PORT},
futures::future::join_all,
futures_util::future::poll_fn,
get_version_number, log,
message_proto::*,
protobuf::{Enum, Message as _},
rendezvous_proto::*,
socket_client,

View File

@@ -3,9 +3,10 @@ use crate::client::translate;
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::platform::breakdown_callback;
use base::config::keys;
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::platform::register_breakdown_handler;
use base::platform::register_breakdown_handler;
use hbb_common::{config, log};
#[cfg(windows)]
use tauri_winrt_notification::{Duration, Sound, Toast};
@@ -113,7 +114,7 @@ pub fn core_main() -> Option<Vec<String>> {
}
#[cfg(windows)]
if args.contains(&"--connect".to_string()) || args.contains(&"--view-camera".to_string()) {
hbb_common::platform::windows::start_cpu_performance_monitor();
base::platform::windows::start_cpu_performance_monitor();
}
#[cfg(feature = "flutter")]
if _is_flutter_invoke_new_connection {
@@ -889,7 +890,7 @@ fn is_user_main_ipc_scope_cli_command(args: &[String]) -> bool {
#[inline]
fn is_cli_setting_change_disabled() -> bool {
let option = config::keys::OPTION_ALLOW_COMMAND_LINE_SETTINGS_WHEN_SETTINGS_DISABLED;
let option = keys::OPTION_ALLOW_COMMAND_LINE_SETTINGS_WHEN_SETTINGS_DISABLED;
let allow_command_line_settings =
config::option2bool(option, &crate::get_builtin_option(option));
config::is_disable_settings() && !allow_command_line_settings

View File

@@ -10,9 +10,10 @@ use hbb_common::dlopen::{
Error as LibError,
};
use hbb_common::{
anyhow::anyhow, bail, config::LocalConfig, get_version_number, log, message_proto::*,
anyhow::anyhow, bail, config::LocalConfig, get_version_number, log,
rendezvous_proto::ConnType, ResultType,
};
use base::message_proto::*;
use serde::Serialize;
use serde_json::json;
#[cfg(target_os = "windows")]
@@ -1102,7 +1103,7 @@ impl InvokeUiSession for FlutterHandler {
}
fn handle_terminal_response(&self, response: TerminalResponse) {
use hbb_common::message_proto::terminal_response::Union;
use base::message_proto::terminal_response::Union;
match response.union {
Some(Union::Opened(opened)) => {

View File

@@ -14,10 +14,14 @@ use crate::{
use flutter_rust_bridge::{StreamSink, SyncReturn};
use hbb_common::{
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
fs, lazy_static, log,
lazy_static, log,
rendezvous_proto::ConnType,
ResultType,
};
use base::{
config::keys,
fs,
};
use std::{
collections::HashMap,
path::PathBuf,
@@ -330,7 +334,7 @@ pub fn session_toggle_option(session_id: SessionID, value: String) {
}
#[cfg(feature = "unix-file-copy-paste")]
if sessions::get_session_by_session_id(&session_id).is_some()
&& (value == config::keys::OPTION_ENABLE_FILE_COPY_PASTE || value == "view-only")
&& (value == keys::OPTION_ENABLE_FILE_COPY_PASTE || value == "view-only")
{
crate::flutter::update_file_clipboard_required();
}
@@ -965,12 +969,12 @@ pub fn main_get_error() -> String {
pub fn main_set_option(key: String, value: String) {
#[cfg(target_os = "android")]
{
let is_permission_option = key.eq(config::keys::OPTION_ENABLE_CLIPBOARD)
|| key.eq(config::keys::OPTION_ENABLE_FILE_TRANSFER)
|| key.eq(config::keys::OPTION_ENABLE_AUDIO);
let is_permission_option = key.eq(keys::OPTION_ENABLE_CLIPBOARD)
|| key.eq(keys::OPTION_ENABLE_FILE_TRANSFER)
|| key.eq(keys::OPTION_ENABLE_AUDIO);
let allow_perm_change_in_accept_window = config::option2bool(
config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
);
if is_permission_option
&& !allow_perm_change_in_accept_window
@@ -985,14 +989,14 @@ pub fn main_set_option(key: String, value: String) {
}
}
#[cfg(target_os = "android")]
if key.eq(config::keys::OPTION_ENABLE_KEYBOARD) {
if key.eq(keys::OPTION_ENABLE_KEYBOARD) {
crate::ui_cm_interface::switch_permission_all(
"keyboard".to_owned(),
config::option2bool(&key, &value),
);
}
#[cfg(target_os = "android")]
if key.eq(config::keys::OPTION_ENABLE_CLIPBOARD) {
if key.eq(keys::OPTION_ENABLE_CLIPBOARD) {
crate::ui_cm_interface::switch_permission_all(
"clipboard".to_owned(),
config::option2bool(&key, &value),
@@ -1002,11 +1006,11 @@ pub fn main_set_option(key: String, value: String) {
// If `is_allow_tls_fallback` and https proxy is used, we need to restart rendezvous mediator.
// No need to check if https proxy is used, because this option does not change frequently
// and restarting mediator is safe even https proxy is not used.
let is_allow_tls_fallback = key.eq(config::keys::OPTION_ALLOW_INSECURE_TLS_FALLBACK);
let is_allow_tls_fallback = key.eq(keys::OPTION_ALLOW_INSECURE_TLS_FALLBACK);
if is_allow_tls_fallback
|| key.eq("custom-rendezvous-server")
|| key.eq(config::keys::OPTION_ALLOW_WEBSOCKET)
|| key.eq(config::keys::OPTION_DISABLE_UDP)
|| key.eq(keys::OPTION_ALLOW_WEBSOCKET)
|| key.eq(keys::OPTION_DISABLE_UDP)
|| key.eq("api-server")
{
if is_allow_tls_fallback {
@@ -1035,14 +1039,14 @@ pub fn main_set_options(json: String) {
#[cfg(target_os = "android")]
{
let allow_perm_change_in_accept_window = config::option2bool(
config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
);
if !allow_perm_change_in_accept_window && crate::ui_cm_interface::has_active_clients() {
for key in [
config::keys::OPTION_ENABLE_CLIPBOARD,
config::keys::OPTION_ENABLE_FILE_TRANSFER,
config::keys::OPTION_ENABLE_AUDIO,
keys::OPTION_ENABLE_CLIPBOARD,
keys::OPTION_ENABLE_FILE_TRANSFER,
keys::OPTION_ENABLE_AUDIO,
] {
if let Some(value) = map.remove(key) {
log::info!(
@@ -1210,8 +1214,8 @@ pub fn main_set_env(key: String, value: Option<String>) -> SyncReturn<()> {
}
pub fn main_set_local_option(key: String, value: String) {
let is_texture_render_key = key.eq(config::keys::OPTION_TEXTURE_RENDER);
let is_d3d_render_key = key.eq(config::keys::OPTION_ALLOW_D3D_RENDER);
let is_texture_render_key = key.eq(keys::OPTION_TEXTURE_RENDER);
let is_d3d_render_key = key.eq(keys::OPTION_ALLOW_D3D_RENDER);
set_local_option(key, value.clone());
let is_render_target =
|session: &crate::flutter::FlutterSession| session.is_default() || session.is_view_camera();
@@ -2651,7 +2655,7 @@ pub fn main_get_common(key: String) -> String {
#[cfg(not(target_os = "windows"))]
return false.to_string();
} else if key == "transfer-job-id" {
return hbb_common::fs::get_next_job_id().to_string();
return base::fs::get_next_job_id().to_string();
} else if key == "is-remote-modify-enabled-by-control-permissions" {
return match is_remote_modify_enabled_by_control_permissions() {
Some(true) => "true",

View File

@@ -7,10 +7,11 @@ use std::{
#[cfg(not(any(target_os = "ios")))]
use crate::{ui_interface::get_builtin_option, Connection};
use hbb_common::{
config::{self, keys, Config, LocalConfig},
config::{self, Config, LocalConfig},
log,
tokio::{self, sync::broadcast, time::Instant},
};
use base::config::keys;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};

View File

@@ -34,7 +34,7 @@ use hbb_common::anyhow;
use hbb_common::{
allow_err, bail, bytes,
bytes_codec::BytesCodec,
config::{self, keys::OPTION_ALLOW_WEBSOCKET, Config, Config2},
config::{self, Config, Config2},
futures::StreamExt as _,
futures_util::sink::SinkExt,
log, password_security as password, timeout,
@@ -45,6 +45,7 @@ use hbb_common::{
tokio_util::codec::Framed,
ResultType,
};
use base::config::keys::{self, OPTION_ALLOW_WEBSOCKET};
#[cfg(windows)]
pub(crate) use ipc_auth::authorize_windows_portable_service_ipc_connection;
#[cfg(windows)]
@@ -752,9 +753,9 @@ impl CheckIfRestart {
audio_input: Config::get_option("audio-input"),
voice_call_input: Config::get_option("voice-call-input"),
ws: Config::get_option(OPTION_ALLOW_WEBSOCKET),
disable_udp: Config::get_option(config::keys::OPTION_DISABLE_UDP),
disable_udp: Config::get_option(keys::OPTION_DISABLE_UDP),
allow_insecure_tls_fallback: Config::get_option(
config::keys::OPTION_ALLOW_INSECURE_TLS_FALLBACK,
keys::OPTION_ALLOW_INSECURE_TLS_FALLBACK,
),
api_server: Config::get_option("api-server"),
}
@@ -766,12 +767,12 @@ impl Drop for CheckIfRestart {
// No need to check if https proxy is used, because this option does not change frequently
// and restarting mediator is safe even https proxy is not used.
let allow_insecure_tls_fallback_changed = self.allow_insecure_tls_fallback
!= Config::get_option(config::keys::OPTION_ALLOW_INSECURE_TLS_FALLBACK);
!= Config::get_option(keys::OPTION_ALLOW_INSECURE_TLS_FALLBACK);
if allow_insecure_tls_fallback_changed
|| self.stop_service != Config::get_option("stop-service")
|| self.rendezvous_servers != Config::get_rendezvous_servers()
|| self.ws != Config::get_option(OPTION_ALLOW_WEBSOCKET)
|| self.disable_udp != Config::get_option(config::keys::OPTION_DISABLE_UDP)
|| self.disable_udp != Config::get_option(keys::OPTION_DISABLE_UDP)
|| self.api_server != Config::get_option("api-server")
{
if allow_insecure_tls_fallback_changed {
@@ -1035,7 +1036,7 @@ async fn handle(data: Data, stream: &mut Connection) {
allow_err!(
stream
.send(&Data::SyncWinCpuUsage(
hbb_common::platform::windows::cpu_uage_one_minute()
base::platform::windows::cpu_uage_one_minute()
))
.await
);
@@ -1227,7 +1228,7 @@ async fn handle(data: Data, stream: &mut Connection) {
let state = crate::server::get_control_permission_state(Permission::file, false);
let enabled = state.unwrap_or_else(|| {
crate::server::Connection::is_permission_enabled_locally(
config::keys::OPTION_ENABLE_FILE_TRANSFER,
keys::OPTION_ENABLE_FILE_TRANSFER,
)
});
allow_err!(

View File

@@ -9,7 +9,7 @@ use crate::ui_session_interface::{InvokeUiSession, Session};
use crate::{client::get_key_state, common::GrabState};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::log;
use hbb_common::message_proto::*;
use base::message_proto::*;
#[cfg(any(target_os = "windows", target_os = "macos"))]
use rdev::KeyCode;
use rdev::{Event, EventType, Key};

View File

@@ -2,12 +2,11 @@
// Sometimes reboot is needed to refresh sudoers.
use crate::lang::translate;
use base::platform::linux::CMD_SH;
use gtk::{glib, prelude::*};
use hbb_common::{
anyhow::{bail, Error},
log,
platform::linux::CMD_SH,
ResultType,
log, ResultType,
};
use nix::{
libc::{fcntl, kill},

View File

@@ -1,6 +1,6 @@
use super::{gtk_sudo, CursorData, ResultType};
use desktop::Desktop;
pub use hbb_common::platform::linux::*;
pub use base::platform::linux::*;
#[cfg(feature = "drm")]
pub fn dispatch_wayland_display_probe() {
@@ -17,10 +17,10 @@ use hbb_common::{
config::Config,
libc::{c_char, c_int, c_long, c_uint, c_ulong, c_void},
log,
message_proto::{DisplayInfo, Resolution},
regex::{Captures, Regex},
users::{get_user_by_name, os::unix::UserExt},
};
use base::message_proto::{DisplayInfo, Resolution};
use libxdo_sys::{self, xdo_t, Window};
use std::{
cell::RefCell,
@@ -64,7 +64,7 @@ lazy_static::lazy_static! {
/// serve but the DRM path can. Unmemoised lookup on purpose: this may run mid-boot, and
/// a "no" cached that early would be wrong for the rest of the process.
pub static ref IS_X11: bool = {
let x11 = hbb_common::platform::linux::is_x11_or_headless();
let x11 = base::platform::linux::is_x11_or_headless();
#[cfg(feature = "drm")]
{
if x11 && !display_server_forced() && is_login_screen_wayland() {

View File

@@ -20,9 +20,9 @@ use core_graphics::{
use hbb_common::{
anyhow::anyhow,
bail, log,
message_proto::{DisplayInfo, Resolution},
sysinfo::{Pid, Process, ProcessRefreshKind, System},
};
use base::message_proto::{DisplayInfo, Resolution};
use include_dir::{include_dir, Dir};
use objc::rc::autoreleasepool;
use objc::{class, msg_send, sel, sel_impl};

View File

@@ -23,13 +23,15 @@ pub mod linux;
#[cfg(target_os = "linux")]
pub mod gtk_sudo;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use base::message_proto::CursorData;
#[cfg(all(
not(all(target_os = "windows", not(target_pointer_width = "64"))),
not(any(target_os = "android", target_os = "ios"))
))]
use hbb_common::sysinfo::System;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::{message_proto::CursorData, sysinfo::Pid, ResultType};
use hbb_common::{sysinfo::Pid, ResultType};
use std::sync::{Arc, Mutex};
#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))]
pub const SERVICE_INTERVAL: u64 = 300;

View File

@@ -5,15 +5,14 @@ use crate::{
ipc,
privacy_mode::win_topmost_window::{self, WIN_TOPMOST_INJECTED_PROCESS_EXE},
};
use base::message_proto::{DisplayInfo, Resolution, WindowsSession};
use hbb_common::{
allow_err,
anyhow::anyhow,
bail,
config::{self, Config},
libc::{c_int, wchar_t},
log,
message_proto::{DisplayInfo, Resolution, WindowsSession},
sleep,
log, sleep,
sysinfo::{Pid, System},
timeout, tokio,
};
@@ -2482,7 +2481,7 @@ pub fn elevate_or_run_as_system(is_setup: bool, is_elevate: bool, is_run_as_syst
}
pub fn is_elevated(process_id: Option<DWORD>) -> ResultType<bool> {
use hbb_common::platform::windows::RAIIHandle;
use base::platform::windows::RAIIHandle;
unsafe {
let handle: HANDLE = match process_id {
Some(process_id) => OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id),
@@ -4754,7 +4753,7 @@ mod tests {
// Test-only reusable Win32 HANDLE RAII helper.
// If a future non-test path needs the same pattern, move it out of this test module.
//
// This struct is similar to `hbb_common::platform::windows::RAIIHandle`,
// This struct is similar to `base::platform::windows::RAIIHandle`,
// but `RAIIHandle` depends on `WinApi` crate, while this `HandleGuard` only depends on `windows` crate.
struct HandleGuard(WinHANDLE);

View File

@@ -7,7 +7,6 @@ use hbb_common::{
config::READ_TIMEOUT,
futures::{SinkExt, StreamExt},
log,
message_proto::*,
protobuf::Message as _,
rendezvous_proto::ConnType,
tcp, timeout,
@@ -15,6 +14,7 @@ use hbb_common::{
tokio_util::codec::{BytesCodec, Framed},
ResultType, Stream,
};
use base::message_proto::*;
fn run_rdp(port: u16, name: &str) {
std::process::Command::new("cmdkey")
@@ -551,7 +551,8 @@ fn take_socket(forward: Framed<TcpStream, BytesCodec>, mut prebuf: Vec<u8>) -> (
/// The controlling side's `enable-port-forward-mux`: on unless set to `N`.
pub fn mux_enabled() -> bool {
use hbb_common::config::{keys, option2bool, LocalConfig};
use hbb_common::config::{option2bool, LocalConfig};
use base::config::keys;
option2bool(
keys::OPTION_ENABLE_PORT_FORWARD_MUX,
&LocalConfig::get_option(keys::OPTION_ENABLE_PORT_FORWARD_MUX),
@@ -816,7 +817,8 @@ mod tests {
#[test]
fn port_forward_mux_defaults_to_on() {
use hbb_common::config::{keys, option2bool};
use hbb_common::config::option2bool;
use base::config::keys;
// option2bool's fallback branch is also "on unless N", so the value
// assertions below would pass for a prefixless key too. The `enable-`
// prefix is what actually guarantees the default, and renaming the key

View File

@@ -1,7 +1,6 @@
use hbb_common::{
bytes::Bytes,
log,
message_proto::*,
tokio::{
self,
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
@@ -10,6 +9,7 @@ use hbb_common::{
},
ResultType,
};
use base::message_proto::*;
use std::sync::{Arc, Mutex};
/// On the wire and fixed forever: what the controller may have in flight on a
@@ -892,7 +892,7 @@ mod tests {
#[test]
fn frame_builders_set_the_expected_union_variant() {
use hbb_common::message_proto::{message, port_forward_channel};
use base::message_proto::{message, port_forward_channel};
let m = data_msg(7, Bytes::from_static(b"abc"));
match m.union {
Some(message::Union::PortForwardChannel(ch)) => match ch.union {
@@ -917,7 +917,7 @@ mod tests {
}
}
use hbb_common::message_proto::{message, port_forward_channel};
use base::message_proto::{message, port_forward_channel};
use hbb_common::tokio::{self, io::AsyncReadExt, io::AsyncWriteExt, sync::mpsc};
use std::sync::{Arc, Mutex};

View File

@@ -1,4 +1,4 @@
use hbb_common::platform::windows::is_windows_version_or_greater;
use base::platform::windows::is_windows_version_or_greater;
pub use super::win_topmost_window::PrivacyModeImpl;

View File

@@ -14,9 +14,7 @@ use uuid::Uuid;
use hbb_common::{
allow_err,
anyhow::{self, bail},
config::{
self, keys::*, option2bool, use_ws, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT,
},
config::{self, option2bool, use_ws, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT},
futures::future::join_all,
log,
protobuf::Message as _,
@@ -32,6 +30,7 @@ use hbb_common::{
webrtc::WebRTCStream,
AddrMangle, IntoTargetAddr, ResultType, Stream, TargetAddr,
};
use base::config::keys::*;
use crate::{
check_port,

View File

@@ -17,13 +17,13 @@ use hbb_common::{
bail,
config::{Config, CONNECT_TIMEOUT, RELAY_PORT},
log,
message_proto::*,
protobuf::{Enum, Message as _},
rendezvous_proto::*,
socket_client,
sodiumoxide::crypto::{box_, sign},
timeout, tokio, ResultType, Stream,
};
use base::message_proto::*;
use scrap::camera;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
@@ -597,7 +597,7 @@ pub async fn start_server(is_server: bool, no_server: bool) {
log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY"));
}
#[cfg(windows)]
hbb_common::platform::windows::start_cpu_performance_monitor();
base::platform::windows::start_cpu_performance_monitor();
});
if is_server {

View File

@@ -11,12 +11,14 @@ pub use crate::{
clipboard::{check_clipboard_files, FILE_CLIPBOARD_NAME as FILE_NAME},
clipboard_file::unix_file_clip,
};
#[cfg(target_os = "android")]
use base::config::keys;
#[cfg(all(feature = "unix-file-copy-paste", target_os = "linux"))]
use clipboard::platform::unix::fuse::{init_fuse_context, uninit_fuse_context};
#[cfg(not(target_os = "android"))]
use clipboard_master::CallbackResult;
#[cfg(target_os = "android")]
use hbb_common::config::{keys, option2bool};
use hbb_common::config::option2bool;
#[cfg(target_os = "android")]
use std::sync::atomic::{AtomicBool, Ordering};
use std::{

View File

@@ -30,13 +30,11 @@ use hbb_common::protobuf::EnumOrUnknown;
use hbb_common::{
config::{
self, decode_permanent_password_h1_from_storage, decode_preset_password_h1_from_storage,
keys, local_permanent_password_storage_is_usable_for_auth,
local_permanent_password_storage_is_usable_for_auth,
preset_permanent_password_storage_is_usable_for_auth, Config, TrustedDevice,
},
fs::{self, can_enable_overwrite_detection, JobType},
futures::{SinkExt, StreamExt},
get_time, get_version_number,
message_proto::{option_message::BoolOption, permission_info::Permission},
password_security::{self as password, ApproveMode},
sha2::{Digest, Sha256},
sleep, timeout,
@@ -47,6 +45,11 @@ use hbb_common::{
},
tokio_util::codec::{BytesCodec, Framed},
};
use base::{
config::keys,
fs::{self, can_enable_overwrite_detection, JobType},
message_proto::{option_message::BoolOption, permission_info::Permission},
};
#[cfg(any(target_os = "android", target_os = "ios"))]
use scrap::android::{call_main_service_key_event, call_main_service_pointer_input};
use scrap::camera;
@@ -5980,7 +5983,7 @@ impl Connection {
"Process clipboard message from clip, stop: {}, is_stopping_allowed: {}, file_transfer_enabled: {}",
stop, is_stopping_allowed, file_transfer_enabled);
if !stop {
use hbb_common::config::keys::OPTION_ONE_WAY_FILE_TRANSFER;
use base::config::keys::OPTION_ONE_WAY_FILE_TRANSFER;
// Note: Code will not reach here if `crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y"` is true.
// Because `file-clipboard` service will not be subscribed.
// But we still check it here to keep the same logic to windows version in `ui_cm_interface.rs`.

View File

@@ -158,7 +158,7 @@ pub(super) fn set_wayland_layout_baseline(baseline: Vec<scrap::wayland::display:
/// before taking that layout. See `WaylandLayout::note_capturer`.
#[cfg(all(target_os = "linux", feature = "drm"))]
pub(super) fn note_capturer_layout(
displays: &[hbb_common::platform::linux::WaylandDisplayInfo],
displays: &[base::platform::linux::WaylandDisplayInfo],
built_gen: u64,
) {
if displays.is_empty() {

View File

@@ -2,7 +2,8 @@
// privileged export (open + grab the scanout dma-buf fd), the EGL detile / RGBA convert runs here.
use crate::ipc::{connect_drm, Data, DrmDisplayInfo};
use hbb_common::{anyhow::anyhow, bail, log, message_proto::DisplayInfo, tokio, ResultType};
use hbb_common::{anyhow::anyhow, bail, log, tokio, ResultType};
use base::message_proto::DisplayInfo;
use scrap::drm_render::RenderConverter;
use scrap::drmtap_dl::drmtap_dmabuf_desc;
use scrap::{Frame, Pixfmt, PixelBuffer, TraitCapturer};
@@ -1581,7 +1582,7 @@ fn augment_with_wayland_geometry_from(
/// earlier connector must never steal an exact name match from a later one.
fn identity_matches(
drm: &[DrmDisplayInfo],
wl: &[hbb_common::platform::linux::WaylandDisplayInfo],
wl: &[base::platform::linux::WaylandDisplayInfo],
) -> Vec<Option<usize>> {
let mut taken = vec![false; wl.len()];
let mut matched: Vec<Option<usize>> = vec![None; drm.len()];
@@ -1621,7 +1622,7 @@ fn identity_matches(
fn assign_wayland_outputs(
drm: &[DrmDisplayInfo],
wl: &[hbb_common::platform::linux::WaylandDisplayInfo],
wl: &[base::platform::linux::WaylandDisplayInfo],
) -> Vec<Option<usize>> {
let mut matched = identity_matches(drm, wl);
let mut taken = vec![false; wl.len()];
@@ -2107,8 +2108,8 @@ mod drm_capturer_tests {
y: i32,
w: i32,
h: i32,
) -> hbb_common::platform::linux::WaylandDisplayInfo {
hbb_common::platform::linux::WaylandDisplayInfo {
) -> base::platform::linux::WaylandDisplayInfo {
base::platform::linux::WaylandDisplayInfo {
name: name.to_owned(),
x,
y,

View File

@@ -4,14 +4,13 @@ use super::*;
use crate::input::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::whiteboard;
use base::message_proto::{
pointer_device_event::Union::TouchEvent, touch_event::Union::ScaleUpdate,
};
#[cfg(target_os = "macos")]
use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
use hbb_common::{
get_time,
message_proto::{pointer_device_event::Union::TouchEvent, touch_event::Union::ScaleUpdate},
protobuf::EnumOrUnknown,
};
use hbb_common::{get_time, protobuf::EnumOrUnknown};
use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
#[cfg(target_os = "macos")]
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};

View File

@@ -6,10 +6,10 @@ use crate::port_forward_mux::{
use hbb_common::{
bytes::Bytes,
log,
message_proto::*,
timeout,
tokio::{self, net::TcpStream, sync::{mpsc, watch}},
};
use base::message_proto::*;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
@@ -262,7 +262,6 @@ mod tests {
use super::*;
use crate::port_forward_mux::{CHANNEL_WINDOW, INITIAL_WINDOW, MAX_CHANNELS, MIN_FRAME_CHARGE};
use hbb_common::{
message_proto::{message, port_forward_channel},
tokio::{
self,
io::{AsyncReadExt, AsyncWriteExt},
@@ -271,6 +270,7 @@ mod tests {
time::Instant,
},
};
use base::message_proto::{message, port_forward_channel};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()

View File

@@ -6,12 +6,12 @@ use crate::{
validate_path_for_portable_service_shmem_dir,
},
};
use base::message_proto::{KeyEvent, MouseEvent};
use core::slice;
use hbb_common::{
allow_err,
anyhow::anyhow,
bail, libc, log,
message_proto::{KeyEvent, MouseEvent},
protobuf::Message,
tokio::{self, sync::mpsc},
ResultType,
@@ -435,7 +435,7 @@ mod utils {
// functions called in separate SYSTEM user process.
pub mod server {
use hbb_common::message_proto::PointerDeviceEvent;
use base::message_proto::PointerDeviceEvent;
use crate::display_service;
@@ -826,7 +826,8 @@ pub mod server {
pub mod client {
use super::*;
use crate::display_service;
use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent};
use base::message_proto::PointerDeviceEvent;
use hbb_common::anyhow::Context;
use scrap::PixelBuffer;
lazy_static::lazy_static! {

View File

@@ -9,7 +9,7 @@ use std::collections::HashMap;
use std::sync::Arc;
pub mod client {
use hbb_common::platform::linux::{DISPLAY_DESKTOP_KDE, XDG_CURRENT_DESKTOP};
use base::platform::linux::{DISPLAY_DESKTOP_KDE, XDG_CURRENT_DESKTOP};
use super::*;

View File

@@ -1076,7 +1076,7 @@ fn get_recorder(
#[cfg(target_os = "android")]
fn check_change_scale(hardware: bool) -> ResultType<()> {
use hbb_common::config::keys::OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE as SCALE_SOFT;
use base::config::keys::OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE as SCALE_SOFT;
// isStart flag is set at the end of startCapture() in Android, wait it to be set.
let n = 60; // 3s

View File

@@ -1,5 +1,6 @@
use super::*;
use hbb_common::{allow_err, anyhow, platform::linux::DISTRO};
use hbb_common::{allow_err, anyhow};
use base::platform::linux::DISTRO;
use scrap::{
is_cursor_embedded, set_map_err,
wayland::pipewire::{fill_displays, try_fix_logical_size},

View File

@@ -4,12 +4,13 @@ use crate::ipc::Data;
#[cfg(windows)]
use hbb_common::tokio;
use hbb_common::{allow_err, log};
use base::config::keys;
use std::sync::{Arc, Mutex};
#[cfg(windows)]
use std::time::Duration;
pub fn start_tray() {
if crate::ui_interface::get_builtin_option(hbb_common::config::keys::OPTION_HIDE_TRAY) == "Y" {
if crate::ui_interface::get_builtin_option(keys::OPTION_HIDE_TRAY) == "Y" {
#[cfg(not(target_os = "macos"))]
{
return;
@@ -64,7 +65,7 @@ fn make_tray() -> hbb_common::ResultType<()> {
let tray_menu = Menu::new();
let hide_stop_service = crate::ui_interface::get_builtin_option(
hbb_common::config::keys::OPTION_HIDE_STOP_SERVICE,
keys::OPTION_HIDE_STOP_SERVICE,
) == "Y";
// The tray icon is only shown when the service is running, so we don't need to check
// the `stop-service` option here.
@@ -147,7 +148,7 @@ fn make_tray() -> hbb_common::ResultType<()> {
if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
// for fixing https://github.com/rustdesk/rustdesk/discussions/10210#discussioncomment-14600745
// so we start tray, but not to show it
if crate::ui_interface::get_builtin_option(hbb_common::config::keys::OPTION_HIDE_TRAY) == "Y" {
if crate::ui_interface::get_builtin_option(keys::OPTION_HIDE_TRAY) == "Y" {
return;
}
// We create the icon once the event loop is actually running

View File

@@ -15,7 +15,10 @@ use sciter::{
};
use hbb_common::{
allow_err, fs::TransferJobMeta, log, message_proto::*, rendezvous_proto::ConnType,
allow_err, log, rendezvous_proto::ConnType,
};
use base::{
fs::TransferJobMeta, message_proto::*,
};
use crate::{

View File

@@ -5,20 +5,24 @@ use crate::ipc::{self, Data};
#[cfg(target_os = "windows")]
use crate::{clipboard::ClipboardSide, ipc::ClipboardNonFile};
#[cfg(target_os = "windows")]
use clipboard::ContextSend;
use base::config::keys::*;
#[cfg(not(any(target_os = "ios")))]
use hbb_common::fs::serialize_transfer_job;
use base::fs::serialize_transfer_job;
use base::{
config::keys::{OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW, OPTION_FILE_TRANSFER_MAX_FILES},
fs::{self, get_string, is_write_need_confirmation, new_send_confirm, DigestCheckResult},
message_proto::*,
};
#[cfg(target_os = "windows")]
use clipboard::ContextSend;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::tokio::sync::mpsc::unbounded_channel;
#[cfg(target_os = "windows")]
use hbb_common::tokio::sync::Mutex as TokioMutex;
use hbb_common::{
allow_err, bail,
config::{
keys::{OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW, OPTION_FILE_TRANSFER_MAX_FILES},
option2bool, Config,
},
fs::{self, get_string, is_write_need_confirmation, new_send_confirm, DigestCheckResult},
config::{option2bool, Config},
log,
message_proto::*,
protobuf::Message as _,
tokio::{
self,
@@ -27,8 +31,6 @@ use hbb_common::{
},
ResultType,
};
#[cfg(target_os = "windows")]
use hbb_common::{config::keys::*, tokio::sync::Mutex as TokioMutex};
use serde_derive::Serialize;
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use std::iter::FromIterator;
@@ -1400,7 +1402,7 @@ async fn start_read_job(
/// Process read jobs periodically, reading file blocks and sending them via IPC.
///
/// NOTE: This is the CM-side equivalent of `handle_read_jobs()` in
/// `libs/hbb_common/src/fs.rs`. The logic mirrors that implementation
/// `libs/base/src/fs.rs`. The logic mirrors that implementation
/// but communicates via IPC instead of direct network stream.
/// When modifying job processing logic, ensure both implementations stay in sync.
#[cfg(not(any(target_os = "ios")))]
@@ -1499,7 +1501,7 @@ async fn handle_read_jobs_tick(
/// Initialize a read job's data stream and handle digest sending for overwrite detection.
///
/// NOTE: This is the CM-side equivalent of `TransferJob::init_data_stream()` in
/// `libs/hbb_common/src/fs.rs`. It calls `init_data_stream_for_cm()` and sends
/// `libs/base/src/fs.rs`. It calls `init_data_stream_for_cm()` and sends
/// digest via IPC instead of direct network stream.
/// When modifying initialization or digest logic, ensure both paths stay in sync.
#[cfg(not(any(target_os = "ios")))]
@@ -1777,10 +1779,8 @@ mod tests {
use super::*;
use crate::ipc::Data;
use hbb_common::{
message_proto::{FileDirectory, Message},
tokio::{runtime::Runtime, sync::mpsc::unbounded_channel},
};
use base::message_proto::{FileDirectory, Message};
use hbb_common::tokio::{runtime::Runtime, sync::mpsc::unbounded_channel};
use std::fs;
#[test]

View File

@@ -1,9 +1,10 @@
use base::config::keys::{self, *};
#[cfg(any(target_os = "android", target_os = "ios"))]
use hbb_common::password_security;
use hbb_common::{
allow_err,
bytes::Bytes,
config::{self, keys::*, Config, LocalConfig, PeerConfig, CONNECT_TIMEOUT, RENDEZVOUS_PORT},
config::{self, Config, LocalConfig, PeerConfig, CONNECT_TIMEOUT, RENDEZVOUS_PORT},
directories_next,
futures::future::join_all,
log,
@@ -185,11 +186,11 @@ pub fn use_texture_render() -> bool {
#[cfg(target_os = "macos")]
return cfg!(feature = "flutter")
&& LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
&& LocalConfig::get_option(keys::OPTION_TEXTURE_RENDER) == "Y";
#[cfg(target_os = "linux")]
return cfg!(feature = "flutter")
&& LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N";
&& LocalConfig::get_option(keys::OPTION_TEXTURE_RENDER) != "N";
#[cfg(target_os = "windows")]
{
@@ -202,9 +203,9 @@ pub fn use_texture_render() -> bool {
#[cfg(not(debug_assertions))]
let default_texture = crate::platform::is_win_10_or_greater();
if default_texture {
LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
LocalConfig::get_option(keys::OPTION_TEXTURE_RENDER) != "N"
} else {
return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
return LocalConfig::get_option(keys::OPTION_TEXTURE_RENDER) == "Y";
}
}
}

View File

@@ -7,16 +7,16 @@ use crate::{
ui_interface::use_texture_render,
};
use async_trait::async_trait;
use bytes::Bytes;
#[cfg(all(target_os = "windows", not(feature = "flutter")))]
use hbb_common::config::keys;
use base::config::keys;
#[cfg(not(feature = "flutter"))]
use hbb_common::fs;
use base::fs;
use base::message_proto::*;
use bytes::Bytes;
use hbb_common::{
allow_err,
config::{Config, LocalConfig, PeerConfig},
get_version_number, log,
message_proto::*,
rendezvous_proto::ConnType,
tokio::{
self,
@@ -1666,7 +1666,7 @@ impl<T: InvokeUiSession> Session<T> {
let to = std::env::temp_dir().join(format!("rustdesk_printer_{id}"));
self.send(Data::SendFiles((
id,
hbb_common::fs::JobType::Printer,
base::fs::JobType::Printer,
path,
to.to_string_lossy().to_string(),
0,
@@ -1908,7 +1908,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
}
}
fn swap_modifier_mouse(&self, msg: &mut hbb_common::protos::message::MouseEvent) {
fn swap_modifier_mouse(&self, msg: &mut base::protos::message::MouseEvent) {
let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string());
if allow_swap_key {
msg.modifiers = msg

View File

@@ -1,5 +1,6 @@
use crate::{common::do_check_software_update, hbbs_http::create_http_client_with_url_strict};
use hbb_common::{bail, config, log, ResultType};
use base::config::keys;
use std::{
io::Write,
path::{Component, Path, PathBuf},
@@ -181,7 +182,7 @@ fn check_update(manually: bool) -> ResultType<()> {
}
#[cfg(target_os = "windows")]
let update_msi = crate::platform::is_msi_installed()? && !crate::is_custom_client();
if !(manually || config::Config::get_bool_option(config::keys::OPTION_ALLOW_AUTO_UPDATE)) {
if !(manually || config::Config::get_bool_option(keys::OPTION_ALLOW_AUTO_UPDATE)) {
return Ok(());
}
if do_check_software_update().is_err() {
@@ -535,7 +536,7 @@ pub fn start_auto_update_macos() {
pub fn check_update_as_root() -> ResultType<bool> {
let _update_lock = acquire_mac_update_lock()?;
// Allow-auto-update setting
if !config::Config::get_bool_option(config::keys::OPTION_ALLOW_AUTO_UPDATE) {
if !config::Config::get_bool_option(keys::OPTION_ALLOW_AUTO_UPDATE) {
log::info!("[root-update] Auto update is disabled, skipping.");
return Ok(false);
}

View File

@@ -1,4 +1,5 @@
use hbb_common::{bail, platform::windows::is_windows_version_or_greater, ResultType};
use base::platform::windows::is_windows_version_or_greater;
use hbb_common::{bail, ResultType};
// This string is defined here.
// https://github.com/rustdesk-org/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40