Files
rustdesk/src/clipboard_file.rs
RustDesk b50fde6910 add the base crate and repoint the moved modules at it (#16107)
* 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

* one `use` per crate, and write the rule down

`fs.rs` came out of the move with two ungated `use hbb_common::` statements,
because the original single `use crate::{...}` had to give up `message_proto`
to the new crate and the rest was left in a second block. Fold it back into one.

A scan of the whole tree for the same shape finds nothing else: every other file
with more than one top-level `use base::` or `use hbb_common::` is split by a
`#[cfg]` that does not cover the whole block, or by `pub use` next to `use`.
Those are the cases that cannot merge, so AGENTS.md now states both the rule and
the exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:46:42 +08:00

436 lines
16 KiB
Rust

use clipboard::ClipboardFile;
use base::message_proto::*;
pub fn clip_2_msg(clip: ClipboardFile) -> Message {
match clip {
ClipboardFile::NotifyCallback {
r#type,
title,
text,
} => Message {
union: Some(message::Union::MessageBox(MessageBox {
msgtype: r#type,
title,
text,
link: "".to_string(),
..Default::default()
})),
..Default::default()
},
ClipboardFile::MonitorReady => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::Ready(CliprdrMonitorReady {
..Default::default()
})),
..Default::default()
})),
..Default::default()
},
ClipboardFile::FormatList { format_list } => {
let mut formats: Vec<CliprdrFormat> = Vec::new();
for v in format_list.iter() {
formats.push(CliprdrFormat {
id: v.0,
format: v.1.clone(),
..Default::default()
});
}
Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatList(CliprdrServerFormatList {
formats,
..Default::default()
})),
..Default::default()
})),
..Default::default()
}
}
ClipboardFile::FormatListResponse { msg_flags } => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatListResponse(
CliprdrServerFormatListResponse {
msg_flags,
..Default::default()
},
)),
..Default::default()
})),
..Default::default()
},
ClipboardFile::FormatDataRequest {
requested_format_id,
} => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatDataRequest(
CliprdrServerFormatDataRequest {
requested_format_id,
..Default::default()
},
)),
..Default::default()
})),
..Default::default()
},
ClipboardFile::FormatDataResponse {
msg_flags,
format_data,
} => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatDataResponse(
CliprdrServerFormatDataResponse {
msg_flags,
format_data: format_data.into(),
..Default::default()
},
)),
..Default::default()
})),
..Default::default()
},
ClipboardFile::FileContentsRequest {
stream_id,
list_index,
dw_flags,
n_position_low,
n_position_high,
cb_requested,
have_clip_data_id,
clip_data_id,
} => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FileContentsRequest(
CliprdrFileContentsRequest {
stream_id,
list_index,
dw_flags,
n_position_low,
n_position_high,
cb_requested,
have_clip_data_id,
clip_data_id,
..Default::default()
},
)),
..Default::default()
})),
..Default::default()
},
ClipboardFile::FileContentsResponse {
msg_flags,
stream_id,
requested_data,
} => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FileContentsResponse(
CliprdrFileContentsResponse {
msg_flags,
stream_id,
requested_data: requested_data.into(),
..Default::default()
},
)),
..Default::default()
})),
..Default::default()
},
ClipboardFile::TryEmpty => Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::TryEmpty(CliprdrTryEmpty {
..Default::default()
})),
..Default::default()
})),
..Default::default()
},
ClipboardFile::Files { files } => {
let files = files
.iter()
.filter_map(|(f, s)| {
if *s == 0 {
if let Ok(meta) = std::fs::metadata(f) {
Some(CliprdrFile {
name: f.to_owned(),
size: meta.len(),
..Default::default()
})
} else {
None
}
} else {
Some(CliprdrFile {
name: f.to_owned(),
size: *s,
..Default::default()
})
}
})
.collect::<Vec<_>>();
Message {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::Files(CliprdrFiles {
files,
..Default::default()
})),
..Default::default()
})),
..Default::default()
}
}
}
}
pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipboardFile> {
match msg.union {
Some(cliprdr::Union::Ready(_)) => Some(ClipboardFile::MonitorReady),
Some(cliprdr::Union::FormatList(data)) => {
let mut format_list: Vec<(i32, String)> = Vec::new();
for v in data.formats.iter() {
format_list.push((v.id, v.format.clone()));
}
Some(ClipboardFile::FormatList { format_list })
}
Some(cliprdr::Union::FormatListResponse(data)) => Some(ClipboardFile::FormatListResponse {
msg_flags: data.msg_flags,
}),
Some(cliprdr::Union::FormatDataRequest(data)) => Some(ClipboardFile::FormatDataRequest {
requested_format_id: data.requested_format_id,
}),
Some(cliprdr::Union::FormatDataResponse(data)) => Some(ClipboardFile::FormatDataResponse {
msg_flags: data.msg_flags,
format_data: data.format_data.into(),
}),
Some(cliprdr::Union::FileContentsRequest(data)) => {
Some(ClipboardFile::FileContentsRequest {
stream_id: data.stream_id,
list_index: data.list_index,
dw_flags: data.dw_flags,
n_position_low: data.n_position_low,
n_position_high: data.n_position_high,
cb_requested: data.cb_requested,
have_clip_data_id: data.have_clip_data_id,
clip_data_id: data.clip_data_id,
})
}
Some(cliprdr::Union::FileContentsResponse(data)) => {
Some(ClipboardFile::FileContentsResponse {
msg_flags: data.msg_flags,
stream_id: data.stream_id,
requested_data: data.requested_data.into(),
})
}
Some(cliprdr::Union::TryEmpty(_)) => Some(ClipboardFile::TryEmpty),
_ => None,
}
}
#[cfg(feature = "unix-file-copy-paste")]
pub mod unix_file_clip {
use super::*;
#[cfg(target_os = "linux")]
use crate::clipboard::update_clipboard_files;
use crate::clipboard::{try_empty_clipboard_files, ClipboardSide};
#[cfg(target_os = "linux")]
use clipboard::platform::unix::fuse;
use clipboard::platform::unix::{
get_local_format, serv_files, FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME,
FILEDESCRIPTORW_FORMAT_NAME, FILEDESCRIPTOR_FORMAT_ID,
};
use hbb_common::log;
use std::sync::{Arc, Mutex};
lazy_static::lazy_static! {
static ref CLIPBOARD_CTX: Arc<Mutex<Option<crate::clipboard::ClipboardContext>>> = Arc::new(Mutex::new(None));
}
pub fn get_format_list() -> ClipboardFile {
let fd_format_name = get_local_format(FILEDESCRIPTOR_FORMAT_ID)
.unwrap_or(FILEDESCRIPTORW_FORMAT_NAME.to_string());
let fc_format_name = get_local_format(FILECONTENTS_FORMAT_ID)
.unwrap_or(FILECONTENTS_FORMAT_NAME.to_string());
ClipboardFile::FormatList {
format_list: vec![
(FILEDESCRIPTOR_FORMAT_ID, fd_format_name),
(FILECONTENTS_FORMAT_ID, fc_format_name),
],
}
}
#[inline]
fn msg_resp_format_data_failure() -> Message {
clip_2_msg(ClipboardFile::FormatDataResponse {
msg_flags: 0x2,
format_data: vec![],
})
}
#[inline]
fn resp_file_contents_fail(stream_id: i32) -> Message {
clip_2_msg(ClipboardFile::FileContentsResponse {
msg_flags: 0x2,
stream_id,
requested_data: vec![],
})
}
pub fn serve_clip_messages(
side: ClipboardSide,
clip: ClipboardFile,
conn_id: i32,
) -> Vec<Message> {
log::debug!("got clipfile from client peer");
match clip {
ClipboardFile::MonitorReady => {
log::debug!("client is ready for clipboard");
}
ClipboardFile::FormatList { format_list } => {
if !format_list
.iter()
.find(|(_, name)| name == FILECONTENTS_FORMAT_NAME)
.map(|(id, _)| *id)
.is_some()
{
log::error!("no file contents format found");
return vec![];
};
let Some(file_descriptor_id) = format_list
.iter()
.find(|(_, name)| name == FILEDESCRIPTORW_FORMAT_NAME)
.map(|(id, _)| *id)
else {
log::error!("no file descriptor format found");
return vec![];
};
// sync file system from peer
let data = ClipboardFile::FormatDataRequest {
requested_format_id: file_descriptor_id,
};
return vec![clip_2_msg(data)];
}
ClipboardFile::FormatListResponse {
msg_flags: _msg_flags,
} => {}
ClipboardFile::FormatDataRequest {
requested_format_id: _requested_format_id,
} => {
log::debug!("requested format id: {}", _requested_format_id);
let format_data = serv_files::get_file_list_pdu();
if !format_data.is_empty() {
return vec![clip_2_msg(ClipboardFile::FormatDataResponse {
msg_flags: 1,
format_data,
})];
}
// empty file list, send failure message
return vec![msg_resp_format_data_failure()];
}
#[cfg(target_os = "linux")]
ClipboardFile::FormatDataResponse {
msg_flags,
format_data,
} => {
log::debug!("format data response: msg_flags: {}", msg_flags);
if msg_flags != 0x1 {
log::error!(
"peer reported clipboard format data failure: {}",
msg_flags
);
return vec![];
}
log::debug!("parsing file descriptors");
match fuse::init_fuse_context(side == ClipboardSide::Client) {
Ok(()) => match fuse::format_data_response_to_urls(
side == ClipboardSide::Client,
format_data,
conn_id,
) {
Ok(files) => {
update_clipboard_files(files, side);
}
Err(e) => {
log::error!("failed to parse file descriptors: {:?}", e);
}
},
Err(e) => {
log::error!("failed to initialize clipboard FUSE context: {:?}", e);
}
}
}
ClipboardFile::FileContentsRequest {
stream_id,
list_index,
dw_flags,
n_position_low,
n_position_high,
cb_requested,
..
} => {
log::debug!("file contents request: stream_id: {}, list_index: {}, dw_flags: {}, n_position_low: {}, n_position_high: {}, cb_requested: {}", stream_id, list_index, dw_flags, n_position_low, n_position_high, cb_requested);
return serv_files::read_file_contents(
conn_id,
stream_id,
list_index,
dw_flags,
n_position_low,
n_position_high,
cb_requested,
)
.into_iter()
.map(|res| match res {
Ok(data) => clip_2_msg(data),
Err(e) => {
log::error!("failed to read file contents: {:?}", e);
resp_file_contents_fail(stream_id)
}
})
.collect::<_>();
}
#[cfg(target_os = "linux")]
ClipboardFile::FileContentsResponse {
msg_flags,
stream_id,
requested_data,
..
} => {
log::debug!(
"file contents response: msg_flags: {}, stream_id: {}",
msg_flags,
stream_id,
);
let response = ClipboardFile::FileContentsResponse {
msg_flags,
stream_id,
requested_data,
};
if let Err(e) =
fuse::handle_file_content_response(side == ClipboardSide::Client, response)
{
log::error!("failed to handle file contents response: {:?}", e);
}
}
ClipboardFile::NotifyCallback {
r#type,
title,
text,
} => {
// unreachable, but still log it
log::debug!(
"notify callback: type: {}, title: {}, text: {}",
r#type,
title,
text
);
}
ClipboardFile::TryEmpty => {
try_empty_clipboard_files(side, conn_id);
}
_ => {
log::error!("unsupported clipboard file type");
}
}
vec![]
}
}