diff --git a/.gitignore b/.gitignore index cce3b90a1..56f4c29c6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ __pycache__ src/version.rs *dmg sciter.dll +**pdb diff --git a/Cargo.lock b/Cargo.lock index ebc50fcb6..32e50572e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,6 +388,16 @@ dependencies = [ "textwrap 0.14.2", ] +[[package]] +name = "clipboard" +version = "0.1.0" +dependencies = [ + "cc", + "hbb_common", + "lazy_static", + "thiserror", +] + [[package]] name = "clipboard-master" version = "3.1.3" @@ -3365,6 +3375,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "clap 3.0.10", + "clipboard", "clipboard-master", "cocoa 0.24.0", "core-foundation 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index a6218e983..7cab6e7a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ whoami = "1.2" scrap = { path = "libs/scrap" } hbb_common = { path = "libs/hbb_common" } enigo = { path = "libs/enigo" } +clipboard = { path = "libs/clipboard" } sys-locale = "0.1" serde_derive = "1.0" serde = "1.0" @@ -82,7 +83,7 @@ rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" } android_logger = "0.10" [workspace] -members = ["libs/scrap", "libs/hbb_common", "libs/enigo"] +members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard"] [package.metadata.winres] LegalCopyright = "Copyright © 2020" diff --git a/libs/clipboard/Cargo.toml b/libs/clipboard/Cargo.toml new file mode 100644 index 000000000..3d2b5ef77 --- /dev/null +++ b/libs/clipboard/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "clipboard" +version = "0.1.0" +edition = "2021" +build= "build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +cc = "1.0" + +[dependencies] +thiserror = "1.0.30" +lazy_static = "1.4" +hbb_common = { path = "../hbb_common" } diff --git a/libs/clipboard/README.md b/libs/clipboard/README.md new file mode 100644 index 000000000..ea2b85785 --- /dev/null +++ b/libs/clipboard/README.md @@ -0,0 +1,16 @@ +# clipboard + +Copy files and text through network. +Main lowlevel logic from [FreeRDP](https://github.com/FreeRDP/FreeRDP). + +TODO: Move this lib to a separate project. + +## impl + +### windows + +![scene](./docs/assets/scene3.png) + +![A1->B1](./docs/assets/win_A_B.png) + +![B1->A1](./docs/assets/win_B_A.png) diff --git a/libs/clipboard/build.rs b/libs/clipboard/build.rs new file mode 100644 index 000000000..b5c547637 --- /dev/null +++ b/libs/clipboard/build.rs @@ -0,0 +1,43 @@ +use cc; + +fn build_c_impl() { + let mut build = cc::Build::new(); + + #[cfg(target_os = "windows")] + build.file("src/windows/wf_cliprdr.c"); + #[cfg(target_os = "linux")] + build.file("src/X11/xf_cliprdr.c"); + #[cfg(target_os = "macos")] + build.file("src/OSX/Clipboard.m"); + + build.flag_if_supported("-Wno-c++0x-extensions"); + build.flag_if_supported("-Wno-return-type-c-linkage"); + build.flag_if_supported("-Wno-invalid-offsetof"); + build.flag_if_supported("-Wno-unused-parameter"); + + if build.get_compiler().is_like_msvc() { + build.define("WIN32", ""); + // build.define("_AMD64_", ""); + build.flag("-Zi"); + build.flag("-GR-"); + // build.flag("-std:c++11"); + } else { + build.flag("-fPIC"); + // build.flag("-std=c++11"); + // build.flag("-include"); + // build.flag(&confdefs_path.to_string_lossy()); + } + + build.compile("mycliprdr"); + + #[cfg(target_os = "windows")] + println!("cargo:rerun-if-changed=src/windows/wf_cliprdr.c"); + #[cfg(target_os = "linux")] + println!("cargo:rerun-if-changed=src/X11/xf_cliprdr.c"); + #[cfg(target_os = "macos")] + println!("cargo:rerun-if-changed=src/OSX/Clipboard.m"); +} + +fn main() { + build_c_impl(); +} diff --git a/libs/clipboard/docs/assets/scene3.png b/libs/clipboard/docs/assets/scene3.png new file mode 100644 index 000000000..639236460 Binary files /dev/null and b/libs/clipboard/docs/assets/scene3.png differ diff --git a/libs/clipboard/docs/assets/win_A_B.png b/libs/clipboard/docs/assets/win_A_B.png new file mode 100644 index 000000000..0b6b25d0e Binary files /dev/null and b/libs/clipboard/docs/assets/win_A_B.png differ diff --git a/libs/clipboard/docs/assets/win_B_A.png b/libs/clipboard/docs/assets/win_B_A.png new file mode 100644 index 000000000..377fa801f Binary files /dev/null and b/libs/clipboard/docs/assets/win_B_A.png differ diff --git a/libs/clipboard/src/OSX/Clipboard.m b/libs/clipboard/src/OSX/Clipboard.m new file mode 100644 index 000000000..658579ce0 --- /dev/null +++ b/libs/clipboard/src/OSX/Clipboard.m @@ -0,0 +1,11 @@ +#include "../cliprdr.h" + +void mac_cliprdr_init(CliprdrClientContext *cliprdr) +{ + (void)cliprdr; +} + +void mac_cliprdr_uninit(CliprdrClientContext *cliprdr) +{ + (void)cliprdr; +} diff --git a/libs/clipboard/src/X11/xf_cliprdr.c b/libs/clipboard/src/X11/xf_cliprdr.c new file mode 100644 index 000000000..55d34a088 --- /dev/null +++ b/libs/clipboard/src/X11/xf_cliprdr.c @@ -0,0 +1,11 @@ +#include "../cliprdr.h" + +void xf_cliprdr_init(CliprdrClientContext* cliprdr) +{ + (void)cliprdr; +} + +void xf_cliprdr_uninit( CliprdrClientContext* cliprdr) +{ + (void)cliprdr; +} diff --git a/libs/clipboard/src/cliprdr.h b/libs/clipboard/src/cliprdr.h new file mode 100644 index 000000000..0e581cc2f --- /dev/null +++ b/libs/clipboard/src/cliprdr.h @@ -0,0 +1,231 @@ +#ifndef WF_CLIPRDR_H__ +#define WF_CLIPRDR_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef signed char INT8, *PINT8; + typedef signed short INT16, *PINT16; + typedef signed int INT32, *PINT32; + typedef unsigned char UINT8, *PUINT8; + typedef unsigned short UINT16, *PUINT16; + typedef unsigned int UINT32, *PUINT32; + typedef unsigned int UINT; + typedef int BOOL; + typedef unsigned char BYTE; + +/* Clipboard Messages */ +#define DEFINE_CLIPRDR_HEADER_COMMON() \ + UINT32 serverConnID; \ + UINT32 remoteConnID; \ + UINT16 msgType; \ + UINT16 msgFlags; \ + UINT32 dataLen + + struct _CLIPRDR_HEADER + { + DEFINE_CLIPRDR_HEADER_COMMON(); + }; + typedef struct _CLIPRDR_HEADER CLIPRDR_HEADER; + + struct _CLIPRDR_CAPABILITY_SET + { + UINT16 capabilitySetType; + UINT16 capabilitySetLength; + }; + typedef struct _CLIPRDR_CAPABILITY_SET CLIPRDR_CAPABILITY_SET; + + struct _CLIPRDR_GENERAL_CAPABILITY_SET + { + UINT16 capabilitySetType; + UINT16 capabilitySetLength; + + UINT32 version; + UINT32 generalFlags; + }; + typedef struct _CLIPRDR_GENERAL_CAPABILITY_SET CLIPRDR_GENERAL_CAPABILITY_SET; + + struct _CLIPRDR_CAPABILITIES + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 cCapabilitiesSets; + CLIPRDR_CAPABILITY_SET *capabilitySets; + }; + typedef struct _CLIPRDR_CAPABILITIES CLIPRDR_CAPABILITIES; + + struct _CLIPRDR_MONITOR_READY + { + DEFINE_CLIPRDR_HEADER_COMMON(); + }; + typedef struct _CLIPRDR_MONITOR_READY CLIPRDR_MONITOR_READY; + + struct _CLIPRDR_TEMP_DIRECTORY + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + char szTempDir[520]; + }; + typedef struct _CLIPRDR_TEMP_DIRECTORY CLIPRDR_TEMP_DIRECTORY; + + struct _CLIPRDR_FORMAT + { + UINT32 formatId; + char *formatName; + }; + typedef struct _CLIPRDR_FORMAT CLIPRDR_FORMAT; + + struct _CLIPRDR_FORMAT_LIST + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 numFormats; + CLIPRDR_FORMAT *formats; + }; + typedef struct _CLIPRDR_FORMAT_LIST CLIPRDR_FORMAT_LIST; + + struct _CLIPRDR_FORMAT_LIST_RESPONSE + { + DEFINE_CLIPRDR_HEADER_COMMON(); + }; + typedef struct _CLIPRDR_FORMAT_LIST_RESPONSE CLIPRDR_FORMAT_LIST_RESPONSE; + + struct _CLIPRDR_LOCK_CLIPBOARD_DATA + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 clipDataId; + }; + typedef struct _CLIPRDR_LOCK_CLIPBOARD_DATA CLIPRDR_LOCK_CLIPBOARD_DATA; + + struct _CLIPRDR_UNLOCK_CLIPBOARD_DATA + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 clipDataId; + }; + typedef struct _CLIPRDR_UNLOCK_CLIPBOARD_DATA CLIPRDR_UNLOCK_CLIPBOARD_DATA; + + struct _CLIPRDR_FORMAT_DATA_REQUEST + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 requestedFormatId; + }; + typedef struct _CLIPRDR_FORMAT_DATA_REQUEST CLIPRDR_FORMAT_DATA_REQUEST; + + struct _CLIPRDR_FORMAT_DATA_RESPONSE + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + const BYTE *requestedFormatData; + }; + typedef struct _CLIPRDR_FORMAT_DATA_RESPONSE CLIPRDR_FORMAT_DATA_RESPONSE; + + struct _CLIPRDR_FILE_CONTENTS_REQUEST + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 streamId; + UINT32 listIndex; + UINT32 dwFlags; + UINT32 nPositionLow; + UINT32 nPositionHigh; + UINT32 cbRequested; + BOOL haveClipDataId; + UINT32 clipDataId; + }; + typedef struct _CLIPRDR_FILE_CONTENTS_REQUEST CLIPRDR_FILE_CONTENTS_REQUEST; + + struct _CLIPRDR_FILE_CONTENTS_RESPONSE + { + DEFINE_CLIPRDR_HEADER_COMMON(); + + UINT32 streamId; + UINT32 cbRequested; + const BYTE *requestedData; + }; + typedef struct _CLIPRDR_FILE_CONTENTS_RESPONSE CLIPRDR_FILE_CONTENTS_RESPONSE; + + typedef struct _cliprdr_client_context CliprdrClientContext; + + typedef UINT (*pcCliprdrServerCapabilities)(CliprdrClientContext *context, + const CLIPRDR_CAPABILITIES *capabilities); + typedef UINT (*pcCliprdrClientCapabilities)(CliprdrClientContext *context, + const CLIPRDR_CAPABILITIES *capabilities); + typedef UINT (*pcCliprdrMonitorReady)(CliprdrClientContext *context, + const CLIPRDR_MONITOR_READY *monitorReady); + typedef UINT (*pcCliprdrTempDirectory)(CliprdrClientContext *context, + const CLIPRDR_TEMP_DIRECTORY *tempDirectory); + typedef UINT (*pcCliprdrClientFormatList)(CliprdrClientContext *context, + const CLIPRDR_FORMAT_LIST *formatList); + typedef UINT (*pcCliprdrServerFormatList)(CliprdrClientContext *context, + const CLIPRDR_FORMAT_LIST *formatList); + typedef UINT (*pcCliprdrClientFormatListResponse)( + CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse); + typedef UINT (*pcCliprdrServerFormatListResponse)( + CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse); + typedef UINT (*pcCliprdrClientLockClipboardData)( + CliprdrClientContext *context, const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData); + typedef UINT (*pcCliprdrServerLockClipboardData)( + CliprdrClientContext *context, const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData); + typedef UINT (*pcCliprdrClientUnlockClipboardData)( + CliprdrClientContext *context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData); + typedef UINT (*pcCliprdrServerUnlockClipboardData)( + CliprdrClientContext *context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData); + typedef UINT (*pcCliprdrClientFormatDataRequest)( + CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest); + typedef UINT (*pcCliprdrServerFormatDataRequest)( + CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest); + typedef UINT (*pcCliprdrClientFormatDataResponse)( + CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse); + typedef UINT (*pcCliprdrServerFormatDataResponse)( + CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse); + typedef UINT (*pcCliprdrClientFileContentsRequest)( + CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest); + typedef UINT (*pcCliprdrServerFileContentsRequest)( + CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest); + typedef UINT (*pcCliprdrClientFileContentsResponse)( + CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse); + typedef UINT (*pcCliprdrServerFileContentsResponse)( + CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse); + + // TODO: hide more members of clipboard context + struct _cliprdr_client_context + { + void *custom; + BOOL enableFiles; + BOOL enableOthers; + + pcCliprdrServerCapabilities ServerCapabilities; + pcCliprdrClientCapabilities ClientCapabilities; + pcCliprdrMonitorReady MonitorReady; + pcCliprdrTempDirectory TempDirectory; + pcCliprdrClientFormatList ClientFormatList; + pcCliprdrServerFormatList ServerFormatList; + pcCliprdrClientFormatListResponse ClientFormatListResponse; + pcCliprdrServerFormatListResponse ServerFormatListResponse; + pcCliprdrClientLockClipboardData ClientLockClipboardData; + pcCliprdrServerLockClipboardData ServerLockClipboardData; + pcCliprdrClientUnlockClipboardData ClientUnlockClipboardData; + pcCliprdrServerUnlockClipboardData ServerUnlockClipboardData; + pcCliprdrClientFormatDataRequest ClientFormatDataRequest; + pcCliprdrServerFormatDataRequest ServerFormatDataRequest; + pcCliprdrClientFormatDataResponse ClientFormatDataResponse; + pcCliprdrServerFormatDataResponse ServerFormatDataResponse; + pcCliprdrClientFileContentsRequest ClientFileContentsRequest; + pcCliprdrServerFileContentsRequest ServerFileContentsRequest; + pcCliprdrClientFileContentsResponse ClientFileContentsResponse; + pcCliprdrServerFileContentsResponse ServerFileContentsResponse; + + UINT32 lastRequestedFormatId; + }; + +#ifdef __cplusplus +} +#endif + +#endif // WF_CLIPRDR_H__ + diff --git a/libs/clipboard/src/cliprdr.rs b/libs/clipboard/src/cliprdr.rs new file mode 100644 index 000000000..b88d9c40d --- /dev/null +++ b/libs/clipboard/src/cliprdr.rs @@ -0,0 +1,566 @@ +#![allow(dead_code)] +#![allow(non_camel_case_types)] +#![allow(unused_variables)] +#![allow(non_snake_case)] +#![allow(deref_nullptr)] + +use std::{boxed::Box, result::Result}; +use thiserror::Error; + +pub type size_t = ::std::os::raw::c_ulonglong; +pub type __vcrt_bool = bool; +pub type wchar_t = ::std::os::raw::c_ushort; + +pub type POINTER_64_INT = ::std::os::raw::c_ulonglong; +pub type INT8 = ::std::os::raw::c_schar; +pub type PINT8 = *mut ::std::os::raw::c_schar; +pub type INT16 = ::std::os::raw::c_short; +pub type PINT16 = *mut ::std::os::raw::c_short; +pub type INT32 = ::std::os::raw::c_int; +pub type PINT32 = *mut ::std::os::raw::c_int; +pub type INT64 = ::std::os::raw::c_longlong; +pub type PINT64 = *mut ::std::os::raw::c_longlong; +pub type UINT8 = ::std::os::raw::c_uchar; +pub type PUINT8 = *mut ::std::os::raw::c_uchar; +pub type UINT16 = ::std::os::raw::c_ushort; +pub type PUINT16 = *mut ::std::os::raw::c_ushort; +pub type UINT32 = ::std::os::raw::c_uint; +pub type PUINT32 = *mut ::std::os::raw::c_uint; +pub type UINT64 = ::std::os::raw::c_ulonglong; +pub type PUINT64 = *mut ::std::os::raw::c_ulonglong; +pub type LONG32 = ::std::os::raw::c_int; +pub type PLONG32 = *mut ::std::os::raw::c_int; +pub type ULONG32 = ::std::os::raw::c_uint; +pub type PULONG32 = *mut ::std::os::raw::c_uint; +pub type DWORD32 = ::std::os::raw::c_uint; +pub type PDWORD32 = *mut ::std::os::raw::c_uint; +pub type INT_PTR = ::std::os::raw::c_longlong; +pub type PINT_PTR = *mut ::std::os::raw::c_longlong; +pub type UINT_PTR = ::std::os::raw::c_ulonglong; +pub type PUINT_PTR = *mut ::std::os::raw::c_ulonglong; +pub type LONG_PTR = ::std::os::raw::c_longlong; +pub type PLONG_PTR = *mut ::std::os::raw::c_longlong; +pub type ULONG_PTR = ::std::os::raw::c_ulonglong; +pub type PULONG_PTR = *mut ::std::os::raw::c_ulonglong; +pub type SHANDLE_PTR = ::std::os::raw::c_longlong; +pub type HANDLE_PTR = ::std::os::raw::c_ulonglong; +pub type UHALF_PTR = ::std::os::raw::c_uint; +pub type PUHALF_PTR = *mut ::std::os::raw::c_uint; +pub type HALF_PTR = ::std::os::raw::c_int; +pub type PHALF_PTR = *mut ::std::os::raw::c_int; +pub type SIZE_T = ULONG_PTR; +pub type PSIZE_T = *mut ULONG_PTR; +pub type SSIZE_T = LONG_PTR; +pub type PSSIZE_T = *mut LONG_PTR; +pub type DWORD_PTR = ULONG_PTR; +pub type PDWORD_PTR = *mut ULONG_PTR; +pub type LONG64 = ::std::os::raw::c_longlong; +pub type PLONG64 = *mut ::std::os::raw::c_longlong; +pub type ULONG64 = ::std::os::raw::c_ulonglong; +pub type PULONG64 = *mut ::std::os::raw::c_ulonglong; +pub type DWORD64 = ::std::os::raw::c_ulonglong; +pub type PDWORD64 = *mut ::std::os::raw::c_ulonglong; +pub type KAFFINITY = ULONG_PTR; +pub type PKAFFINITY = *mut KAFFINITY; +pub type PVOID = *mut ::std::os::raw::c_void; +pub type CHAR = ::std::os::raw::c_char; +pub type SHORT = ::std::os::raw::c_short; +pub type LONG = ::std::os::raw::c_long; +pub type WCHAR = wchar_t; +pub type PWCHAR = *mut WCHAR; +pub type LPWCH = *mut WCHAR; +pub type PWCH = *mut WCHAR; +pub type LPCWCH = *const WCHAR; +pub type PCWCH = *const WCHAR; +pub type NWPSTR = *mut WCHAR; +pub type LPWSTR = *mut WCHAR; +pub type PWSTR = *mut WCHAR; +pub type PZPWSTR = *mut PWSTR; +pub type PCZPWSTR = *const PWSTR; +pub type LPUWSTR = *mut WCHAR; +pub type PUWSTR = *mut WCHAR; +pub type LPCWSTR = *const WCHAR; +pub type PCWSTR = *const WCHAR; +pub type PZPCWSTR = *mut PCWSTR; +pub type PCZPCWSTR = *const PCWSTR; +pub type LPCUWSTR = *const WCHAR; +pub type PCUWSTR = *const WCHAR; +pub type PZZWSTR = *mut WCHAR; +pub type PCZZWSTR = *const WCHAR; +pub type PUZZWSTR = *mut WCHAR; +pub type PCUZZWSTR = *const WCHAR; +pub type PNZWCH = *mut WCHAR; +pub type PCNZWCH = *const WCHAR; +pub type PUNZWCH = *mut WCHAR; +pub type PCUNZWCH = *const WCHAR; +pub type PCHAR = *mut CHAR; +pub type LPCH = *mut CHAR; +pub type PCH = *mut CHAR; +pub type LPCCH = *const CHAR; +pub type PCCH = *const CHAR; +pub type NPSTR = *mut CHAR; +pub type LPSTR = *mut CHAR; +pub type PSTR = *mut CHAR; +pub type PZPSTR = *mut PSTR; +pub type PCZPSTR = *const PSTR; +pub type LPCSTR = *const CHAR; +pub type PCSTR = *const CHAR; +pub type PZPCSTR = *mut PCSTR; +pub type PCZPCSTR = *const PCSTR; +pub type PZZSTR = *mut CHAR; +pub type PCZZSTR = *const CHAR; +pub type PNZCH = *mut CHAR; +pub type PCNZCH = *const CHAR; +pub type TCHAR = ::std::os::raw::c_char; +pub type PTCHAR = *mut ::std::os::raw::c_char; +pub type TBYTE = ::std::os::raw::c_uchar; +pub type PTBYTE = *mut ::std::os::raw::c_uchar; +pub type LPTCH = LPCH; +pub type PTCH = LPCH; +pub type LPCTCH = LPCCH; +pub type PCTCH = LPCCH; +pub type PTSTR = LPSTR; +pub type LPTSTR = LPSTR; +pub type PUTSTR = LPSTR; +pub type LPUTSTR = LPSTR; +pub type PCTSTR = LPCSTR; +pub type LPCTSTR = LPCSTR; +pub type PCUTSTR = LPCSTR; +pub type LPCUTSTR = LPCSTR; +pub type PZZTSTR = PZZSTR; +pub type PUZZTSTR = PZZSTR; +pub type PCZZTSTR = PCZZSTR; +pub type PCUZZTSTR = PCZZSTR; +pub type PZPTSTR = PZPSTR; +pub type PNZTCH = PNZCH; +pub type PUNZTCH = PNZCH; +pub type PCNZTCH = PCNZCH; +pub type PCUNZTCH = PCNZCH; +pub type PSHORT = *mut SHORT; +pub type PLONG = *mut LONG; +pub type ULONG = ::std::os::raw::c_ulong; +pub type PULONG = *mut ULONG; +pub type USHORT = ::std::os::raw::c_ushort; +pub type PUSHORT = *mut USHORT; +pub type UCHAR = ::std::os::raw::c_uchar; +pub type PUCHAR = *mut UCHAR; +pub type PSZ = *mut ::std::os::raw::c_char; +pub type DWORD = ::std::os::raw::c_ulong; +pub type BOOL = ::std::os::raw::c_int; +pub type BYTE = ::std::os::raw::c_uchar; +pub type WORD = ::std::os::raw::c_ushort; +pub type FLOAT = f32; +pub type PFLOAT = *mut FLOAT; +pub type PBOOL = *mut BOOL; +pub type LPBOOL = *mut BOOL; +pub type PBYTE = *mut BYTE; +pub type LPBYTE = *mut BYTE; +pub type PINT = *mut ::std::os::raw::c_int; +pub type LPINT = *mut ::std::os::raw::c_int; +pub type PWORD = *mut WORD; +pub type LPWORD = *mut WORD; +pub type LPLONG = *mut ::std::os::raw::c_long; +pub type PDWORD = *mut DWORD; +pub type LPDWORD = *mut DWORD; +pub type LPVOID = *mut ::std::os::raw::c_void; +pub type LPCVOID = *const ::std::os::raw::c_void; +pub type INT = ::std::os::raw::c_int; +pub type UINT = ::std::os::raw::c_uint; +pub type PUINT = *mut ::std::os::raw::c_uint; +pub type va_list = *mut ::std::os::raw::c_char; + +pub const TRUE: ::std::os::raw::c_int = 1; +pub const FALSE: ::std::os::raw::c_int = 0; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_HEADER { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, +} +pub type CLIPRDR_HEADER = _CLIPRDR_HEADER; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_CAPABILITY_SET { + pub capabilitySetType: UINT16, + pub capabilitySetLength: UINT16, +} +pub type CLIPRDR_CAPABILITY_SET = _CLIPRDR_CAPABILITY_SET; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_GENERAL_CAPABILITY_SET { + pub capabilitySetType: UINT16, + pub capabilitySetLength: UINT16, + pub version: UINT32, + pub generalFlags: UINT32, +} +pub type CLIPRDR_GENERAL_CAPABILITY_SET = _CLIPRDR_GENERAL_CAPABILITY_SET; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_CAPABILITIES { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub cCapabilitiesSets: UINT32, + pub capabilitySets: *mut CLIPRDR_CAPABILITY_SET, +} +pub type CLIPRDR_CAPABILITIES = _CLIPRDR_CAPABILITIES; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_MONITOR_READY { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, +} +pub type CLIPRDR_MONITOR_READY = _CLIPRDR_MONITOR_READY; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_TEMP_DIRECTORY { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub szTempDir: [::std::os::raw::c_char; 520usize], +} +pub type CLIPRDR_TEMP_DIRECTORY = _CLIPRDR_TEMP_DIRECTORY; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FORMAT { + pub formatId: UINT32, + pub formatName: *mut ::std::os::raw::c_char, +} +pub type CLIPRDR_FORMAT = _CLIPRDR_FORMAT; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FORMAT_LIST { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub numFormats: UINT32, + pub formats: *mut CLIPRDR_FORMAT, +} +pub type CLIPRDR_FORMAT_LIST = _CLIPRDR_FORMAT_LIST; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FORMAT_LIST_RESPONSE { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, +} +pub type CLIPRDR_FORMAT_LIST_RESPONSE = _CLIPRDR_FORMAT_LIST_RESPONSE; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_LOCK_CLIPBOARD_DATA { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub clipDataId: UINT32, +} +pub type CLIPRDR_LOCK_CLIPBOARD_DATA = _CLIPRDR_LOCK_CLIPBOARD_DATA; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_UNLOCK_CLIPBOARD_DATA { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub clipDataId: UINT32, +} +pub type CLIPRDR_UNLOCK_CLIPBOARD_DATA = _CLIPRDR_UNLOCK_CLIPBOARD_DATA; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FORMAT_DATA_REQUEST { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub requestedFormatId: UINT32, +} +pub type CLIPRDR_FORMAT_DATA_REQUEST = _CLIPRDR_FORMAT_DATA_REQUEST; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FORMAT_DATA_RESPONSE { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub requestedFormatData: *const BYTE, +} +pub type CLIPRDR_FORMAT_DATA_RESPONSE = _CLIPRDR_FORMAT_DATA_RESPONSE; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FILE_CONTENTS_REQUEST { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub streamId: UINT32, + pub listIndex: UINT32, + pub dwFlags: UINT32, + pub nPositionLow: UINT32, + pub nPositionHigh: UINT32, + pub cbRequested: UINT32, + pub haveClipDataId: BOOL, + pub clipDataId: UINT32, +} +pub type CLIPRDR_FILE_CONTENTS_REQUEST = _CLIPRDR_FILE_CONTENTS_REQUEST; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _CLIPRDR_FILE_CONTENTS_RESPONSE { + pub serverConnID: UINT32, + pub remoteConnID: UINT32, + pub msgType: UINT16, + pub msgFlags: UINT16, + pub dataLen: UINT32, + pub streamId: UINT32, + pub cbRequested: UINT32, + pub requestedData: *const BYTE, +} +pub type CLIPRDR_FILE_CONTENTS_RESPONSE = _CLIPRDR_FILE_CONTENTS_RESPONSE; +pub type CliprdrClientContext = _cliprdr_client_context; +pub type pcCliprdrServerCapabilities = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + capabilities: *const CLIPRDR_CAPABILITIES, + ) -> UINT, +>; +pub type pcCliprdrClientCapabilities = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + capabilities: *const CLIPRDR_CAPABILITIES, + ) -> UINT, +>; +pub type pcCliprdrMonitorReady = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + monitorReady: *const CLIPRDR_MONITOR_READY, + ) -> UINT, +>; +pub type pcCliprdrTempDirectory = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + tempDirectory: *const CLIPRDR_TEMP_DIRECTORY, + ) -> UINT, +>; +pub type pcCliprdrClientFormatList = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatList: *const CLIPRDR_FORMAT_LIST, + ) -> UINT, +>; +pub type pcCliprdrServerFormatList = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatList: *const CLIPRDR_FORMAT_LIST, + ) -> UINT, +>; +pub type pcCliprdrClientFormatListResponse = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatListResponse: *const CLIPRDR_FORMAT_LIST_RESPONSE, + ) -> UINT, +>; +pub type pcCliprdrServerFormatListResponse = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatListResponse: *const CLIPRDR_FORMAT_LIST_RESPONSE, + ) -> UINT, +>; +pub type pcCliprdrClientLockClipboardData = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + lockClipboardData: *const CLIPRDR_LOCK_CLIPBOARD_DATA, + ) -> UINT, +>; +pub type pcCliprdrServerLockClipboardData = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + lockClipboardData: *const CLIPRDR_LOCK_CLIPBOARD_DATA, + ) -> UINT, +>; +pub type pcCliprdrClientUnlockClipboardData = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + unlockClipboardData: *const CLIPRDR_UNLOCK_CLIPBOARD_DATA, + ) -> UINT, +>; +pub type pcCliprdrServerUnlockClipboardData = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + unlockClipboardData: *const CLIPRDR_UNLOCK_CLIPBOARD_DATA, + ) -> UINT, +>; +pub type pcCliprdrClientFormatDataRequest = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatDataRequest: *const CLIPRDR_FORMAT_DATA_REQUEST, + ) -> UINT, +>; +pub type pcCliprdrServerFormatDataRequest = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatDataRequest: *const CLIPRDR_FORMAT_DATA_REQUEST, + ) -> UINT, +>; +pub type pcCliprdrClientFormatDataResponse = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatDataResponse: *const CLIPRDR_FORMAT_DATA_RESPONSE, + ) -> UINT, +>; +pub type pcCliprdrServerFormatDataResponse = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + formatDataResponse: *const CLIPRDR_FORMAT_DATA_RESPONSE, + ) -> UINT, +>; +pub type pcCliprdrClientFileContentsRequest = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + fileContentsRequest: *const CLIPRDR_FILE_CONTENTS_REQUEST, + ) -> UINT, +>; +pub type pcCliprdrServerFileContentsRequest = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + fileContentsRequest: *const CLIPRDR_FILE_CONTENTS_REQUEST, + ) -> UINT, +>; +pub type pcCliprdrClientFileContentsResponse = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + fileContentsResponse: *const CLIPRDR_FILE_CONTENTS_RESPONSE, + ) -> UINT, +>; +pub type pcCliprdrServerFileContentsResponse = ::std::option::Option< + unsafe extern "C" fn( + context: *mut CliprdrClientContext, + fileContentsResponse: *const CLIPRDR_FILE_CONTENTS_RESPONSE, + ) -> UINT, +>; + +// TODO: hide more members of clipboard context +#[repr(C)] +#[derive(Debug, Clone)] +pub struct _cliprdr_client_context { + pub custom: *mut ::std::os::raw::c_void, + pub enableFiles: BOOL, + pub enableOthers: BOOL, + pub ServerCapabilities: pcCliprdrServerCapabilities, + pub ClientCapabilities: pcCliprdrClientCapabilities, + pub MonitorReady: pcCliprdrMonitorReady, + pub TempDirectory: pcCliprdrTempDirectory, + pub ClientFormatList: pcCliprdrClientFormatList, + pub ServerFormatList: pcCliprdrServerFormatList, + pub ClientFormatListResponse: pcCliprdrClientFormatListResponse, + pub ServerFormatListResponse: pcCliprdrServerFormatListResponse, + pub ClientLockClipboardData: pcCliprdrClientLockClipboardData, + pub ServerLockClipboardData: pcCliprdrServerLockClipboardData, + pub ClientUnlockClipboardData: pcCliprdrClientUnlockClipboardData, + pub ServerUnlockClipboardData: pcCliprdrServerUnlockClipboardData, + pub ClientFormatDataRequest: pcCliprdrClientFormatDataRequest, + pub ServerFormatDataRequest: pcCliprdrServerFormatDataRequest, + pub ClientFormatDataResponse: pcCliprdrClientFormatDataResponse, + pub ServerFormatDataResponse: pcCliprdrServerFormatDataResponse, + pub ClientFileContentsRequest: pcCliprdrClientFileContentsRequest, + pub ServerFileContentsRequest: pcCliprdrServerFileContentsRequest, + pub ClientFileContentsResponse: pcCliprdrClientFileContentsResponse, + pub ServerFileContentsResponse: pcCliprdrServerFileContentsResponse, + pub lastRequestedFormatId: UINT32, +} + +// #[link(name = "user32")] +// #[link(name = "ole32")] +extern "C" { + pub(crate) fn init_cliprdr(context: *mut CliprdrClientContext) -> BOOL; + pub(crate) fn uninit_cliprdr(context: *mut CliprdrClientContext) -> BOOL; +} + +#[derive(Error, Debug)] +pub enum CliprdrError { + #[error("invalid cliprdr name")] + CliprdrName, + #[error("failed to init cliprdr")] + CliprdrInit, + #[error("unknown cliprdr error")] + Unknown, +} + +impl CliprdrClientContext { + pub fn create( + enable_files: bool, + enable_others: bool, + client_format_list: pcCliprdrClientFormatList, + client_format_list_response: pcCliprdrClientFormatListResponse, + client_format_data_request: pcCliprdrClientFormatDataRequest, + client_format_data_response: pcCliprdrClientFormatDataResponse, + client_file_contents_request: pcCliprdrClientFileContentsRequest, + client_file_contents_response: pcCliprdrClientFileContentsResponse, + ) -> Result, CliprdrError> { + let context = CliprdrClientContext { + custom: 0 as *mut _, + enableFiles: if enable_files { TRUE } else { FALSE }, + enableOthers: if enable_others { TRUE } else { FALSE }, + ServerCapabilities: None, + ClientCapabilities: None, + MonitorReady: None, + TempDirectory: None, + ClientFormatList: client_format_list, + ServerFormatList: None, + ClientFormatListResponse: client_format_list_response, + ServerFormatListResponse: None, + ClientLockClipboardData: None, + ServerLockClipboardData: None, + ClientUnlockClipboardData: None, + ServerUnlockClipboardData: None, + ClientFormatDataRequest: client_format_data_request, + ServerFormatDataRequest: None, + ClientFormatDataResponse: client_format_data_response, + ServerFormatDataResponse: None, + ClientFileContentsRequest: client_file_contents_request, + ServerFileContentsRequest: None, + ClientFileContentsResponse: client_file_contents_response, + ServerFileContentsResponse: None, + lastRequestedFormatId: 0, + }; + let mut context = Box::new(context); + unsafe { + if FALSE == init_cliprdr(&mut (*context)) { + println!("Failed to init cliprdr"); + Err(CliprdrError::CliprdrInit) + } else { + Ok(context) + } + } + } +} + +impl Drop for CliprdrClientContext { + fn drop(&mut self) { + unsafe { + if FALSE == uninit_cliprdr(&mut *self) { + println!("Failed to uninit cliprdr"); + } else { + println!("Succeeded to uninit cliprdr"); + } + } + } +} diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs new file mode 100644 index 000000000..21ca6bfb8 --- /dev/null +++ b/libs/clipboard/src/lib.rs @@ -0,0 +1,548 @@ +use cliprdr::*; +use hbb_common::{ + log, + message_proto::cliprdr as msg_cliprdr, + message_proto::*, + tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex as TokioMutex, + }, + ResultType, +}; +use std::{ + boxed::Box, + ffi::{CStr, CString}, +}; + +pub mod cliprdr; + +#[derive(Debug)] +pub struct ConnID { + pub server_conn_id: u32, + pub remote_conn_id: u32, +} + +lazy_static::lazy_static! { + static ref MSG_CHANNEL_CLIENT: (UnboundedSender<(ConnID, Message)>, TokioMutex>) = { + let (tx, rx) = unbounded_channel(); + (tx, TokioMutex::new(rx)) + }; +} + +#[inline(always)] +pub fn get_rx_client_msg<'a>() -> &'a TokioMutex> { + &MSG_CHANNEL_CLIENT.1 +} + +pub fn server_msg(context: &mut Box, conn_id: ConnID, msg: Cliprdr) -> u32 { + match msg.union { + Some(msg_cliprdr::Union::ready(_)) => { + // proc ready + 0 + } + Some(msg_cliprdr::Union::format_list(req)) => { + log::debug!("server_format_list called"); + let ret = server_format_list(context, conn_id, req); + log::debug!("server_format_list called, return {}", ret); + ret + } + Some(msg_cliprdr::Union::format_list_response(req)) => { + log::debug!("format_list_response called"); + let ret = server_format_list_response(context, conn_id, req); + log::debug!("server_format_list_response called, return {}", ret); + ret + } + Some(msg_cliprdr::Union::format_data_request(req)) => { + log::debug!("format_data_request called"); + let ret = server_format_data_request(context, conn_id, req); + log::debug!("server_format_data_request called, return {}", ret); + ret + } + Some(msg_cliprdr::Union::format_data_response(req)) => { + log::debug!("format_data_response called"); + let ret = server_format_data_response(context, conn_id, req); + log::debug!("server_format_data_response called, return {}", ret); + ret + } + Some(msg_cliprdr::Union::file_contents_request(req)) => { + log::debug!("file_contents_request called"); + let ret = server_file_contents_request(context, conn_id, req); + log::debug!("server_file_contents_request called, return {}", ret); + ret + } + Some(msg_cliprdr::Union::file_contents_response(req)) => { + log::debug!("file_contents_response called"); + let ret = server_file_contents_response(context, conn_id, req); + log::debug!("server_file_contents_response called, return {}", ret); + ret + } + None => { + unreachable!() + } + } +} + +fn server_format_list( + context: &mut Box, + conn_id: ConnID, + data: CliprdrServerFormatList, +) -> u32 { + // do not check msgFlags for now + unsafe { + let num_formats = data.formats.len() as UINT32; + let mut formats = data + .formats + .into_iter() + .map(|format| { + if format.format.is_empty() { + CLIPRDR_FORMAT { + formatId: format.id as UINT32, + formatName: 0 as *mut _, + } + } else { + let n = match CString::new(format.format) { + Ok(n) => n, + Err(_) => CString::new("").unwrap(), + }; + CLIPRDR_FORMAT { + formatId: format.id as UINT32, + formatName: n.into_raw(), + } + } + }) + .collect::>(); + + let server_conn_id = if conn_id.server_conn_id != 0 { + conn_id.server_conn_id as UINT32 + } else { + data.server_conn_id as UINT32 + }; + let remote_conn_id = if conn_id.remote_conn_id != 0 { + conn_id.remote_conn_id as UINT32 + } else { + data.remote_conn_id as UINT32 + }; + + let format_list = CLIPRDR_FORMAT_LIST { + serverConnID: server_conn_id, + remoteConnID: remote_conn_id, + msgType: 0 as UINT16, + msgFlags: 0 as UINT16, + dataLen: 0 as UINT32, + numFormats: num_formats, + formats: formats.as_mut_ptr(), + }; + + let ret = ((**context).ServerFormatList.unwrap())(&mut (**context), &format_list); + + for f in formats { + if !f.formatName.is_null() { + // retake pointer to free memory + let _ = CString::from_raw(f.formatName); + } + } + + ret as u32 + } +} +fn server_format_list_response( + context: &mut Box, + conn_id: ConnID, + data: CliprdrServerFormatListResponse, +) -> u32 { + unsafe { + let server_conn_id = if conn_id.server_conn_id != 0 { + conn_id.server_conn_id as UINT32 + } else { + data.server_conn_id as UINT32 + }; + let remote_conn_id = if conn_id.remote_conn_id != 0 { + conn_id.remote_conn_id as UINT32 + } else { + data.remote_conn_id as UINT32 + }; + + let format_list_response = CLIPRDR_FORMAT_LIST_RESPONSE { + serverConnID: server_conn_id, + remoteConnID: remote_conn_id, + msgType: 0 as UINT16, + msgFlags: data.msg_flags as UINT16, + dataLen: 0 as UINT32, + }; + + let ret = + (**context).ServerFormatListResponse.unwrap()(&mut (**context), &format_list_response); + + ret as u32 + } +} +fn server_format_data_request( + context: &mut Box, + conn_id: ConnID, + data: CliprdrServerFormatDataRequest, +) -> u32 { + unsafe { + let server_conn_id = if conn_id.server_conn_id != 0 { + conn_id.server_conn_id as UINT32 + } else { + data.server_conn_id as UINT32 + }; + let remote_conn_id = if conn_id.remote_conn_id != 0 { + conn_id.remote_conn_id as UINT32 + } else { + data.remote_conn_id as UINT32 + }; + + let format_data_request = CLIPRDR_FORMAT_DATA_REQUEST { + serverConnID: server_conn_id, + remoteConnID: remote_conn_id, + msgType: 0 as UINT16, + msgFlags: 0 as UINT16, + dataLen: 0 as UINT32, + requestedFormatId: data.requested_format_id as UINT32, + }; + let ret = + ((**context).ServerFormatDataRequest.unwrap())(&mut (**context), &format_data_request); + ret as u32 + } +} +fn server_format_data_response( + context: &mut Box, + conn_id: ConnID, + mut data: CliprdrServerFormatDataResponse, +) -> u32 { + unsafe { + let server_conn_id = if conn_id.server_conn_id != 0 { + conn_id.server_conn_id as UINT32 + } else { + data.server_conn_id as UINT32 + }; + let remote_conn_id = if conn_id.remote_conn_id != 0 { + conn_id.remote_conn_id as UINT32 + } else { + data.remote_conn_id as UINT32 + }; + let format_data_response = CLIPRDR_FORMAT_DATA_RESPONSE { + serverConnID: server_conn_id, + remoteConnID: remote_conn_id, + msgType: 0 as UINT16, + msgFlags: data.msg_flags as UINT16, + dataLen: data.format_data.len() as UINT32, + requestedFormatData: data.format_data.as_mut_ptr(), + }; + let ret = ((**context).ServerFormatDataResponse.unwrap())( + &mut (**context), + &format_data_response, + ); + ret as u32 + } +} +fn server_file_contents_request( + context: &mut Box, + conn_id: ConnID, + data: CliprdrFileContentsRequest, +) -> u32 { + unsafe { + let server_conn_id = if conn_id.server_conn_id != 0 { + conn_id.server_conn_id as UINT32 + } else { + data.server_conn_id as UINT32 + }; + let remote_conn_id = if conn_id.remote_conn_id != 0 { + conn_id.remote_conn_id as UINT32 + } else { + data.remote_conn_id as UINT32 + }; + let file_contents_request = CLIPRDR_FILE_CONTENTS_REQUEST { + serverConnID: server_conn_id, + remoteConnID: remote_conn_id, + msgType: 0 as UINT16, + msgFlags: 0 as UINT16, + dataLen: 0 as UINT32, + streamId: data.stream_id as UINT32, + listIndex: data.list_index as UINT32, + dwFlags: data.dw_flags as UINT32, + nPositionLow: data.n_position_low as UINT32, + nPositionHigh: data.n_position_high as UINT32, + cbRequested: data.cb_requested as UINT32, + haveClipDataId: if data.have_clip_data_id { TRUE } else { FALSE }, + clipDataId: data.clip_data_id as UINT32, + }; + let ret = ((**context).ServerFileContentsRequest.unwrap())( + &mut (**context), + &file_contents_request, + ); + ret as u32 + } +} +fn server_file_contents_response( + context: &mut Box, + conn_id: ConnID, + mut data: CliprdrFileContentsResponse, +) -> u32 { + unsafe { + let server_conn_id = if conn_id.server_conn_id != 0 { + conn_id.server_conn_id as UINT32 + } else { + data.server_conn_id as UINT32 + }; + let remote_conn_id = if conn_id.remote_conn_id != 0 { + conn_id.remote_conn_id as UINT32 + } else { + data.remote_conn_id as UINT32 + }; + let file_contents_response = CLIPRDR_FILE_CONTENTS_RESPONSE { + serverConnID: server_conn_id, + remoteConnID: remote_conn_id, + msgType: 0 as UINT16, + msgFlags: data.msg_flags as UINT16, + dataLen: 4 + data.requested_data.len() as UINT32, + streamId: data.stream_id as UINT32, + cbRequested: data.requested_data.len() as UINT32, + requestedData: data.requested_data.as_mut_ptr(), + }; + let ret = ((**context).ServerFileContentsResponse.unwrap())( + &mut (**context), + &file_contents_response, + ); + ret as u32 + } +} + +pub fn create_cliprdr_context(enable_files: bool, enable_others: bool) -> ResultType> { + Ok(CliprdrClientContext::create( + enable_files, + enable_others, + Some(client_format_list), + Some(client_format_list_response), + Some(client_format_data_request), + Some(client_format_data_response), + Some(client_file_contents_request), + Some(client_file_contents_response), + )?) +} + +extern "C" fn client_format_list( + _context: *mut CliprdrClientContext, + format_list: *const CLIPRDR_FORMAT_LIST, +) -> UINT { + log::debug!("client_format_list called"); + + let mut data = CliprdrServerFormatList::default(); + unsafe { + let mut i = 0u32; + while i < (*format_list).numFormats { + let format_data = &(*(*format_list).formats.offset(i as isize)); + if format_data.formatName.is_null() { + data.formats.push(CliprdrFormat { + id: format_data.formatId as i32, + format: "".to_owned(), + ..Default::default() + }); + } else { + let format_name = CStr::from_ptr(format_data.formatName).to_str(); + let format_name = match format_name { + Ok(n) => n.to_owned(), + Err(_) => { + log::warn!("failed to get format name"); + "".to_owned() + } + }; + data.formats.push(CliprdrFormat { + id: format_data.formatId as i32, + format: format_name, + ..Default::default() + }); + } + i += 1; + } + + data.server_conn_id = (*format_list).serverConnID as i32; + data.remote_conn_id = (*format_list).remoteConnID as i32; + } + let conn_id = ConnID { + server_conn_id: data.server_conn_id as u32, + remote_conn_id: data.remote_conn_id as u32, + }; + + let mut msg = Message::new(); + let mut cliprdr = Cliprdr::new(); + cliprdr.set_format_list(data); + msg.set_cliprdr(cliprdr); + + // no need to handle result here + MSG_CHANNEL_CLIENT.0.send((conn_id, msg)).unwrap(); + + 0 +} + +extern "C" fn client_format_list_response( + _context: *mut CliprdrClientContext, + format_list_response: *const CLIPRDR_FORMAT_LIST_RESPONSE, +) -> UINT { + log::debug!("client_format_list_response called"); + + let mut data = CliprdrServerFormatListResponse::default(); + unsafe { + data.server_conn_id = (*format_list_response).serverConnID as i32; + data.remote_conn_id = (*format_list_response).remoteConnID as i32; + data.msg_flags = (*format_list_response).msgFlags as i32; + } + let conn_id = ConnID { + server_conn_id: data.server_conn_id as u32, + remote_conn_id: data.remote_conn_id as u32, + }; + + let mut msg = Message::new(); + let mut cliprdr = Cliprdr::new(); + cliprdr.set_format_list_response(data); + msg.set_cliprdr(cliprdr); + // no need to handle result here + MSG_CHANNEL_CLIENT.0.send((conn_id, msg)).unwrap(); + + 0 +} + +extern "C" fn client_format_data_request( + _context: *mut CliprdrClientContext, + format_data_request: *const CLIPRDR_FORMAT_DATA_REQUEST, +) -> UINT { + log::debug!("client_format_data_request called"); + + let mut data = CliprdrServerFormatDataRequest::default(); + unsafe { + data.server_conn_id = (*format_data_request).serverConnID as i32; + data.remote_conn_id = (*format_data_request).remoteConnID as i32; + data.requested_format_id = (*format_data_request).requestedFormatId as i32; + } + let conn_id = ConnID { + server_conn_id: data.server_conn_id as u32, + remote_conn_id: data.remote_conn_id as u32, + }; + + let mut msg = Message::new(); + let mut cliprdr = Cliprdr::new(); + cliprdr.set_format_data_request(data); + msg.set_cliprdr(cliprdr); + // no need to handle result here + MSG_CHANNEL_CLIENT.0.send((conn_id, msg)).unwrap(); + + 0 +} + +extern "C" fn client_format_data_response( + _context: *mut CliprdrClientContext, + format_data_response: *const CLIPRDR_FORMAT_DATA_RESPONSE, +) -> UINT { + log::debug!("client_format_data_response called"); + + let mut data = CliprdrServerFormatDataResponse::default(); + unsafe { + data.server_conn_id = (*format_data_response).serverConnID as i32; + data.remote_conn_id = (*format_data_response).remoteConnID as i32; + data.msg_flags = (*format_data_response).msgFlags as i32; + data.format_data = std::slice::from_raw_parts( + (*format_data_response).requestedFormatData, + (*format_data_response).dataLen as usize, + ) + .to_vec(); + } + let conn_id = ConnID { + server_conn_id: data.server_conn_id as u32, + remote_conn_id: data.remote_conn_id as u32, + }; + + let mut msg = Message::new(); + let mut cliprdr = Cliprdr::new(); + cliprdr.set_format_data_response(data); + msg.set_cliprdr(cliprdr); + // no need to handle result here + MSG_CHANNEL_CLIENT.0.send((conn_id, msg)).unwrap(); + + 0 +} + +extern "C" fn client_file_contents_request( + _context: *mut CliprdrClientContext, + file_contents_request: *const CLIPRDR_FILE_CONTENTS_REQUEST, +) -> UINT { + log::debug!("client_file_contents_request called"); + + // TODO: support huge file? + // if (!cliprdr->hasHugeFileSupport) + // { + // if (((UINT64)fileContentsRequest->cbRequested + fileContentsRequest->nPositionLow) > + // UINT32_MAX) + // return ERROR_INVALID_PARAMETER; + // if (fileContentsRequest->nPositionHigh != 0) + // return ERROR_INVALID_PARAMETER; + // } + + let mut data = CliprdrFileContentsRequest::default(); + unsafe { + data.server_conn_id = (*file_contents_request).serverConnID as i32; + data.remote_conn_id = (*file_contents_request).remoteConnID as i32; + data.stream_id = (*file_contents_request).streamId as i32; + data.list_index = (*file_contents_request).listIndex as i32; + data.dw_flags = (*file_contents_request).dwFlags as i32; + data.n_position_low = (*file_contents_request).nPositionLow as i32; + data.n_position_high = (*file_contents_request).nPositionHigh as i32; + data.cb_requested = (*file_contents_request).cbRequested as i32; + data.have_clip_data_id = (*file_contents_request).haveClipDataId == TRUE; + data.clip_data_id = (*file_contents_request).clipDataId as i32; + } + let conn_id = ConnID { + server_conn_id: data.server_conn_id as u32, + remote_conn_id: data.remote_conn_id as u32, + }; + + let mut msg = Message::new(); + let mut cliprdr = Cliprdr::new(); + cliprdr.set_file_contents_request(data); + msg.set_cliprdr(cliprdr); + // no need to handle result here + MSG_CHANNEL_CLIENT.0.send((conn_id, msg)).unwrap(); + + 0 +} + +extern "C" fn client_file_contents_response( + _context: *mut CliprdrClientContext, + file_contents_response: *const CLIPRDR_FILE_CONTENTS_RESPONSE, +) -> UINT { + log::debug!("client_file_contents_response called"); + + let mut data = CliprdrFileContentsResponse::default(); + unsafe { + data.server_conn_id = (*file_contents_response).serverConnID as i32; + data.remote_conn_id = (*file_contents_response).remoteConnID as i32; + data.msg_flags = (*file_contents_response).msgFlags as i32; + data.stream_id = (*file_contents_response).streamId as i32; + data.requested_data = std::slice::from_raw_parts( + (*file_contents_response).requestedData, + (*file_contents_response).cbRequested as usize, + ) + .to_vec(); + } + let conn_id = ConnID { + server_conn_id: data.server_conn_id as u32, + remote_conn_id: data.remote_conn_id as u32, + }; + + let mut msg = Message::new(); + let mut cliprdr = Cliprdr::new(); + cliprdr.set_file_contents_response(data); + msg.set_cliprdr(cliprdr); + // no need to handle result here + MSG_CHANNEL_CLIENT.0.send((conn_id, msg)).unwrap(); + + 0 +} + +#[cfg(test)] +mod tests { + // #[test] + // fn test_cliprdr_run() { + // super::cliprdr_run(); + // } +} diff --git a/libs/clipboard/src/windows/wf_cliprdr.c b/libs/clipboard/src/windows/wf_cliprdr.c new file mode 100644 index 000000000..3c51724c0 --- /dev/null +++ b/libs/clipboard/src/windows/wf_cliprdr.c @@ -0,0 +1,2789 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Clipboard Redirection + * + * Copyright 2012 Jason Champion + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CINTERFACE +#define COBJMACROS + +#include +#include +#include +#include +#include + +#include + +#include + +#include "../cliprdr.h" + +#define CLIPRDR_SVC_CHANNEL_NAME "cliprdr" + +/** + * Clipboard Formats + */ +#define CB_FORMAT_HTML 0xD010 +#define CB_FORMAT_PNG 0xD011 +#define CB_FORMAT_JPEG 0xD012 +#define CB_FORMAT_GIF 0xD013 +#define CB_FORMAT_TEXTURILIST 0xD014 +#define CB_FORMAT_GNOMECOPIEDFILES 0xD015 +#define CB_FORMAT_MATECOPIEDFILES 0xD016 + +/* CLIPRDR_HEADER.msgType */ +#define CB_MONITOR_READY 0x0001 +#define CB_FORMAT_LIST 0x0002 +#define CB_FORMAT_LIST_RESPONSE 0x0003 +#define CB_FORMAT_DATA_REQUEST 0x0004 +#define CB_FORMAT_DATA_RESPONSE 0x0005 +#define CB_TEMP_DIRECTORY 0x0006 +#define CB_CLIP_CAPS 0x0007 +#define CB_FILECONTENTS_REQUEST 0x0008 +#define CB_FILECONTENTS_RESPONSE 0x0009 +#define CB_LOCK_CLIPDATA 0x000A +#define CB_UNLOCK_CLIPDATA 0x000B + +/* CLIPRDR_HEADER.msgFlags */ +#define CB_RESPONSE_OK 0x0001 +#define CB_RESPONSE_FAIL 0x0002 +#define CB_ASCII_NAMES 0x0004 + +/* CLIPRDR_CAPS_SET.capabilitySetType */ +#define CB_CAPSTYPE_GENERAL 0x0001 + +/* CLIPRDR_GENERAL_CAPABILITY.lengthCapability */ +#define CB_CAPSTYPE_GENERAL_LEN 12 + +/* CLIPRDR_GENERAL_CAPABILITY.version */ +#define CB_CAPS_VERSION_1 0x00000001 +#define CB_CAPS_VERSION_2 0x00000002 + +/* CLIPRDR_GENERAL_CAPABILITY.generalFlags */ +#define CB_USE_LONG_FORMAT_NAMES 0x00000002 +#define CB_STREAM_FILECLIP_ENABLED 0x00000004 +#define CB_FILECLIP_NO_FILE_PATHS 0x00000008 +#define CB_CAN_LOCK_CLIPDATA 0x00000010 +#define CB_HUGE_FILE_SUPPORT_ENABLED 0x00000020 + +/* File Contents Request Flags */ +#define FILECONTENTS_SIZE 0x00000001 +#define FILECONTENTS_RANGE 0x00000002 + +/* Special Clipboard Response Formats */ + +struct _CLIPRDR_MFPICT +{ + UINT32 mappingMode; + UINT32 xExt; + UINT32 yExt; + UINT32 metaFileSize; + BYTE *metaFileData; +}; +typedef struct _CLIPRDR_MFPICT CLIPRDR_MFPICT; + +struct _CONN_ID +{ + UINT32 serverConnID; + UINT32 remoteConnID; +}; +typedef struct _CONN_ID CONN_ID; + +struct _FORMAT_IDS +{ + UINT32 serverConnID; + UINT32 remoteConnID; + UINT32 size; + UINT32 *formats; +}; +typedef struct _FORMAT_IDS FORMAT_IDS; + +/* File Contents Request Flags */ +#define FILECONTENTS_SIZE 0x00000001 +#define FILECONTENTS_RANGE 0x00000002 + +#define CHANNEL_RC_OK 0 +#define CHANNEL_RC_ALREADY_INITIALIZED 1 +#define CHANNEL_RC_NOT_INITIALIZED 2 +#define CHANNEL_RC_ALREADY_CONNECTED 3 +#define CHANNEL_RC_NOT_CONNECTED 4 +#define CHANNEL_RC_TOO_MANY_CHANNELS 5 +#define CHANNEL_RC_BAD_CHANNEL 6 +#define CHANNEL_RC_BAD_CHANNEL_HANDLE 7 +#define CHANNEL_RC_NO_BUFFER 8 +#define CHANNEL_RC_BAD_INIT_HANDLE 9 +#define CHANNEL_RC_NOT_OPEN 10 +#define CHANNEL_RC_BAD_PROC 11 +#define CHANNEL_RC_NO_MEMORY 12 +#define CHANNEL_RC_UNKNOWN_CHANNEL_NAME 13 +#define CHANNEL_RC_ALREADY_OPEN 14 +#define CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY 15 +#define CHANNEL_RC_NULL_DATA 16 +#define CHANNEL_RC_ZERO_LENGTH 17 +#define CHANNEL_RC_INVALID_INSTANCE 18 +#define CHANNEL_RC_UNSUPPORTED_VERSION 19 +#define CHANNEL_RC_INITIALIZATION_ERROR 20 + +#define TAG "windows" + +#ifdef WITH_DEBUG_CLIPRDR +#define DEBUG_CLIPRDR(...) printf(TAG, __VA_ARGS__) +#else +#define DEBUG_CLIPRDR(...) \ + do \ + { \ + } while (0) +#endif + +typedef BOOL(WINAPI *fnAddClipboardFormatListener)(HWND hwnd); +typedef BOOL(WINAPI *fnRemoveClipboardFormatListener)(HWND hwnd); +typedef BOOL(WINAPI *fnGetUpdatedClipboardFormats)(PUINT lpuiFormats, UINT cFormats, + PUINT pcFormatsOut); + +struct format_mapping +{ + UINT32 remote_format_id; + UINT32 local_format_id; + WCHAR *name; +}; +typedef struct format_mapping formatMapping; + +struct _CliprdrEnumFORMATETC +{ + IEnumFORMATETC iEnumFORMATETC; + + LONG m_lRefCount; + LONG m_nIndex; + LONG m_nNumFormats; + FORMATETC *m_pFormatEtc; +}; +typedef struct _CliprdrEnumFORMATETC CliprdrEnumFORMATETC; + +struct _CliprdrStream +{ + IStream iStream; + + LONG m_lRefCount; + ULONG m_lIndex; + ULARGE_INTEGER m_lSize; + ULARGE_INTEGER m_lOffset; + FILEDESCRIPTORW m_Dsc; + void *m_pData; + UINT32 m_serverConnID; + UINT32 m_remoteConnID; +}; +typedef struct _CliprdrStream CliprdrStream; + +struct _CliprdrDataObject +{ + IDataObject iDataObject; + + LONG m_lRefCount; + FORMATETC *m_pFormatEtc; + STGMEDIUM *m_pStgMedium; + ULONG m_nNumFormats; + ULONG m_nStreams; + IStream **m_pStream; + void *m_pData; + UINT32 m_serverConnID; + UINT32 m_remoteConnID; +}; +typedef struct _CliprdrDataObject CliprdrDataObject; + +struct wf_clipboard +{ + // wfContext* wfc; + // rdpChannels* channels; + CliprdrClientContext *context; + + BOOL sync; + UINT32 capabilities; + + size_t map_size; + size_t map_capacity; + formatMapping *format_mappings; + + UINT32 requestedFormatId; + + HWND hwnd; + HANDLE hmem; + HANDLE thread; + HANDLE response_data_event; + + LPDATAOBJECT data_obj; + ULONG req_fsize; + char *req_fdata; + HANDLE req_fevent; + + size_t nFiles; + size_t file_array_size; + WCHAR **file_names; + FILEDESCRIPTORW **fileDescriptor; + + BOOL legacyApi; + HMODULE hUser32; + HWND hWndNextViewer; + fnAddClipboardFormatListener AddClipboardFormatListener; + fnRemoveClipboardFormatListener RemoveClipboardFormatListener; + fnGetUpdatedClipboardFormats GetUpdatedClipboardFormats; +}; +typedef struct wf_clipboard wfClipboard; + +#define WM_CLIPRDR_MESSAGE (WM_USER + 156) +#define OLE_SETCLIPBOARD 1 +#define DELAY_RENDERING 2 + +BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr); +BOOL wf_cliprdr_uninit(wfClipboard *clipboard, CliprdrClientContext *cliprdr); + +static BOOL wf_create_file_obj(UINT32 serverConnID, UINT32 remoteConnID, wfClipboard *cliprdrrdr, IDataObject **ppDataObject); +static void wf_destroy_file_obj(IDataObject *instance); +static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format); +static UINT cliprdr_send_data_request(UINT32 serverConnID, UINT32 remoteConnID, wfClipboard *clipboard, UINT32 format); +static UINT cliprdr_send_lock(wfClipboard *clipboard); +static UINT cliprdr_send_unlock(wfClipboard *clipboard); +static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 serverConnID, UINT32 remoteConnID, const void *streamid, + ULONG index, UINT32 flag, DWORD positionhigh, + DWORD positionlow, ULONG request); + +static void CliprdrDataObject_Delete(CliprdrDataObject *instance); + +static CliprdrEnumFORMATETC *CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC *pFormatEtc); +static void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC *instance); + +static void CliprdrStream_Delete(CliprdrStream *instance); + +static BOOL try_open_clipboard(HWND hwnd) +{ + size_t x; + for (x = 0; x < 10; x++) + { + if (OpenClipboard(hwnd)) + return TRUE; + Sleep(10); + } + return FALSE; +} + +/** + * IStream + */ + +static HRESULT STDMETHODCALLTYPE CliprdrStream_QueryInterface(IStream *This, REFIID riid, + void **ppvObject) +{ + if (IsEqualIID(riid, &IID_IStream) || IsEqualIID(riid, &IID_IUnknown)) + { + IStream_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrStream_AddRef(IStream *This) +{ + CliprdrStream *instance = (CliprdrStream *)This; + + if (!instance) + return 0; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrStream_Release(IStream *This) +{ + LONG count; + CliprdrStream *instance = (CliprdrStream *)This; + + if (!instance) + return 0; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrStream_Delete(instance); + return 0; + } + else + { + return count; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream *This, void *pv, ULONG cb, + ULONG *pcbRead) +{ + int ret; + CliprdrStream *instance = (CliprdrStream *)This; + wfClipboard *clipboard; + + if (!pv || !pcbRead || !instance) + return E_INVALIDARG; + + clipboard = (wfClipboard *)instance->m_pData; + *pcbRead = 0; + + if (instance->m_lOffset.QuadPart >= instance->m_lSize.QuadPart) + return S_FALSE; + + ret = cliprdr_send_request_filecontents(clipboard, instance->m_serverConnID, instance->m_remoteConnID, (void *)This, instance->m_lIndex, + FILECONTENTS_RANGE, instance->m_lOffset.HighPart, + instance->m_lOffset.LowPart, cb); + + if (ret < 0) + return E_FAIL; + + if (clipboard->req_fdata) + { + CopyMemory(pv, clipboard->req_fdata, clipboard->req_fsize); + free(clipboard->req_fdata); + } + + *pcbRead = clipboard->req_fsize; + instance->m_lOffset.QuadPart += clipboard->req_fsize; + + if (clipboard->req_fsize < cb) + return S_FALSE; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Write(IStream *This, const void *pv, ULONG cb, + ULONG *pcbWritten) +{ + (void)This; + (void)pv; + (void)cb; + (void)pcbWritten; + return STG_E_ACCESSDENIED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Seek(IStream *This, LARGE_INTEGER dlibMove, + DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) +{ + ULONGLONG newoffset; + CliprdrStream *instance = (CliprdrStream *)This; + + if (!instance) + return E_INVALIDARG; + + newoffset = instance->m_lOffset.QuadPart; + + switch (dwOrigin) + { + case STREAM_SEEK_SET: + newoffset = dlibMove.QuadPart; + break; + + case STREAM_SEEK_CUR: + newoffset += dlibMove.QuadPart; + break; + + case STREAM_SEEK_END: + newoffset = instance->m_lSize.QuadPart + dlibMove.QuadPart; + break; + + default: + return E_INVALIDARG; + } + + if (newoffset < 0 || newoffset >= instance->m_lSize.QuadPart) + return E_FAIL; + + instance->m_lOffset.QuadPart = newoffset; + + if (plibNewPosition) + plibNewPosition->QuadPart = instance->m_lOffset.QuadPart; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_SetSize(IStream *This, ULARGE_INTEGER libNewSize) +{ + (void)This; + (void)libNewSize; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_CopyTo(IStream *This, IStream *pstm, + ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, + ULARGE_INTEGER *pcbWritten) +{ + (void)This; + (void)pstm; + (void)cb; + (void)pcbRead; + (void)pcbWritten; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Commit(IStream *This, DWORD grfCommitFlags) +{ + (void)This; + (void)grfCommitFlags; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Revert(IStream *This) +{ + (void)This; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_LockRegion(IStream *This, ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + (void)This; + (void)libOffset; + (void)cb; + (void)dwLockType; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_UnlockRegion(IStream *This, ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + (void)This; + (void)libOffset; + (void)cb; + (void)dwLockType; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Stat(IStream *This, STATSTG *pstatstg, + DWORD grfStatFlag) +{ + CliprdrStream *instance = (CliprdrStream *)This; + + if (!instance) + return E_INVALIDARG; + + if (pstatstg == NULL) + return STG_E_INVALIDPOINTER; + + ZeroMemory(pstatstg, sizeof(STATSTG)); + + switch (grfStatFlag) + { + case STATFLAG_DEFAULT: + return STG_E_INSUFFICIENTMEMORY; + + case STATFLAG_NONAME: + pstatstg->cbSize.QuadPart = instance->m_lSize.QuadPart; + pstatstg->grfLocksSupported = LOCK_EXCLUSIVE; + pstatstg->grfMode = GENERIC_READ; + pstatstg->grfStateBits = 0; + pstatstg->type = STGTY_STREAM; + break; + + case STATFLAG_NOOPEN: + return STG_E_INVALIDFLAG; + + default: + return STG_E_INVALIDFLAG; + } + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Clone(IStream *This, IStream **ppstm) +{ + (void)This; + (void)ppstm; + return E_NOTIMPL; +} + +static CliprdrStream *CliprdrStream_New(UINT32 serverConnID, UINT32 remoteConnID, ULONG index, void *pData, const FILEDESCRIPTORW *dsc) +{ + IStream *iStream; + BOOL success = FALSE; + BOOL isDir = FALSE; + CliprdrStream *instance; + wfClipboard *clipboard = (wfClipboard *)pData; + instance = (CliprdrStream *)calloc(1, sizeof(CliprdrStream)); + + if (instance) + { + instance->m_Dsc = *dsc; + iStream = &instance->iStream; + iStream->lpVtbl = (IStreamVtbl *)calloc(1, sizeof(IStreamVtbl)); + + if (iStream->lpVtbl) + { + iStream->lpVtbl->QueryInterface = CliprdrStream_QueryInterface; + iStream->lpVtbl->AddRef = CliprdrStream_AddRef; + iStream->lpVtbl->Release = CliprdrStream_Release; + iStream->lpVtbl->Read = CliprdrStream_Read; + iStream->lpVtbl->Write = CliprdrStream_Write; + iStream->lpVtbl->Seek = CliprdrStream_Seek; + iStream->lpVtbl->SetSize = CliprdrStream_SetSize; + iStream->lpVtbl->CopyTo = CliprdrStream_CopyTo; + iStream->lpVtbl->Commit = CliprdrStream_Commit; + iStream->lpVtbl->Revert = CliprdrStream_Revert; + iStream->lpVtbl->LockRegion = CliprdrStream_LockRegion; + iStream->lpVtbl->UnlockRegion = CliprdrStream_UnlockRegion; + iStream->lpVtbl->Stat = CliprdrStream_Stat; + iStream->lpVtbl->Clone = CliprdrStream_Clone; + instance->m_lRefCount = 1; + instance->m_lIndex = index; + instance->m_pData = pData; + instance->m_lOffset.QuadPart = 0; + instance->m_serverConnID = serverConnID; + instance->m_remoteConnID = remoteConnID; + + if (instance->m_Dsc.dwFlags & FD_ATTRIBUTES) + { + if (instance->m_Dsc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + isDir = TRUE; + } + + if (((instance->m_Dsc.dwFlags & FD_FILESIZE) == 0) && !isDir) + { + /* get content size of this stream */ + if (cliprdr_send_request_filecontents(clipboard, instance->m_serverConnID, instance->m_remoteConnID, (void *)instance, + instance->m_lIndex, FILECONTENTS_SIZE, 0, 0, + 8) == CHANNEL_RC_OK) + { + success = TRUE; + } + + instance->m_lSize.QuadPart = *((LONGLONG *)clipboard->req_fdata); + free(clipboard->req_fdata); + } + else + success = TRUE; + } + } + + if (!success) + { + CliprdrStream_Delete(instance); + instance = NULL; + } + + return instance; +} + +void CliprdrStream_Delete(CliprdrStream *instance) +{ + if (instance) + { + free(instance->iStream.lpVtbl); + free(instance); + } +} + +/** + * IDataObject + */ + +static LONG cliprdr_lookup_format(CliprdrDataObject *instance, FORMATETC *pFormatEtc) +{ + ULONG i; + + if (!instance || !pFormatEtc) + return -1; + + for (i = 0; i < instance->m_nNumFormats; i++) + { + if ((pFormatEtc->tymed & instance->m_pFormatEtc[i].tymed) && + pFormatEtc->cfFormat == instance->m_pFormatEtc[i].cfFormat && + pFormatEtc->dwAspect & instance->m_pFormatEtc[i].dwAspect) + { + return (LONG)i; + } + } + + return -1; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_QueryInterface(IDataObject *This, REFIID riid, + void **ppvObject) +{ + (void)This; + + if (!ppvObject) + return E_INVALIDARG; + + if (IsEqualIID(riid, &IID_IDataObject) || IsEqualIID(riid, &IID_IUnknown)) + { + IDataObject_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrDataObject_AddRef(IDataObject *This) +{ + CliprdrDataObject *instance = (CliprdrDataObject *)This; + + if (!instance) + return E_INVALIDARG; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrDataObject_Release(IDataObject *This) +{ + LONG count; + CliprdrDataObject *instance = (CliprdrDataObject *)This; + + if (!instance) + return E_INVALIDARG; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrDataObject_Delete(instance); + return 0; + } + else + return count; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData(IDataObject *This, FORMATETC *pFormatEtc, + STGMEDIUM *pMedium) +{ + ULONG i; + LONG idx; + CliprdrDataObject *instance = (CliprdrDataObject *)This; + wfClipboard *clipboard; + + if (!pFormatEtc || !pMedium || !instance) + return E_INVALIDARG; + + clipboard = (wfClipboard *)instance->m_pData; + + if (!clipboard) + return E_INVALIDARG; + + if ((idx = cliprdr_lookup_format(instance, pFormatEtc)) == -1) + return DV_E_FORMATETC; + + pMedium->tymed = instance->m_pFormatEtc[idx].tymed; + pMedium->pUnkForRelease = 0; + + if (instance->m_pFormatEtc[idx].cfFormat == RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW)) + { + // FILEGROUPDESCRIPTOR *dsc; + FILEGROUPDESCRIPTORW *dsc; + // DWORD remote_format_id = get_remote_format_id(clipboard, instance->m_pFormatEtc[idx].cfFormat); + // FIXME: origin code may be failed here??? + if (cliprdr_send_data_request(instance->m_serverConnID, instance->m_remoteConnID, clipboard, instance->m_pFormatEtc[idx].cfFormat) != 0) + return E_UNEXPECTED; + + pMedium->hGlobal = clipboard->hmem; /* points to a FILEGROUPDESCRIPTOR structure */ + /* GlobalLock returns a pointer to the first byte of the memory block, + * in which is a FILEGROUPDESCRIPTOR structure, whose first UINT member + * is the number of FILEDESCRIPTOR's */ + // dsc = (FILEGROUPDESCRIPTOR *)GlobalLock(clipboard->hmem); + dsc = (FILEGROUPDESCRIPTORW *)GlobalLock(clipboard->hmem); + instance->m_nStreams = dsc->cItems; + GlobalUnlock(clipboard->hmem); + + if (instance->m_nStreams > 0) + { + if (!instance->m_pStream) + { + instance->m_pStream = (LPSTREAM *)calloc(instance->m_nStreams, sizeof(LPSTREAM)); + + if (instance->m_pStream) + { + for (i = 0; i < instance->m_nStreams; i++) + { + instance->m_pStream[i] = + (IStream *)CliprdrStream_New(instance->m_serverConnID, instance->m_remoteConnID, i, clipboard, &dsc->fgd[i]); + + if (!instance->m_pStream[i]) + return E_OUTOFMEMORY; + } + } + } + } + + if (!instance->m_pStream) + { + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + + pMedium->hGlobal = NULL; + return E_OUTOFMEMORY; + } + } + else if (instance->m_pFormatEtc[idx].cfFormat == RegisterClipboardFormat(CFSTR_FILECONTENTS)) + { + if ((pFormatEtc->lindex >= 0) && ((ULONG)pFormatEtc->lindex < instance->m_nStreams)) + { + pMedium->pstm = instance->m_pStream[pFormatEtc->lindex]; + IDataObject_AddRef(instance->m_pStream[pFormatEtc->lindex]); + } + else + return E_INVALIDARG; + } + else + return E_UNEXPECTED; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetDataHere(IDataObject *This, + FORMATETC *pformatetc, + STGMEDIUM *pmedium) +{ + (void)This; + (void)pformatetc; + (void)pmedium; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_QueryGetData(IDataObject *This, + FORMATETC *pformatetc) +{ + CliprdrDataObject *instance = (CliprdrDataObject *)This; + + if (!pformatetc) + return E_INVALIDARG; + + if (cliprdr_lookup_format(instance, pformatetc) == -1) + return DV_E_FORMATETC; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetCanonicalFormatEtc(IDataObject *This, + FORMATETC *pformatectIn, + FORMATETC *pformatetcOut) +{ + (void)This; + (void)pformatectIn; + + if (!pformatetcOut) + return E_INVALIDARG; + + pformatetcOut->ptd = NULL; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_SetData(IDataObject *This, FORMATETC *pformatetc, + STGMEDIUM *pmedium, BOOL fRelease) +{ + (void)This; + (void)pformatetc; + (void)pmedium; + (void)fRelease; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumFormatEtc(IDataObject *This, + DWORD dwDirection, + IEnumFORMATETC **ppenumFormatEtc) +{ + CliprdrDataObject *instance = (CliprdrDataObject *)This; + + if (!instance || !ppenumFormatEtc) + return E_INVALIDARG; + + if (dwDirection == DATADIR_GET) + { + *ppenumFormatEtc = (IEnumFORMATETC *)CliprdrEnumFORMATETC_New(instance->m_nNumFormats, + instance->m_pFormatEtc); + return (*ppenumFormatEtc) ? S_OK : E_OUTOFMEMORY; + } + else + { + return E_NOTIMPL; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_DAdvise(IDataObject *This, FORMATETC *pformatetc, + DWORD advf, IAdviseSink *pAdvSink, + DWORD *pdwConnection) +{ + (void)This; + (void)pformatetc; + (void)advf; + (void)pAdvSink; + (void)pdwConnection; + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_DUnadvise(IDataObject *This, DWORD dwConnection) +{ + (void)This; + (void)dwConnection; + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumDAdvise(IDataObject *This, + IEnumSTATDATA **ppenumAdvise) +{ + (void)This; + (void)ppenumAdvise; + return OLE_E_ADVISENOTSUPPORTED; +} + +static CliprdrDataObject *CliprdrDataObject_New(UINT32 serverConnID, UINT32 remoteConnID, FORMATETC *fmtetc, STGMEDIUM *stgmed, ULONG count, + void *data) +{ + CliprdrDataObject *instance; + IDataObject *iDataObject; + instance = (CliprdrDataObject *)calloc(1, sizeof(CliprdrDataObject)); + + if (!instance) + goto error; + + iDataObject = &instance->iDataObject; + iDataObject->lpVtbl = (IDataObjectVtbl *)calloc(1, sizeof(IDataObjectVtbl)); + + if (!iDataObject->lpVtbl) + goto error; + + iDataObject->lpVtbl->QueryInterface = CliprdrDataObject_QueryInterface; + iDataObject->lpVtbl->AddRef = CliprdrDataObject_AddRef; + iDataObject->lpVtbl->Release = CliprdrDataObject_Release; + iDataObject->lpVtbl->GetData = CliprdrDataObject_GetData; + iDataObject->lpVtbl->GetDataHere = CliprdrDataObject_GetDataHere; + iDataObject->lpVtbl->QueryGetData = CliprdrDataObject_QueryGetData; + iDataObject->lpVtbl->GetCanonicalFormatEtc = CliprdrDataObject_GetCanonicalFormatEtc; + iDataObject->lpVtbl->SetData = CliprdrDataObject_SetData; + iDataObject->lpVtbl->EnumFormatEtc = CliprdrDataObject_EnumFormatEtc; + iDataObject->lpVtbl->DAdvise = CliprdrDataObject_DAdvise; + iDataObject->lpVtbl->DUnadvise = CliprdrDataObject_DUnadvise; + iDataObject->lpVtbl->EnumDAdvise = CliprdrDataObject_EnumDAdvise; + instance->m_lRefCount = 1; + instance->m_nNumFormats = count; + instance->m_pData = data; + instance->m_nStreams = 0; + instance->m_pStream = NULL; + instance->m_serverConnID = serverConnID; + instance->m_remoteConnID = remoteConnID; + + if (count > 0) + { + ULONG i; + instance->m_pFormatEtc = (FORMATETC *)calloc(count, sizeof(FORMATETC)); + + if (!instance->m_pFormatEtc) + goto error; + + instance->m_pStgMedium = (STGMEDIUM *)calloc(count, sizeof(STGMEDIUM)); + + if (!instance->m_pStgMedium) + goto error; + + for (i = 0; i < count; i++) + { + instance->m_pFormatEtc[i] = fmtetc[i]; + instance->m_pStgMedium[i] = stgmed[i]; + } + } + + return instance; +error: + CliprdrDataObject_Delete(instance); + return NULL; +} + +void CliprdrDataObject_Delete(CliprdrDataObject *instance) +{ + if (instance) + { + free(instance->iDataObject.lpVtbl); + free(instance->m_pFormatEtc); + free(instance->m_pStgMedium); + + if (instance->m_pStream) + { + ULONG i; + + for (i = 0; i < instance->m_nStreams; i++) + CliprdrStream_Release(instance->m_pStream[i]); + + free(instance->m_pStream); + } + + free(instance); + } +} + +static BOOL wf_create_file_obj(CONN_ID *conn_id, wfClipboard *clipboard, IDataObject **ppDataObject) +{ + FORMATETC fmtetc[2]; + STGMEDIUM stgmeds[2]; + + if (!ppDataObject) + return FALSE; + + fmtetc[0].cfFormat = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + fmtetc[0].dwAspect = DVASPECT_CONTENT; + fmtetc[0].lindex = 0; + fmtetc[0].ptd = NULL; + fmtetc[0].tymed = TYMED_HGLOBAL; + stgmeds[0].tymed = TYMED_HGLOBAL; + stgmeds[0].hGlobal = NULL; + stgmeds[0].pUnkForRelease = NULL; + fmtetc[1].cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); + fmtetc[1].dwAspect = DVASPECT_CONTENT; + fmtetc[1].lindex = 0; + fmtetc[1].ptd = NULL; + fmtetc[1].tymed = TYMED_ISTREAM; + stgmeds[1].tymed = TYMED_ISTREAM; + stgmeds[1].pstm = NULL; + stgmeds[1].pUnkForRelease = NULL; + *ppDataObject = (IDataObject *)CliprdrDataObject_New(conn_id->serverConnID, conn_id->remoteConnID, fmtetc, stgmeds, 2, clipboard); + return (*ppDataObject) ? TRUE : FALSE; +} + +static void wf_destroy_file_obj(IDataObject *instance) +{ + if (instance) + IDataObject_Release(instance); +} + +/** + * IEnumFORMATETC + */ + +static void cliprdr_format_deep_copy(FORMATETC *dest, FORMATETC *source) +{ + *dest = *source; + + if (source->ptd) + { + dest->ptd = (DVTARGETDEVICE *)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + + if (dest->ptd) + *(dest->ptd) = *(source->ptd); + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_QueryInterface(IEnumFORMATETC *This, + REFIID riid, void **ppvObject) +{ + (void)This; + + if (IsEqualIID(riid, &IID_IEnumFORMATETC) || IsEqualIID(riid, &IID_IUnknown)) + { + IEnumFORMATETC_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrEnumFORMATETC_AddRef(IEnumFORMATETC *This) +{ + CliprdrEnumFORMATETC *instance = (CliprdrEnumFORMATETC *)This; + + if (!instance) + return 0; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrEnumFORMATETC_Release(IEnumFORMATETC *This) +{ + LONG count; + CliprdrEnumFORMATETC *instance = (CliprdrEnumFORMATETC *)This; + + if (!instance) + return 0; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrEnumFORMATETC_Delete(instance); + return 0; + } + else + { + return count; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Next(IEnumFORMATETC *This, ULONG celt, + FORMATETC *rgelt, ULONG *pceltFetched) +{ + ULONG copied = 0; + CliprdrEnumFORMATETC *instance = (CliprdrEnumFORMATETC *)This; + + if (!instance || !celt || !rgelt) + return E_INVALIDARG; + + while ((instance->m_nIndex < instance->m_nNumFormats) && (copied < celt)) + { + cliprdr_format_deep_copy(&rgelt[copied++], &instance->m_pFormatEtc[instance->m_nIndex++]); + } + + if (pceltFetched != 0) + *pceltFetched = copied; + + return (copied == celt) ? S_OK : E_FAIL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Skip(IEnumFORMATETC *This, ULONG celt) +{ + CliprdrEnumFORMATETC *instance = (CliprdrEnumFORMATETC *)This; + + if (!instance) + return E_INVALIDARG; + + if (instance->m_nIndex + (LONG)celt > instance->m_nNumFormats) + return E_FAIL; + + instance->m_nIndex += celt; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Reset(IEnumFORMATETC *This) +{ + CliprdrEnumFORMATETC *instance = (CliprdrEnumFORMATETC *)This; + + if (!instance) + return E_INVALIDARG; + + instance->m_nIndex = 0; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Clone(IEnumFORMATETC *This, + IEnumFORMATETC **ppEnum) +{ + CliprdrEnumFORMATETC *instance = (CliprdrEnumFORMATETC *)This; + + if (!instance || !ppEnum) + return E_INVALIDARG; + + *ppEnum = + (IEnumFORMATETC *)CliprdrEnumFORMATETC_New(instance->m_nNumFormats, instance->m_pFormatEtc); + + if (!*ppEnum) + return E_OUTOFMEMORY; + + ((CliprdrEnumFORMATETC *)*ppEnum)->m_nIndex = instance->m_nIndex; + return S_OK; +} + +CliprdrEnumFORMATETC *CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC *pFormatEtc) +{ + ULONG i; + CliprdrEnumFORMATETC *instance; + IEnumFORMATETC *iEnumFORMATETC; + + if ((nFormats != 0) && !pFormatEtc) + return NULL; + + instance = (CliprdrEnumFORMATETC *)calloc(1, sizeof(CliprdrEnumFORMATETC)); + + if (!instance) + goto error; + + iEnumFORMATETC = &instance->iEnumFORMATETC; + iEnumFORMATETC->lpVtbl = (IEnumFORMATETCVtbl *)calloc(1, sizeof(IEnumFORMATETCVtbl)); + + if (!iEnumFORMATETC->lpVtbl) + goto error; + + iEnumFORMATETC->lpVtbl->QueryInterface = CliprdrEnumFORMATETC_QueryInterface; + iEnumFORMATETC->lpVtbl->AddRef = CliprdrEnumFORMATETC_AddRef; + iEnumFORMATETC->lpVtbl->Release = CliprdrEnumFORMATETC_Release; + iEnumFORMATETC->lpVtbl->Next = CliprdrEnumFORMATETC_Next; + iEnumFORMATETC->lpVtbl->Skip = CliprdrEnumFORMATETC_Skip; + iEnumFORMATETC->lpVtbl->Reset = CliprdrEnumFORMATETC_Reset; + iEnumFORMATETC->lpVtbl->Clone = CliprdrEnumFORMATETC_Clone; + instance->m_lRefCount = 1; + instance->m_nIndex = 0; + instance->m_nNumFormats = nFormats; + + if (nFormats > 0) + { + instance->m_pFormatEtc = (FORMATETC *)calloc(nFormats, sizeof(FORMATETC)); + + if (!instance->m_pFormatEtc) + goto error; + + for (i = 0; i < nFormats; i++) + cliprdr_format_deep_copy(&instance->m_pFormatEtc[i], &pFormatEtc[i]); + } + + return instance; +error: + CliprdrEnumFORMATETC_Delete(instance); + return NULL; +} + +void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC *instance) +{ + LONG i; + + if (instance) + { + free(instance->iEnumFORMATETC.lpVtbl); + + if (instance->m_pFormatEtc) + { + for (i = 0; i < instance->m_nNumFormats; i++) + { + if (instance->m_pFormatEtc[i].ptd) + CoTaskMemFree(instance->m_pFormatEtc[i].ptd); + } + + free(instance->m_pFormatEtc); + } + + free(instance); + } +} + +/***********************************************************************************/ + +static UINT32 get_local_format_id_by_name(wfClipboard *clipboard, const TCHAR *format_name) +{ + size_t i; + formatMapping *map; + WCHAR *unicode_name; +#if !defined(UNICODE) + size_t size; +#endif + + if (!clipboard || !format_name) + return 0; + +#if defined(UNICODE) + unicode_name = _wcsdup(format_name); +#else + size = _tcslen(format_name); + unicode_name = calloc(size + 1, sizeof(WCHAR)); + + if (!unicode_name) + return 0; + + MultiByteToWideChar(CP_OEMCP, 0, format_name, strlen(format_name), unicode_name, size); +#endif + + if (!unicode_name) + return 0; + + for (i = 0; i < clipboard->map_size; i++) + { + map = &clipboard->format_mappings[i]; + + if (map->name) + { + if (wcscmp(map->name, unicode_name) == 0) + { + free(unicode_name); + return map->local_format_id; + } + } + } + + free(unicode_name); + return 0; +} + +static BOOL file_transferring(wfClipboard *clipboard) +{ + return get_local_format_id_by_name(clipboard, CFSTR_FILEDESCRIPTORW) ? TRUE : FALSE; +} + +static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format) +{ + UINT32 i; + formatMapping *map; + + if (!clipboard) + return 0; + + for (i = 0; i < clipboard->map_size; i++) + { + map = &clipboard->format_mappings[i]; + + if (map->local_format_id == local_format) + return map->remote_format_id; + } + + return local_format; +} + +static void map_ensure_capacity(wfClipboard *clipboard) +{ + if (!clipboard) + return; + + if (clipboard->map_size >= clipboard->map_capacity) + { + size_t new_size; + formatMapping *new_map; + new_size = clipboard->map_capacity * 2; + new_map = + (formatMapping *)realloc(clipboard->format_mappings, sizeof(formatMapping) * new_size); + + if (!new_map) + return; + + clipboard->format_mappings = new_map; + clipboard->map_capacity = new_size; + } +} + +static BOOL clear_format_map(wfClipboard *clipboard) +{ + size_t i; + formatMapping *map; + + if (!clipboard) + return FALSE; + + if (clipboard->format_mappings) + { + for (i = 0; i < clipboard->map_capacity; i++) + { + map = &clipboard->format_mappings[i]; + map->remote_format_id = 0; + map->local_format_id = 0; + free(map->name); + map->name = NULL; + } + } + + clipboard->map_size = 0; + return TRUE; +} + +static UINT cliprdr_send_tempdir(wfClipboard *clipboard) +{ + CLIPRDR_TEMP_DIRECTORY tempDirectory; + + if (!clipboard) + return -1; + + if (GetEnvironmentVariableA("TEMP", tempDirectory.szTempDir, sizeof(tempDirectory.szTempDir)) == + 0) + return -1; + + return clipboard->context->TempDirectory(clipboard->context, &tempDirectory); +} + +static BOOL cliprdr_GetUpdatedClipboardFormats(wfClipboard *clipboard, PUINT lpuiFormats, + UINT cFormats, PUINT pcFormatsOut) +{ + UINT index = 0; + UINT format = 0; + BOOL clipboardOpen = FALSE; + + if (!clipboard->legacyApi) + return clipboard->GetUpdatedClipboardFormats(lpuiFormats, cFormats, pcFormatsOut); + + clipboardOpen = try_open_clipboard(clipboard->hwnd); + + if (!clipboardOpen) + { + *pcFormatsOut = 0; + return TRUE; /* Other app holding clipboard */ + } + + while (index < cFormats) + { + format = EnumClipboardFormats(format); + + if (!format) + break; + + lpuiFormats[index] = format; + index++; + } + + *pcFormatsOut = index; + CloseClipboard(); + return TRUE; +} + +static UINT cliprdr_send_format_list(wfClipboard *clipboard) +{ + UINT rc; + int count = 0; + UINT32 index; + UINT32 numFormats = 0; + UINT32 formatId = 0; + char formatName[1024]; + CLIPRDR_FORMAT *formats = NULL; + CLIPRDR_FORMAT_LIST formatList = {0}; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + ZeroMemory(&formatList, sizeof(CLIPRDR_FORMAT_LIST)); + + /* Ignore if other app is holding clipboard */ + if (try_open_clipboard(clipboard->hwnd)) + { + count = CountClipboardFormats(); + numFormats = (UINT32)count; + formats = (CLIPRDR_FORMAT *)calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + CloseClipboard(); + return CHANNEL_RC_NO_MEMORY; + } + + index = 0; + + if (IsClipboardFormatAvailable(CF_HDROP)) + { + UINT fsid = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + UINT fcid = RegisterClipboardFormat(CFSTR_FILECONTENTS); + + formats[index++].formatId = fsid; + formats[index++].formatId = fcid; + } + else + { + while (formatId = EnumClipboardFormats(formatId)) + formats[index++].formatId = formatId; + } + + numFormats = index; + + if (!CloseClipboard()) + { + free(formats); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < numFormats; index++) + { + if (GetClipboardFormatNameA(formats[index].formatId, formatName, sizeof(formatName))) + { + formats[index].formatName = _strdup(formatName); + } + } + } + + formatList.serverConnID = 0; + formatList.remoteConnID = 0; + formatList.numFormats = numFormats; + formatList.formats = formats; + formatList.msgType = CB_FORMAT_LIST; + + // send + rc = clipboard->context->ClientFormatList(clipboard->context, &formatList); + + for (index = 0; index < numFormats; index++) + free(formats[index].formatName); + + free(formats); + return rc; + // return 0; +} + +static UINT cliprdr_send_data_request(UINT32 serverConnID, UINT32 remoteConnID, wfClipboard *clipboard, UINT32 formatId) +{ + UINT rc; + UINT32 remoteFormatId; + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientFormatDataRequest) + return ERROR_INTERNAL_ERROR; + + remoteFormatId = get_remote_format_id(clipboard, formatId); + + formatDataRequest.serverConnID = serverConnID; + formatDataRequest.remoteConnID = remoteConnID; + formatDataRequest.requestedFormatId = remoteFormatId; + clipboard->requestedFormatId = formatId; + rc = clipboard->context->ClientFormatDataRequest(clipboard->context, &formatDataRequest); + + if (WaitForSingleObject(clipboard->response_data_event, INFINITE) != WAIT_OBJECT_0) + rc = ERROR_INTERNAL_ERROR; + else if (!ResetEvent(clipboard->response_data_event)) + rc = ERROR_INTERNAL_ERROR; + + return rc; +} + +UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 serverConnID, UINT32 remoteConnID, const void *streamid, ULONG index, + UINT32 flag, DWORD positionhigh, DWORD positionlow, + ULONG nreq) +{ + UINT rc; + CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsRequest) + return ERROR_INTERNAL_ERROR; + + fileContentsRequest.serverConnID = serverConnID; + fileContentsRequest.remoteConnID = remoteConnID; + fileContentsRequest.streamId = (UINT32)(ULONG_PTR)streamid; + fileContentsRequest.listIndex = index; + fileContentsRequest.dwFlags = flag; + fileContentsRequest.nPositionLow = positionlow; + fileContentsRequest.nPositionHigh = positionhigh; + fileContentsRequest.cbRequested = nreq; + fileContentsRequest.clipDataId = 0; + fileContentsRequest.msgFlags = 0; + rc = clipboard->context->ClientFileContentsRequest(clipboard->context, &fileContentsRequest); + + if (WaitForSingleObject(clipboard->req_fevent, INFINITE) != WAIT_OBJECT_0) + rc = ERROR_INTERNAL_ERROR; + else if (!ResetEvent(clipboard->req_fevent)) + rc = ERROR_INTERNAL_ERROR; + + return rc; +} + +static UINT cliprdr_send_response_filecontents(wfClipboard *clipboard, UINT32 serverConnID, UINT32 remoteConnID, UINT32 streamId, UINT32 size, + BYTE *data) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE fileContentsResponse; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsResponse) + return ERROR_INTERNAL_ERROR; + + fileContentsResponse.serverConnID = serverConnID; + fileContentsResponse.remoteConnID = remoteConnID; + fileContentsResponse.streamId = streamId; + fileContentsResponse.cbRequested = size; + fileContentsResponse.requestedData = data; + fileContentsResponse.msgFlags = CB_RESPONSE_OK; + return clipboard->context->ClientFileContentsResponse(clipboard->context, + &fileContentsResponse); +} + +static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + static wfClipboard *clipboard = NULL; + + switch (Msg) + { + case WM_CREATE: + DEBUG_CLIPRDR("info: WM_CREATE"); + clipboard = (wfClipboard *)((CREATESTRUCT *)lParam)->lpCreateParams; + clipboard->hwnd = hWnd; + + if (!clipboard->legacyApi) + clipboard->AddClipboardFormatListener(hWnd); + else + clipboard->hWndNextViewer = SetClipboardViewer(hWnd); + + break; + + case WM_CLOSE: + DEBUG_CLIPRDR("info: WM_CLOSE"); + + if (!clipboard->legacyApi) + clipboard->RemoveClipboardFormatListener(hWnd); + + break; + + case WM_DESTROY: + if (clipboard->legacyApi) + ChangeClipboardChain(hWnd, clipboard->hWndNextViewer); + + break; + + case WM_CLIPBOARDUPDATE: + DEBUG_CLIPRDR("info: WM_CLIPBOARDUPDATE"); + + // if (clipboard->sync) + { + if ((GetClipboardOwner() != clipboard->hwnd) && + (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj))) + { + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + + cliprdr_send_format_list(clipboard); + } + } + + break; + + case WM_RENDERALLFORMATS: + DEBUG_CLIPRDR("info: WM_RENDERALLFORMATS"); + + /* discard all contexts in clipboard */ + if (!try_open_clipboard(clipboard->hwnd)) + { + DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError()); + break; + } + + EmptyClipboard(); + CloseClipboard(); + break; + + case WM_RENDERFORMAT: + DEBUG_CLIPRDR("info: WM_RENDERFORMAT"); + + // https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-renderformat?redirectedfrom=MSDN + if (cliprdr_send_data_request(0, 0, clipboard, (UINT32)wParam) != 0) + { + DEBUG_CLIPRDR("error: cliprdr_send_data_request failed."); + break; + } + + if (!SetClipboardData((UINT)wParam, clipboard->hmem)) + { + DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError()); + + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + } + + /* Note: GlobalFree() is not needed when success */ + break; + + case WM_DRAWCLIPBOARD: + if (clipboard->legacyApi) + { + if ((GetClipboardOwner() != clipboard->hwnd) && + (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj))) + { + cliprdr_send_format_list(clipboard); + } + + SendMessage(clipboard->hWndNextViewer, Msg, wParam, lParam); + } + + break; + + case WM_CHANGECBCHAIN: + if (clipboard->legacyApi) + { + HWND hWndCurrViewer = (HWND)wParam; + HWND hWndNextViewer = (HWND)lParam; + + if (hWndCurrViewer == clipboard->hWndNextViewer) + clipboard->hWndNextViewer = hWndNextViewer; + else if (clipboard->hWndNextViewer) + SendMessage(clipboard->hWndNextViewer, Msg, wParam, lParam); + } + + break; + + case WM_CLIPRDR_MESSAGE: + DEBUG_CLIPRDR("info: WM_CLIPRDR_MESSAGE"); + + switch (wParam) + { + case OLE_SETCLIPBOARD: + DEBUG_CLIPRDR("info: OLE_SETCLIPBOARD"); + + if (clipboard->data_obj != NULL) + { + wf_destroy_file_obj(clipboard->data_obj); + clipboard->data_obj = NULL; + } + if (wf_create_file_obj((CONN_ID *)lParam, clipboard, &clipboard->data_obj)) + { + HRESULT res = OleSetClipboard(clipboard->data_obj); + if (res != S_OK) + { + wf_destroy_file_obj(clipboard->data_obj); + clipboard->data_obj = NULL; + } + } + free((void *)lParam); + + break; + + case DELAY_RENDERING: + if (!try_open_clipboard(clipboard->hwnd)) + { + // failed to open clipboard + break; + } + + FORMAT_IDS *format_ids = (FORMAT_IDS *)lParam; + for (UINT32 i = 0; i < format_ids->size; ++i) + { + if (cliprdr_send_data_request(format_ids->serverConnID, format_ids->remoteConnID, clipboard, format_ids->formats[i]) != 0) + { + DEBUG_CLIPRDR("error: cliprdr_send_data_request failed."); + continue; + } + + if (!SetClipboardData(format_ids->formats[i], clipboard->hmem)) + { + printf("SetClipboardData failed with 0x%x\n", GetLastError()); + DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError()); + + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + } + } + + if (!CloseClipboard() && GetLastError()) + { + // failed to close clipboard? + } + + free(format_ids->formats); + free(format_ids); + + break; + + default: + break; + } + + break; + + case WM_DESTROYCLIPBOARD: + case WM_ASKCBFORMATNAME: + case WM_HSCROLLCLIPBOARD: + case WM_PAINTCLIPBOARD: + case WM_SIZECLIPBOARD: + case WM_VSCROLLCLIPBOARD: + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return 0; +} + +static int create_cliprdr_window(wfClipboard *clipboard) +{ + WNDCLASSEX wnd_cls; + ZeroMemory(&wnd_cls, sizeof(WNDCLASSEX)); + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_OWNDC; + wnd_cls.lpfnWndProc = cliprdr_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = NULL; + wnd_cls.hCursor = NULL; + wnd_cls.hbrBackground = NULL; + wnd_cls.lpszMenuName = NULL; + wnd_cls.lpszClassName = _T("ClipboardHiddenMessageProcessor"); + wnd_cls.hInstance = GetModuleHandle(NULL); + wnd_cls.hIconSm = NULL; + RegisterClassEx(&wnd_cls); + clipboard->hwnd = + CreateWindowEx(WS_EX_LEFT, _T("ClipboardHiddenMessageProcessor"), _T("rdpclip"), 0, 0, 0, 0, + 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), clipboard); + + if (!clipboard->hwnd) + { + DEBUG_CLIPRDR("error: CreateWindowEx failed with %x.", GetLastError()); + return -1; + } + + return 0; +} + +static DWORD WINAPI cliprdr_thread_func(LPVOID arg) +{ + int ret; + MSG msg; + BOOL mcode; + wfClipboard *clipboard = (wfClipboard *)arg; + OleInitialize(0); + + if ((ret = create_cliprdr_window(clipboard)) != 0) + { + OleUninitialize(); + DEBUG_CLIPRDR("error: create clipboard window failed."); + return 0; + } + + while ((mcode = GetMessage(&msg, 0, 0, 0)) != 0) + { + if (mcode == -1) + { + DEBUG_CLIPRDR("error: clipboard thread GetMessage failed."); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + OleUninitialize(); + return 0; +} + +static void clear_file_array(wfClipboard *clipboard) +{ + size_t i; + + if (!clipboard) + return; + + /* clear file_names array */ + if (clipboard->file_names) + { + for (i = 0; i < clipboard->nFiles; i++) + { + free(clipboard->file_names[i]); + clipboard->file_names[i] = NULL; + } + + free(clipboard->file_names); + clipboard->file_names = NULL; + } + + /* clear fileDescriptor array */ + if (clipboard->fileDescriptor) + { + for (i = 0; i < clipboard->nFiles; i++) + { + free(clipboard->fileDescriptor[i]); + clipboard->fileDescriptor[i] = NULL; + } + + free(clipboard->fileDescriptor); + clipboard->fileDescriptor = NULL; + } + + clipboard->file_array_size = 0; + clipboard->nFiles = 0; +} + +static BOOL wf_cliprdr_get_file_contents(WCHAR *file_name, BYTE *buffer, LONG positionLow, + LONG positionHigh, DWORD nRequested, DWORD *puSize) +{ + BOOL res = FALSE; + HANDLE hFile; + DWORD nGet, rc; + + if (!file_name || !buffer || !puSize) + { + printf("get file contents Invalid Arguments.\n"); + return FALSE; + } + + hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + rc = SetFilePointer(hFile, positionLow, &positionHigh, FILE_BEGIN); + + if (rc == INVALID_SET_FILE_POINTER) + goto error; + + if (!ReadFile(hFile, buffer, nRequested, &nGet, NULL)) + { + DEBUG_CLIPRDR("ReadFile failed with 0x%08lX.", GetLastError()); + goto error; + } + + res = TRUE; +error: + + if (!CloseHandle(hFile)) + res = FALSE; + + if (res) + *puSize = nGet; + + return res; +} + +/* path_name has a '\' at the end. e.g. c:\newfolder\, file_name is c:\newfolder\new.txt */ +static FILEDESCRIPTORW *wf_cliprdr_get_file_descriptor(WCHAR *file_name, size_t pathLen) +{ + HANDLE hFile; + FILEDESCRIPTORW *fd; + fd = (FILEDESCRIPTORW *)calloc(1, sizeof(FILEDESCRIPTORW)); + + if (!fd) + return NULL; + + hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + free(fd); + return NULL; + } + + // fd->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI; + fd->dwFlags = FD_ATTRIBUTES | FD_WRITESTIME | FD_PROGRESSUI; + fd->dwFileAttributes = GetFileAttributesW(file_name); + if (fd->dwFileAttributes == INVALID_FILE_ATTRIBUTES) + { + // TODO: debug handle some errors + } + + if (!GetFileTime(hFile, NULL, NULL, &fd->ftLastWriteTime)) + { + fd->dwFlags &= ~FD_WRITESTIME; + } + + fd->nFileSizeLow = GetFileSize(hFile, &fd->nFileSizeHigh); + wcscpy_s(fd->cFileName, sizeof(fd->cFileName) / 2, file_name + pathLen); + CloseHandle(hFile); + + return fd; +} + +static BOOL wf_cliprdr_array_ensure_capacity(wfClipboard *clipboard) +{ + if (!clipboard) + return FALSE; + + if (clipboard->nFiles == clipboard->file_array_size) + { + size_t new_size; + FILEDESCRIPTORW **new_fd; + WCHAR **new_name; + new_size = (clipboard->file_array_size + 1) * 2; + new_fd = (FILEDESCRIPTORW **)realloc(clipboard->fileDescriptor, + new_size * sizeof(FILEDESCRIPTORW *)); + + if (new_fd) + clipboard->fileDescriptor = new_fd; + + new_name = (WCHAR **)realloc(clipboard->file_names, new_size * sizeof(WCHAR *)); + + if (new_name) + clipboard->file_names = new_name; + + if (!new_fd || !new_name) + return FALSE; + + clipboard->file_array_size = new_size; + } + + return TRUE; +} + +static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard *clipboard, WCHAR *full_file_name, + size_t pathLen) +{ + if (!wf_cliprdr_array_ensure_capacity(clipboard)) + return FALSE; + + /* add to name array */ + clipboard->file_names[clipboard->nFiles] = (LPWSTR)malloc(MAX_PATH * 2); + + if (!clipboard->file_names[clipboard->nFiles]) + return FALSE; + + wcscpy_s(clipboard->file_names[clipboard->nFiles], MAX_PATH, full_file_name); + /* add to descriptor array */ + clipboard->fileDescriptor[clipboard->nFiles] = + wf_cliprdr_get_file_descriptor(full_file_name, pathLen); + + if (!clipboard->fileDescriptor[clipboard->nFiles]) + { + free(clipboard->file_names[clipboard->nFiles]); + return FALSE; + } + + clipboard->nFiles++; + return TRUE; +} + +static BOOL wf_cliprdr_traverse_directory(wfClipboard *clipboard, WCHAR *Dir, size_t pathLen) +{ + HANDLE hFind; + WCHAR DirSpec[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + + if (!clipboard || !Dir) + return FALSE; + + // StringCchCopy(DirSpec, MAX_PATH, Dir); + // StringCchCat(DirSpec, MAX_PATH, TEXT("\\*")); + StringCchCopyW(DirSpec, MAX_PATH, Dir); + StringCchCatW(DirSpec, MAX_PATH, L"\\*"); + + // hFind = FindFirstFile(DirSpec, &FindFileData); + hFind = FindFirstFileW(DirSpec, &FindFileData); + + if (hFind == INVALID_HANDLE_VALUE) + { + // printf("FindFirstFile failed with 0x%x.\n", GetLastError()); + DEBUG_CLIPRDR("FindFirstFile failed with 0x%x.", GetLastError()); + return FALSE; + } + + while (FindNextFileW(hFind, &FindFileData)) + { + // if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && + // wcscmp(FindFileData.cFileName, _T(".")) == 0 || + // wcscmp(FindFileData.cFileName, _T("..")) == 0) + if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && + wcscmp(FindFileData.cFileName, L".") == 0 || + wcscmp(FindFileData.cFileName, L"..") == 0) + { + continue; + } + + if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + WCHAR DirAdd[MAX_PATH]; + // StringCchCopy(DirAdd, MAX_PATH, Dir); + // StringCchCat(DirAdd, MAX_PATH, _T("\\")); + // StringCchCat(DirAdd, MAX_PATH, FindFileData.cFileName); + StringCchCopyW(DirAdd, MAX_PATH, Dir); + StringCchCatW(DirAdd, MAX_PATH, L"\\"); + StringCchCatW(DirAdd, MAX_PATH, FindFileData.cFileName); + + if (!wf_cliprdr_add_to_file_arrays(clipboard, DirAdd, pathLen)) + return FALSE; + + if (!wf_cliprdr_traverse_directory(clipboard, DirAdd, pathLen)) + return FALSE; + } + else + { + WCHAR fileName[MAX_PATH]; + // StringCchCopy(fileName, MAX_PATH, Dir); + // StringCchCat(fileName, MAX_PATH, _T("\\")); + // StringCchCat(fileName, MAX_PATH, FindFileData.cFileName); + + StringCchCopyW(fileName, MAX_PATH, Dir); + StringCchCatW(fileName, MAX_PATH, L"\\"); + StringCchCatW(fileName, MAX_PATH, FindFileData.cFileName); + + if (!wf_cliprdr_add_to_file_arrays(clipboard, fileName, pathLen)) + return FALSE; + } + } + + FindClose(hFind); + return TRUE; +} + +static UINT wf_cliprdr_send_client_capabilities(wfClipboard *clipboard) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientCapabilities) + return ERROR_INTERNAL_ERROR; + + capabilities.serverConnID = 0; + capabilities.remoteConnID = 0; + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = + CB_USE_LONG_FORMAT_NAMES | CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS; + return clipboard->context->ClientCapabilities(clipboard->context, &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_monitor_ready(CliprdrClientContext *context, + const CLIPRDR_MONITOR_READY *monitorReady) +{ + UINT rc; + wfClipboard *clipboard = (wfClipboard *)context->custom; + + if (!context || !monitorReady) + return ERROR_INTERNAL_ERROR; + + clipboard->sync = TRUE; + rc = wf_cliprdr_send_client_capabilities(clipboard); + + if (rc != CHANNEL_RC_OK) + return rc; + + return cliprdr_send_format_list(clipboard); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_capabilities(CliprdrClientContext *context, + const CLIPRDR_CAPABILITIES *capabilities) +{ + UINT32 index; + CLIPRDR_CAPABILITY_SET *capabilitySet; + wfClipboard *clipboard = (wfClipboard *)context->custom; + + if (!context || !capabilities) + return ERROR_INTERNAL_ERROR; + + for (index = 0; index < capabilities->cCapabilitiesSets; index++) + { + capabilitySet = &(capabilities->capabilitySets[index]); + + if ((capabilitySet->capabilitySetType == CB_CAPSTYPE_GENERAL) && + (capabilitySet->capabilitySetLength >= CB_CAPSTYPE_GENERAL_LEN)) + { + CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = + (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySet; + clipboard->capabilities = generalCapabilitySet->generalFlags; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context, + const CLIPRDR_FORMAT_LIST *formatList) +{ + UINT rc = ERROR_INTERNAL_ERROR; + UINT32 i; + formatMapping *mapping; + CLIPRDR_FORMAT *format; + wfClipboard *clipboard = (wfClipboard *)context->custom; + + if (!clear_format_map(clipboard)) + return ERROR_INTERNAL_ERROR; + + for (i = 0; i < formatList->numFormats; i++) + { + format = &(formatList->formats[i]); + mapping = &(clipboard->format_mappings[i]); + mapping->remote_format_id = format->formatId; + + if (format->formatName) + { + int size = MultiByteToWideChar(CP_UTF8, 0, format->formatName, + strlen(format->formatName), NULL, 0); + mapping->name = calloc(size + 1, sizeof(WCHAR)); + + if (mapping->name) + { + MultiByteToWideChar(CP_UTF8, 0, format->formatName, strlen(format->formatName), + mapping->name, size); + mapping->local_format_id = RegisterClipboardFormatW((LPWSTR)mapping->name); + } + } + else + { + mapping->name = NULL; + mapping->local_format_id = mapping->remote_format_id; + } + + clipboard->map_size++; + map_ensure_capacity(clipboard); + } + + if (file_transferring(clipboard)) + { + if (context->enableFiles) + { + CONN_ID *p_conn_id = (CONN_ID *)calloc(1, sizeof(CONN_ID)); + p_conn_id->serverConnID = formatList->serverConnID; + p_conn_id->remoteConnID = formatList->remoteConnID; + if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, OLE_SETCLIPBOARD, p_conn_id)) + rc = CHANNEL_RC_OK; + } + else + { + rc = CHANNEL_RC_OK; + } + } + else + { + if (context->enableOthers) + { + if (!try_open_clipboard(clipboard->hwnd)) + return CHANNEL_RC_OK; /* Ignore, other app holding clipboard */ + + if (EmptyClipboard()) + { + // Modified: do not apply delayed rendering + // for (i = 0; i < (UINT32)clipboard->map_size; i++) + // SetClipboardData(clipboard->format_mappings[i].local_format_id, NULL); + + FORMAT_IDS *format_ids = (FORMAT_IDS *)calloc(1, sizeof(FORMAT_IDS)); + format_ids->serverConnID = formatList->serverConnID; + format_ids->remoteConnID = formatList->remoteConnID; + format_ids->size = (UINT32)clipboard->map_size; + format_ids->formats = (UINT32 *)calloc(format_ids->size, sizeof(UINT32)); + for (i = 0; i < format_ids->size; ++i) + { + format_ids->formats[i] = clipboard->format_mappings[i].local_format_id; + } + if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, DELAY_RENDERING, format_ids)) + { + rc = CHANNEL_RC_OK; + } + else + { + rc = ERROR_INTERNAL_ERROR; + } + } + + if (!CloseClipboard() && GetLastError()) + return ERROR_INTERNAL_ERROR; + } + else + { + rc = CHANNEL_RC_OK; + } + } + + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_format_list_response(CliprdrClientContext *context, + const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse) +{ + (void)context; + (void)formatListResponse; + + if (formatListResponse->msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_lock_clipboard_data(CliprdrClientContext *context, + const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData) +{ + (void)context; + (void)lockClipboardData; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_unlock_clipboard_data(CliprdrClientContext *context, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData) +{ + (void)context; + (void)unlockClipboardData; + return CHANNEL_RC_OK; +} + +static BOOL wf_cliprdr_process_filename(wfClipboard *clipboard, WCHAR *wFileName, size_t str_len) +{ + size_t pathLen; + size_t offset = str_len; + + if (!clipboard || !wFileName) + return FALSE; + + /* find the last '\' in full file name */ + while (offset > 0) + { + if (wFileName[offset] == L'\\') + break; + else + offset--; + } + + pathLen = offset + 1; + + if (!wf_cliprdr_add_to_file_arrays(clipboard, wFileName, pathLen)) + return FALSE; + + if ((clipboard->fileDescriptor[clipboard->nFiles - 1]->dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY) != 0) + { + /* this is a directory */ + if (!wf_cliprdr_traverse_directory(clipboard, wFileName, pathLen)) + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_format_data_request(CliprdrClientContext *context, + const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest) +{ + UINT rc; + size_t size = 0; + void *buff = NULL; + char *globlemem = NULL; + HANDLE hClipdata = NULL; + UINT32 requestedFormatId; + CLIPRDR_FORMAT_DATA_RESPONSE response; + wfClipboard *clipboard; + + if (!context || !formatDataRequest) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard *)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + requestedFormatId = formatDataRequest->requestedFormatId; + + if (requestedFormatId == RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW)) + { + size_t len; + size_t i; + WCHAR *wFileName; + HRESULT result; + LPDATAOBJECT dataObj; + FORMATETC format_etc; + STGMEDIUM stg_medium; + DROPFILES *dropFiles; + FILEGROUPDESCRIPTORW *groupDsc; + result = OleGetClipboard(&dataObj); + + if (FAILED(result)) + return ERROR_INTERNAL_ERROR; + + ZeroMemory(&format_etc, sizeof(FORMATETC)); + ZeroMemory(&stg_medium, sizeof(STGMEDIUM)); + /* get DROPFILES struct from OLE */ + format_etc.cfFormat = CF_HDROP; + format_etc.tymed = TYMED_HGLOBAL; + format_etc.dwAspect = 1; + format_etc.lindex = -1; + result = IDataObject_GetData(dataObj, &format_etc, &stg_medium); + + if (FAILED(result)) + { + DEBUG_CLIPRDR("dataObj->GetData failed."); + goto exit; + } + + dropFiles = (DROPFILES *)GlobalLock(stg_medium.hGlobal); + + if (!dropFiles) + { + GlobalUnlock(stg_medium.hGlobal); + ReleaseStgMedium(&stg_medium); + clipboard->nFiles = 0; + goto exit; + } + + clear_file_array(clipboard); + + if (dropFiles->fWide) + { + /* dropFiles contains file names */ + for (wFileName = (WCHAR *)((char *)dropFiles + dropFiles->pFiles); + (len = wcslen(wFileName)) > 0; wFileName += len + 1) + { + wf_cliprdr_process_filename(clipboard, wFileName, wcslen(wFileName)); + } + } + else + { + char *p; + + for (p = (char *)((char *)dropFiles + dropFiles->pFiles); (len = strlen(p)) > 0; + p += len + 1, clipboard->nFiles++) + { + int cchWideChar; + WCHAR *wFileName; + cchWideChar = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, NULL, 0); + wFileName = (LPWSTR)calloc(cchWideChar, sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, wFileName, cchWideChar); + wf_cliprdr_process_filename(clipboard, wFileName, cchWideChar); + } + } + + GlobalUnlock(stg_medium.hGlobal); + ReleaseStgMedium(&stg_medium); + exit: + size = 4 + clipboard->nFiles * sizeof(FILEDESCRIPTORW); + groupDsc = (FILEGROUPDESCRIPTORW *)malloc(size); + + if (groupDsc) + { + groupDsc->cItems = clipboard->nFiles; + + for (i = 0; i < clipboard->nFiles; i++) + { + if (clipboard->fileDescriptor[i]) + groupDsc->fgd[i] = *clipboard->fileDescriptor[i]; + } + + buff = groupDsc; + } + + IDataObject_Release(dataObj); + } + else + { + /* Ignore if other app is holding the clipboard */ + if (try_open_clipboard(clipboard->hwnd)) + { + hClipdata = GetClipboardData(requestedFormatId); + + if (!hClipdata) + { + CloseClipboard(); + return ERROR_INTERNAL_ERROR; + } + + globlemem = (char *)GlobalLock(hClipdata); + size = (int)GlobalSize(hClipdata); + buff = malloc(size); + CopyMemory(buff, globlemem, size); + GlobalUnlock(hClipdata); + CloseClipboard(); + } + } + + response.serverConnID = formatDataRequest->serverConnID; + response.remoteConnID = formatDataRequest->remoteConnID; + response.msgFlags = CB_RESPONSE_OK; + response.dataLen = size; + response.requestedFormatData = (BYTE *)buff; + rc = clipboard->context->ClientFormatDataResponse(clipboard->context, &response); + + free(buff); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_format_data_response(CliprdrClientContext *context, + const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse) +{ + BYTE *data; + HANDLE hMem; + wfClipboard *clipboard; + + if (!context || !formatDataResponse) + return ERROR_INTERNAL_ERROR; + + if (formatDataResponse->msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + clipboard = (wfClipboard *)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + hMem = GlobalAlloc(GMEM_MOVEABLE, formatDataResponse->dataLen); + + if (!hMem) + return ERROR_INTERNAL_ERROR; + + data = (BYTE *)GlobalLock(hMem); + + if (!data) + { + GlobalFree(hMem); + return ERROR_INTERNAL_ERROR; + } + + CopyMemory(data, formatDataResponse->requestedFormatData, formatDataResponse->dataLen); + + if (!GlobalUnlock(hMem) && GetLastError()) + { + GlobalFree(hMem); + return ERROR_INTERNAL_ERROR; + } + + clipboard->hmem = hMem; + + if (!SetEvent(clipboard->response_data_event)) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_file_contents_request(CliprdrClientContext *context, + const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest) +{ + DWORD uSize = 0; + BYTE *pData = NULL; + HRESULT hRet = S_OK; + FORMATETC vFormatEtc; + LPDATAOBJECT pDataObj = NULL; + STGMEDIUM vStgMedium; + BOOL bIsStreamFile = TRUE; + static LPSTREAM pStreamStc = NULL; + static UINT32 uStreamIdStc = 0; + wfClipboard *clipboard; + UINT rc = ERROR_INTERNAL_ERROR; + UINT sRc; + UINT32 cbRequested; + + if (!context || !fileContentsRequest) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard *)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + cbRequested = fileContentsRequest->cbRequested; + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + cbRequested = sizeof(UINT64); + + pData = (BYTE *)calloc(1, cbRequested); + + if (!pData) + goto error; + + hRet = OleGetClipboard(&pDataObj); + + if (FAILED(hRet)) + { + printf("filecontents: get ole clipboard failed.\n"); + goto error; + } + + ZeroMemory(&vFormatEtc, sizeof(FORMATETC)); + ZeroMemory(&vStgMedium, sizeof(STGMEDIUM)); + vFormatEtc.cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); + vFormatEtc.tymed = TYMED_ISTREAM; + vFormatEtc.dwAspect = 1; + vFormatEtc.lindex = fileContentsRequest->listIndex; + vFormatEtc.ptd = NULL; + + if ((uStreamIdStc != fileContentsRequest->streamId) || !pStreamStc) + { + LPENUMFORMATETC pEnumFormatEtc; + ULONG CeltFetched; + FORMATETC vFormatEtc2; + + if (pStreamStc) + { + IStream_Release(pStreamStc); + pStreamStc = NULL; + } + + bIsStreamFile = FALSE; + hRet = IDataObject_EnumFormatEtc(pDataObj, DATADIR_GET, &pEnumFormatEtc); + + if (hRet == S_OK) + { + do + { + hRet = IEnumFORMATETC_Next(pEnumFormatEtc, 1, &vFormatEtc2, &CeltFetched); + + if (hRet == S_OK) + { + if (vFormatEtc2.cfFormat == RegisterClipboardFormat(CFSTR_FILECONTENTS)) + { + hRet = IDataObject_GetData(pDataObj, &vFormatEtc, &vStgMedium); + + if (hRet == S_OK) + { + pStreamStc = vStgMedium.pstm; + uStreamIdStc = fileContentsRequest->streamId; + bIsStreamFile = TRUE; + } + + break; + } + } + } while (hRet == S_OK); + } + } + + if (bIsStreamFile == TRUE) + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + STATSTG vStatStg; + ZeroMemory(&vStatStg, sizeof(STATSTG)); + hRet = IStream_Stat(pStreamStc, &vStatStg, STATFLAG_NONAME); + + if (hRet == S_OK) + { + *((UINT32 *)&pData[0]) = vStatStg.cbSize.LowPart; + *((UINT32 *)&pData[4]) = vStatStg.cbSize.HighPart; + uSize = cbRequested; + } + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + LARGE_INTEGER dlibMove; + ULARGE_INTEGER dlibNewPosition; + dlibMove.HighPart = fileContentsRequest->nPositionHigh; + dlibMove.LowPart = fileContentsRequest->nPositionLow; + hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition); + + if (SUCCEEDED(hRet)) + hRet = IStream_Read(pStreamStc, pData, cbRequested, (PULONG)&uSize); + } + } + else + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + if (clipboard->nFiles <= fileContentsRequest->listIndex) + goto error; + *((UINT32 *)&pData[0]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeLow; + *((UINT32 *)&pData[4]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeHigh; + uSize = cbRequested; + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + BOOL bRet; + if (clipboard->nFiles <= fileContentsRequest->listIndex) + goto error; + bRet = wf_cliprdr_get_file_contents( + clipboard->file_names[fileContentsRequest->listIndex], pData, + fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, cbRequested, + &uSize); + + if (bRet == FALSE) + { + printf("get file contents failed.\n"); + uSize = 0; + goto error; + } + } + } + + rc = CHANNEL_RC_OK; +error: + + if (pDataObj) + IDataObject_Release(pDataObj); + + if (uSize == 0) + { + free(pData); + pData = NULL; + } + + sRc = + cliprdr_send_response_filecontents( + clipboard, + fileContentsRequest->serverConnID, + fileContentsRequest->remoteConnID, + fileContentsRequest->streamId, + uSize, + pData); + free(pData); + + if (sRc != CHANNEL_RC_OK) + return sRc; + + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_file_contents_response(CliprdrClientContext *context, + const CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse) +{ + wfClipboard *clipboard; + + if (!context || !fileContentsResponse) + return ERROR_INTERNAL_ERROR; + + if (fileContentsResponse->msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + clipboard = (wfClipboard *)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + clipboard->req_fsize = fileContentsResponse->cbRequested; + clipboard->req_fdata = (char *)malloc(fileContentsResponse->cbRequested); + + if (!clipboard->req_fdata) + return ERROR_INTERNAL_ERROR; + + CopyMemory(clipboard->req_fdata, fileContentsResponse->requestedData, + fileContentsResponse->cbRequested); + + if (!SetEvent(clipboard->req_fevent)) + { + free(clipboard->req_fdata); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr) +{ + if (!clipboard || !cliprdr) + return FALSE; + + clipboard->context = cliprdr; + clipboard->sync = FALSE; + clipboard->map_capacity = 32; + clipboard->map_size = 0; + clipboard->hUser32 = LoadLibraryA("user32.dll"); + clipboard->data_obj = NULL; + + if (clipboard->hUser32) + { + clipboard->AddClipboardFormatListener = (fnAddClipboardFormatListener)GetProcAddress( + clipboard->hUser32, "AddClipboardFormatListener"); + clipboard->RemoveClipboardFormatListener = (fnRemoveClipboardFormatListener)GetProcAddress( + clipboard->hUser32, "RemoveClipboardFormatListener"); + clipboard->GetUpdatedClipboardFormats = (fnGetUpdatedClipboardFormats)GetProcAddress( + clipboard->hUser32, "GetUpdatedClipboardFormats"); + } + + if (!(clipboard->hUser32 && clipboard->AddClipboardFormatListener && + clipboard->RemoveClipboardFormatListener && clipboard->GetUpdatedClipboardFormats)) + clipboard->legacyApi = TRUE; + + if (!(clipboard->format_mappings = + (formatMapping *)calloc(clipboard->map_capacity, sizeof(formatMapping)))) + goto error; + + if (!(clipboard->response_data_event = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto error; + + if (!(clipboard->req_fevent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto error; + + if (!(clipboard->thread = CreateThread(NULL, 0, cliprdr_thread_func, clipboard, 0, NULL))) + goto error; + + cliprdr->MonitorReady = wf_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = wf_cliprdr_server_capabilities; + cliprdr->ServerFormatList = wf_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = wf_cliprdr_server_format_list_response; + cliprdr->ServerLockClipboardData = wf_cliprdr_server_lock_clipboard_data; + cliprdr->ServerUnlockClipboardData = wf_cliprdr_server_unlock_clipboard_data; + cliprdr->ServerFormatDataRequest = wf_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = wf_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = wf_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = wf_cliprdr_server_file_contents_response; + cliprdr->custom = (void *)clipboard; + return TRUE; +error: + wf_cliprdr_uninit(clipboard, cliprdr); + return FALSE; +} + +BOOL wf_cliprdr_uninit(wfClipboard *clipboard, CliprdrClientContext *cliprdr) +{ + if (!clipboard || !cliprdr) + return FALSE; + + cliprdr->custom = NULL; + + if (clipboard->hwnd) + PostMessage(clipboard->hwnd, WM_QUIT, 0, 0); + + if (clipboard->thread) + { + WaitForSingleObject(clipboard->thread, INFINITE); + CloseHandle(clipboard->thread); + } + + if (clipboard->data_obj) + { + wf_destroy_file_obj(clipboard->data_obj); + clipboard->data_obj = NULL; + } + + if (clipboard->response_data_event) + CloseHandle(clipboard->response_data_event); + + if (clipboard->req_fevent) + CloseHandle(clipboard->req_fevent); + + clear_file_array(clipboard); + clear_format_map(clipboard); + free(clipboard->format_mappings); + return TRUE; +} + +wfClipboard clipboard; + +BOOL init_cliprdr(CliprdrClientContext *context) +{ + return wf_cliprdr_init(&clipboard, context); +} + +BOOL uninit_cliprdr(CliprdrClientContext *context) +{ + return wf_cliprdr_uninit(&clipboard, context); +} diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 16470a753..a20f5c5ae 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -306,6 +306,73 @@ message FileDirCreate { string path = 2; } +// main logic from freeRDP +message CliprdrMonitorReady { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; +} + +message CliprdrFormat { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + int32 id = 3; + string format = 4; +} +message CliprdrServerFormatList { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + repeated CliprdrFormat formats = 3; +} +message CliprdrServerFormatListResponse { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + int32 msg_flags = 3; +} + +message CliprdrServerFormatDataRequest { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + int32 requested_format_id = 3; +} +message CliprdrServerFormatDataResponse { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + int32 msg_flags = 3; + bytes format_data = 4; +} + +message CliprdrFileContentsRequest { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + int32 stream_id = 3; + int32 list_index = 4; + int32 dw_flags = 5; + int32 n_position_low = 6; + int32 n_position_high = 7; + int32 cb_requested = 8; + bool have_clip_data_id = 9; + int32 clip_data_id = 10; +} +message CliprdrFileContentsResponse { + int32 server_conn_id = 1; + int32 remote_conn_id = 2; + int32 msg_flags = 3; + int32 stream_id = 4; + bytes requested_data = 5; +} + +message Cliprdr { + oneof union { + CliprdrMonitorReady ready = 1; + CliprdrServerFormatList format_list = 2; + CliprdrServerFormatListResponse format_list_response = 3; + CliprdrServerFormatDataRequest format_data_request = 4; + CliprdrServerFormatDataResponse format_data_response = 5; + CliprdrFileContentsRequest file_contents_request = 6; + CliprdrFileContentsResponse file_contents_response = 7; + } +} + message SwitchDisplay { int32 display = 1; sint32 x = 2; @@ -405,5 +472,6 @@ message Message { FileAction file_action = 17; FileResponse file_response = 18; Misc misc = 19; + Cliprdr cliprdr = 20; } } diff --git a/src/common.rs b/src/common.rs index 16da93b7c..e8e59dea7 100644 --- a/src/common.rs +++ b/src/common.rs @@ -16,6 +16,7 @@ use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all}; use std::sync::{Arc, Mutex}; pub const CLIPBOARD_NAME: &'static str = "clipboard"; +pub const CLIPRDR_NAME: &'static str = "cliprdr"; pub const CLIPBOARD_INTERVAL: u64 = 333; lazy_static::lazy_static! { diff --git a/src/server.rs b/src/server.rs index 2951c8982..ed7315200 100644 --- a/src/server.rs +++ b/src/server.rs @@ -29,6 +29,8 @@ use std::{ mod audio_service; mod clipboard_service; +#[cfg(windows)] +pub mod cliprdr_service; mod connection; pub mod input_service; mod service; @@ -61,6 +63,8 @@ pub fn new() -> ServerPtr { server.add_service(Box::new(audio_service::new())); server.add_service(Box::new(video_service::new())); server.add_service(Box::new(clipboard_service::new())); + #[cfg(windows)] + server.add_service(Box::new(cliprdr_service::new())); server.add_service(Box::new(input_service::new_cursor())); server.add_service(Box::new(input_service::new_pos())); Arc::new(RwLock::new(server)) diff --git a/src/server/cliprdr_service.rs b/src/server/cliprdr_service.rs new file mode 100644 index 000000000..53ff10dcf --- /dev/null +++ b/src/server/cliprdr_service.rs @@ -0,0 +1,104 @@ +use super::*; +use clipboard::{create_cliprdr_context, get_rx_client_msg, server_msg, ConnID}; +use hbb_common::{ + log, + tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex as TokioMutex, + }, + tokio::time::{self, Duration, Instant}, + ResultType, +}; +use std::sync::atomic::{AtomicBool, Ordering}; + +pub use crate::common::CLIPRDR_NAME as NAME; + +lazy_static::lazy_static! { + static ref MSG_CHANNEL_SERVER: (UnboundedSender<(ConnID, Cliprdr)>, TokioMutex>) = { + let (tx, rx) = unbounded_channel(); + (tx, TokioMutex::new(rx)) + }; +} + +static RUNNING: AtomicBool = AtomicBool::new(false); + +pub fn new() -> GenericService { + let sp = GenericService::new(NAME, true); + sp.run::<_>(listen::run); + sp +} + +pub fn handle_serve_cliprdr_msg(id: i32, msg: Cliprdr) { + if RUNNING.load(Ordering::SeqCst) { + log::debug!("handle handle_serve_cliprdr_msg"); + MSG_CHANNEL_SERVER + .0 + .send(( + ConnID { + server_conn_id: id as u32, + remote_conn_id: 0, + }, + msg, + )) + .unwrap(); + } else { + // should not reach this branch + } +} + +mod listen { + use super::*; + + static WAIT: Duration = Duration::from_millis(1500); + + #[tokio::main] + pub async fn run(sp: GenericService) -> ResultType<()> { + let mut cliprdr_context = create_cliprdr_context(true, false)?; + + RUNNING.store(false, Ordering::SeqCst); + + let mut timer = time::interval_at(Instant::now() + WAIT, WAIT); + let mut client_rx = get_rx_client_msg().lock().await; + let mut server_rx = MSG_CHANNEL_SERVER.1.lock().await; + while sp.ok() { + RUNNING.store(true, Ordering::SeqCst); + + tokio::select! { + msg = client_rx.recv() => { + match msg { + Some((conn_id, msg)) => { + if conn_id.server_conn_id == 0 { + sp.send(msg) + } else { + sp.send_to(msg, conn_id.server_conn_id as i32) + } + } + None => { + unreachable!() + } + } + } + msg = server_rx.recv() => { + match msg { + Some((conn_id, msg)) => { + let res = server_msg(&mut cliprdr_context, conn_id, msg); + if res != 0 { + // log::warn!("failed to process message for {}", id); + } + } + None => { + unreachable!() + } + } + } + _ = timer.tick() => {}, + } + sp.snapshot(|_| Ok(()))?; + } + + RUNNING.store(false, Ordering::SeqCst); + log::info!("Clipboard listener stopped!"); + + Ok(()) + } +} diff --git a/src/server/connection.rs b/src/server/connection.rs index 9b8b43f25..e2461e88c 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -224,6 +224,10 @@ impl Connection { s.write().unwrap().subscribe( super::clipboard_service::NAME, conn.inner.clone(), conn.clipboard_enabled() && conn.keyboard); + #[cfg(windows)] + s.write().unwrap().subscribe( + super::cliprdr_service::NAME, + conn.inner.clone(), conn.clipboard_enabled() && conn.keyboard); } } else if &name == "audio" { conn.audio = enabled; @@ -600,6 +604,8 @@ impl Connection { } if !self.clipboard_enabled() || !self.keyboard { noperms.push(super::clipboard_service::NAME); + #[cfg(windows)] + noperms.push(super::cliprdr_service::NAME); } if !self.audio_enabled() { noperms.push(super::audio_service::NAME); @@ -824,6 +830,11 @@ impl Connection { update_clipboard(cb, None); } } + #[cfg(windows)] + Some(message::Union::cliprdr(clip)) => { + log::debug!("received cliprdr msg"); + cliprdr_service::handle_serve_cliprdr_msg(self.inner.id, clip) + } Some(message::Union::file_action(fa)) => { if self.file_transfer.is_some() { match fa.union { @@ -993,6 +1004,12 @@ impl Connection { self.inner.clone(), self.clipboard_enabled() && self.keyboard, ); + #[cfg(windows)] + s.write().unwrap().subscribe( + super::cliprdr_service::NAME, + self.inner.clone(), + self.clipboard_enabled() && self.keyboard, + ); } } } diff --git a/src/server/service.rs b/src/server/service.rs index 9c146b047..176d9fdf5 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -137,6 +137,12 @@ impl> ServiceTmpl { self.send_shared(Arc::new(msg)); } + pub fn send_to(&self, msg: Message, id: i32) { + if let Some(s) = self.0.write().unwrap().subscribes.get_mut(&id) { + s.send(Arc::new(msg)); + } + } + pub fn send_shared(&self, msg: Arc) { let mut lock = self.0.write().unwrap(); for s in lock.subscribes.values_mut() { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index b40e32a33..be26b1f57 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -2,6 +2,10 @@ use crate::client::*; use crate::common::{ self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL, }; +#[cfg(windows)] +use clipboard::{ + cliprdr::CliprdrClientContext, create_cliprdr_context, get_rx_client_msg, server_msg, ConnID, +}; use enigo::{self, Enigo, KeyboardControllable}; use hbb_common::{ allow_err, @@ -1159,6 +1163,16 @@ async fn io_loop(handler: Handler) { .as_mut() .map(|v| v.render_frame(data).ok()); }); + + #[cfg(windows)] + let cliprdr_context = match create_cliprdr_context(true, false) { + Ok(context) => Some(context), + Err(err) => { + handler.msgbox("error", "Create clipboard error", &err.to_string()); + None + } + }; + let mut remote = Remote { handler, video_sender, @@ -1172,6 +1186,10 @@ async fn io_loop(handler: Handler) { timer: time::interval(SEC30), last_update_jobs_status: (Instant::now(), Default::default()), first_frame: false, + #[cfg(windows)] + cliprdr_context, + #[cfg(windows)] + pid: std::process::id(), }; remote.io_loop().await; } @@ -1211,6 +1229,10 @@ struct Remote { timer: Interval, last_update_jobs_status: (Instant, HashMap), first_frame: bool, + #[cfg(windows)] + cliprdr_context: Option>, + #[cfg(windows)] + pid: u32, } impl Remote { @@ -1230,6 +1252,13 @@ impl Remote { } self.handler .call("setConnectionType", &make_args!(peer.is_secured(), direct)); + + // just build for now + #[cfg(not(windows))] + let (_client_tx, mut client_rx) = mpsc::unbounded_channel::(); + #[cfg(windows)] + let mut client_rx = get_rx_client_msg().lock().await; + loop { tokio::select! { res = peer.next() => { @@ -1260,6 +1289,21 @@ impl Remote { } } } + msg = client_rx.recv() => { + #[cfg(not(windows))] + println!("{:?}", msg); + #[cfg(windows)] + match msg { + Some((conn_id, msg)) => { + if conn_id.remote_conn_id == 0 || conn_id.remote_conn_id == self.pid { + allow_err!(peer.send(&msg).await); + } + } + None => { + unreachable!() + } + } + } _ = self.timer.tick() => { if last_recv_time.elapsed() >= SEC30 { self.handler.msgbox("error", "Connection Error", "Timeout"); @@ -1631,6 +1675,24 @@ impl Remote { update_clipboard(cb, Some(&self.old_clipboard)); } } + #[allow(unused_variables)] + Some(message::Union::cliprdr(clip)) => { + log::debug!("received cliprdr msg"); + #[cfg(windows)] + if !self.handler.lc.read().unwrap().disable_clipboard { + if let Some(context) = &mut self.cliprdr_context { + let res = server_msg( + context, + ConnID { + server_conn_id: 0, + remote_conn_id: self.pid, + }, + clip, + ); + log::debug!("server msg returns {}", res); + } + } + } Some(message::Union::file_response(fr)) => match fr.union { Some(file_response::Union::dir(fd)) => { let entries = fd.entries.to_vec();