diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4eb62e4..5f1e7c43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -698,6 +698,13 @@ jobs: Copy-Item (Join-Path $buildDir "*.dll") $portableDir -Force + # Bundled Amyuni usbmmidd_v2 virtual display driver (headless hosts) + $driverDir = Join-Path $buildDir "usbmmidd_v2" + if (!(Test-Path (Join-Path $driverDir "deviceinstaller64.exe"))) { + throw "Missing bundled usbmmidd_v2 driver package: $driverDir" + } + Copy-Item $driverDir (Join-Path $portableDir "usbmmidd_v2") -Recurse -Force + foreach ($file in $portableFiles) { $packagedFile = Join-Path $portableDir $file[1] if (!(Test-Path $packagedFile)) { diff --git a/README.md b/README.md index 224edfa4..fcecdc88 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,8 @@ sudo apt install "./crossdesk-linux-amd64-.deb" CrossDesk 使用 [GPL-3.0](LICENSE) 许可。隐私说明见 [隐私政策](PRIVACY.md#中文)。 +Windows 安装包内置 [Amyuni Technologies](https://www.amyuni.com/) 的 USB Mobile Monitor 虚拟显示器驱动(usbmmidd_v2),用于无显示器主机;驱动未作任何修改,其许可见安装目录 `usbmmidd_v2\License.txt`。 + ### Code signing policy CrossDesk 使用 SignPath.io 为本仓库构建的官方 Windows 发布包提供代码签名。 diff --git a/README_EN.md b/README_EN.md index 4c772f94..fbf2bcf4 100644 --- a/README_EN.md +++ b/README_EN.md @@ -205,6 +205,8 @@ Thanks to [HelloGitHub](https://hellogithub.com/), [Ruanyf Weekly](https://githu CrossDesk is licensed under [GPL-3.0](LICENSE). See the [Privacy Policy](PRIVACY.md#english). +The Windows packages bundle the unmodified USB Mobile Monitor virtual display driver (usbmmidd_v2) by [Amyuni Technologies](https://www.amyuni.com/) for hosts without a monitor; its license is in `usbmmidd_v2\License.txt` in the install directory. + ### Code signing policy CrossDesk uses SignPath.io to sign official Windows releases built from this repository. diff --git a/apps/desktop/resources/windows/usbmmidd_v2/License.txt b/apps/desktop/resources/windows/usbmmidd_v2/License.txt new file mode 100644 index 00000000..0c41396f --- /dev/null +++ b/apps/desktop/resources/windows/usbmmidd_v2/License.txt @@ -0,0 +1,12 @@ +Copyright 2014-2021 Amyuni Technologies Inc. +https://www.amyuni.com + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. +By using this software, you accept to be prompted with an advertisement page which is not always under the control of the author. A fully commercial version is available for users who do not wisth to see those advertisements. + +Permission is granted to anyone to use this software for any purpose subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the product documentation would be required. +2. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any distribution. diff --git a/apps/desktop/resources/windows/usbmmidd_v2/Win32/usbmmIdd.dll b/apps/desktop/resources/windows/usbmmidd_v2/Win32/usbmmIdd.dll new file mode 100644 index 00000000..21f88d1b Binary files /dev/null and b/apps/desktop/resources/windows/usbmmidd_v2/Win32/usbmmIdd.dll differ diff --git a/apps/desktop/resources/windows/usbmmidd_v2/deviceinstaller.exe b/apps/desktop/resources/windows/usbmmidd_v2/deviceinstaller.exe new file mode 100644 index 00000000..e9d50298 Binary files /dev/null and b/apps/desktop/resources/windows/usbmmidd_v2/deviceinstaller.exe differ diff --git a/apps/desktop/resources/windows/usbmmidd_v2/deviceinstaller64.exe b/apps/desktop/resources/windows/usbmmidd_v2/deviceinstaller64.exe new file mode 100644 index 00000000..4afc23cf Binary files /dev/null and b/apps/desktop/resources/windows/usbmmidd_v2/deviceinstaller64.exe differ diff --git a/apps/desktop/resources/windows/usbmmidd_v2/idd_instructions.txt b/apps/desktop/resources/windows/usbmmidd_v2/idd_instructions.txt new file mode 100644 index 00000000..f2c8e92d --- /dev/null +++ b/apps/desktop/resources/windows/usbmmidd_v2/idd_instructions.txt @@ -0,0 +1,50 @@ +Procedure to add a virtual monitor to your system (Windows 10 Only!) + +- Download our virtual display driver from https://www.amyuni.com/downloads/usbmmidd_v2.zip +- Unpack the zip file to an empty folder, e.g. c:\temp\usbmmidd +- Make sure you read the License.txt file as with any other software product +- Open a command prompt window as Administrator (you cannot add a device to your system unless you "Run As Administrator") +- Run the following commands: + - cd c:\temp\usbmmid (or whatever destination folder you chose) + - deviceinstaller64 install usbmmidd.inf usbmmidd (If you are on a 32-bit system, replace "deviceinstaller64" by "deviceinstaller") + - Make sure you see the message that the drivers are signed by Amyuni Technologies Inc. This is a confirmation that the drivers went through Microsoft driver signing procedure and are virus free + +- Starting with version 2, the virtual monitor supports the following display resolutions by default: + +1024 x 768 pixels +1360 x 768 pixels +1440 x 900 pixels +1600 x 900 pixels +1600 x 1200 pixels +1920 x 1080 pixels [Default Resolution] +1920 x 1200 pixels +2560 x 1440 pixels +3840 x 2160 pixels + +After the first step above is executed, you can modify the list of supported resolutions by changing the following registry key: +HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services\usbmmIdd\Parameters\Monitors + +Up to 10 resolutions can be defined through this registry key. The keys should be named from "0" to "9". The (Default) registry value defines the default monitor resolution. + +Note that the list of resolutions can also be found in the usbmmidd.inf file that accompanies the printer driver. The inf file should not be modified in any way, otherwise the virtual display driver digital signature is invalidated. + +Once you are satisfied with the list of resolutions, you can activate the virtual monitor by running: +- deviceinstaller64 enableidd 1 + +With version 2, rou can run this command multiple times to add up to 4 virtual monitors to your system. + +The included batch file usbmmidd.bat runs these commands automatically and selects between the 32- or 64-bit version of deviceinstaller. +If you are not comfortable opening a command prompt and entering these commands, right-click on usbmmidd.bat and "Run As Administrator." + +That's it. You now have a high definition monitor in your Display Settings. You can change the display resolution, position the monitor, extend the desktop or do whatever you do with a real monitor. + +To deactivate the virtual monitor, run the command: +deviceinstaller64 enableidd 0 +If ou have added more than one monitor, you should run this command multiple times to remove all monitors. + +to reactivate a virtual monitor: +deviceinstaller64 enableidd 1 + +To completely remove the drivers from your system, you can either go through the Device Manager and uninstall "USB Mobile Monitor Virtual Display", or run the commands: +deviceinstaller64 stop usbmmidd +deviceinstaller64 remove usbmmid \ No newline at end of file diff --git a/apps/desktop/resources/windows/usbmmidd_v2/usbmmIdd.inf b/apps/desktop/resources/windows/usbmmidd_v2/usbmmIdd.inf new file mode 100644 index 00000000..9a36b92f Binary files /dev/null and b/apps/desktop/resources/windows/usbmmidd_v2/usbmmIdd.inf differ diff --git a/apps/desktop/resources/windows/usbmmidd_v2/usbmmidd.bat b/apps/desktop/resources/windows/usbmmidd_v2/usbmmidd.bat new file mode 100644 index 00000000..4e5ffd61 --- /dev/null +++ b/apps/desktop/resources/windows/usbmmidd_v2/usbmmidd.bat @@ -0,0 +1,16 @@ +@cd /d "%~dp0" + +@goto %PROCESSOR_ARCHITECTURE% +@exit + +:AMD64 +@cmd /c deviceinstaller64.exe install usbmmidd.inf usbmmidd +@cmd /c deviceinstaller64.exe enableidd 1 +@goto end + +:x86 +@cmd /c deviceinstaller.exe install usbmmidd.inf usbmmidd +@cmd /c deviceinstaller.exe enableidd 1 + +:end +@pause diff --git a/apps/desktop/resources/windows/usbmmidd_v2/usbmmidd.cat b/apps/desktop/resources/windows/usbmmidd_v2/usbmmidd.cat new file mode 100644 index 00000000..b4b2e57f Binary files /dev/null and b/apps/desktop/resources/windows/usbmmidd_v2/usbmmidd.cat differ diff --git a/apps/desktop/resources/windows/usbmmidd_v2/x64/usbmmIdd.dll b/apps/desktop/resources/windows/usbmmidd_v2/x64/usbmmIdd.dll new file mode 100644 index 00000000..ce2d1c49 Binary files /dev/null and b/apps/desktop/resources/windows/usbmmidd_v2/x64/usbmmIdd.dll differ diff --git a/apps/desktop/scripts/windows/nsis_script.nsi b/apps/desktop/scripts/windows/nsis_script.nsi index 9c0556e8..e96d5a2e 100644 --- a/apps/desktop/scripts/windows/nsis_script.nsi +++ b/apps/desktop/scripts/windows/nsis_script.nsi @@ -92,6 +92,10 @@ installApp: File "..\..\..\..\build\windows\x64\release\crossdesk_privacy_window.dll" ; Bundle runtime DLLs from the release output directory File /x crossdesk_privacy_window.dll "..\..\..\..\build\windows\x64\release\*.dll" + ; Amyuni usbmmidd_v2 virtual display driver for headless hosts. xmake + ; copies apps\desktop\resources\windows\usbmmidd_v2 into the release + ; output directory after building crossdesk; its License.txt ships with it. + File /r "..\..\..\..\build\windows\x64\release\usbmmidd_v2" Call RegisterInstalledService @@ -142,6 +146,7 @@ cancelUninstall: uninstallApp: Call un.UnregisterInstalledService + Call un.RemoveUsbmmiddDriver ; Delete main executable and uninstaller Delete "$INSTDIR\CrossDesk.exe" @@ -151,6 +156,9 @@ uninstallApp: Delete "$INSTDIR\crossdesk_privacy_window.dll" Delete "$INSTDIR\uninstall.exe" + ; Leave the install directory before removing it + SetOutPath "$TEMP" + ; Recursively delete installation directory RMDir /r "$INSTDIR" @@ -248,3 +256,50 @@ unregister_with_sc: Abort ${EndIf} FunctionEnd + +; The session helper (or an elevated CrossDesk) installs the bundled usbmmidd +; driver on first headless use and records HKLM\SOFTWARE\CrossDesk\ +; UsbmmiddDriverInstalled in the 64-bit registry view. Only a driver CrossDesk +; installed is removed here; one set up by the user for other software stays. +; deviceinstaller64.exe resolves usbmmIdd.dll relative to the working +; directory, so run it from the driver folder. Failures are logged, not fatal: +; the driver can still be removed from Device Manager. +Function un.RemoveUsbmmiddDriver + IfFileExists "$INSTDIR\usbmmidd_v2\deviceinstaller64.exe" 0 done + + SetRegView 64 + ClearErrors + ReadRegDWORD $0 HKLM "Software\${PRODUCT_NAME}" "UsbmmiddDriverInstalled" + ${If} ${Errors} + ${OrIf} $0 != 1 + SetRegView lastused + Return + ${EndIf} + + DetailPrint "Removing the usbmmidd virtual display driver installed by CrossDesk" + SetOutPath "$INSTDIR\usbmmidd_v2" + ; Unplug every virtual monitor first (the driver allows up to four). + StrCpy $1 0 +unplug_loop: + nsExec::ExecToLog '"$INSTDIR\usbmmidd_v2\deviceinstaller64.exe" enableidd 0' + Pop $2 + IntOp $1 $1 + 1 + ${If} $2 = 0 + ${AndIf} $1 < 4 + Goto unplug_loop + ${EndIf} + nsExec::ExecToLog '"$INSTDIR\usbmmidd_v2\deviceinstaller64.exe" stop usbmmidd' + Pop $2 + nsExec::ExecToLog '"$INSTDIR\usbmmidd_v2\deviceinstaller64.exe" remove usbmmidd' + Pop $2 + ${If} $2 != 0 + DetailPrint "usbmmidd driver removal returned $2; remove 'USB Mobile Monitor Virtual Display' from Device Manager if it remains" + ${EndIf} + SetOutPath "$TEMP" + + DeleteRegValue HKLM "Software\${PRODUCT_NAME}" "UsbmmiddDriverInstalled" + DeleteRegKey /ifempty HKLM "Software\${PRODUCT_NAME}" + SetRegView lastused + +done: +FunctionEnd diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index f3114a48..38ad0fc6 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -43,6 +43,21 @@ function setup_targets() os.cp(runtime_dll, target:targetdir()) end + -- Amyuni usbmmidd_v2 virtual display driver for headless Windows hosts. + -- The installer and the portable archive pick it up from the target + -- directory; the runtime looks for it next to the executables. + local function copy_usbmmidd_package(target) + if not target:is_plat("windows") then + return + end + local source = path.join(os.projectdir(), "apps/desktop/resources/windows/usbmmidd_v2") + assert(os.isfile(path.join(source, "deviceinstaller64.exe")), + "usbmmidd_v2 driver package not found: " .. source) + local destination = path.join(target:targetdir(), "usbmmidd_v2") + os.tryrm(destination) + os.cp(source, destination) + end + local crossdesk_windows_resource = "apps/desktop/resources/windows/crossdesk.rc" if is_config("CROSSDESK_PORTABLE", true) then crossdesk_windows_resource = "apps/desktop/resources/windows/crossdesk_portable.rc" @@ -509,4 +524,5 @@ function setup_targets() "apps/desktop/src/platform/linux/daemon_backend.cpp") end after_build(copy_slint_runtime) + after_build(copy_usbmmidd_package) end diff --git a/docs/BUILD.md b/docs/BUILD.md index 843297c1..7aef7730 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -29,7 +29,7 @@ xmake r crossdesk .\apps\desktop\scripts\windows\pkg_x64.ps1 1.4.3 ``` -完整流程见 [Windows 脚本](../apps/desktop/scripts/windows/pkg_x64.ps1) 和 [CI 工作流](../.github/workflows/build.yml)。手动部署时需保留运行库及两个服务辅助程序,见 [Windows 服务说明](../README.md#windows-service)。 +完整流程见 [Windows 脚本](../apps/desktop/scripts/windows/pkg_x64.ps1) 和 [CI 工作流](../.github/workflows/build.yml)。手动部署时需保留运行库、两个服务辅助程序以及 `usbmmidd_v2` 驱动文件夹(构建后由 xmake 复制到输出目录),见 [Windows 服务说明](../README.md#windows-service)。 ## macOS diff --git a/docs/BUILD_EN.md b/docs/BUILD_EN.md index ce5da9ae..ab348fa5 100644 --- a/docs/BUILD_EN.md +++ b/docs/BUILD_EN.md @@ -29,7 +29,7 @@ Installer packaging also uses NSIS. Copy the repository's [nsProcess.dll](../app .\apps\desktop\scripts\windows\pkg_x64.ps1 1.4.3 ``` -See the [Windows packaging script](../apps/desktop/scripts/windows/pkg_x64.ps1) and [CI workflow](../.github/workflows/build.yml) for the full process. Manual deployment must retain the runtime libraries and both service helpers; see [Windows Service](../README_EN.md#windows-service). +See the [Windows packaging script](../apps/desktop/scripts/windows/pkg_x64.ps1) and [CI workflow](../.github/workflows/build.yml) for the full process. Manual deployment must retain the runtime libraries, both service helpers and the `usbmmidd_v2` driver folder (copied into the output directory by xmake after the build); see [Windows Service](../README_EN.md#windows-service). ## macOS diff --git a/docs/FAQ.md b/docs/FAQ.md index 6c4abc72..7e13f336 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -28,6 +28,27 @@ Wayland 画面捕获依赖启用了 Wayland 支持的构建、宿主系统 PipeW 在被控电脑检查 CrossDesk Service 是否安装、是否运行,并保持 CrossDesk 客户端运行。便携版可在“设置 → 锁屏控制服务”安装。服务状态检查命令见 [Windows 服务说明](../README.md#windows-service)。 +## 被控 Windows 电脑没有接显示器 + +没有显示器时,Windows 会把桌面放在一个分辨率固定的虚拟目标上,GPU 采集路径不可用。CrossDesk 会先尝试插入内置的 usbmmidd 虚拟显示器(见下一节);无法插入时自动进入**无屏兼容模式**:用 GDI 采集,分辨率由系统决定(常见为 1024×768),帧率和画质低于正常模式,被控端日志会记录进入兼容模式的原因。键鼠控制、隐私屏、文件传输不受影响。 + +需要完整体验时,给显卡接一个 HDMI/DP 假负载(EDID 模拟器)或真实显示器,然后重新连接;CrossDesk 会在下一次连接时重新检测并切回 GPU 采集。兼容模式只在“采集方式”为“自动”时启用;手动指定采集方式时按所选方式工作。 + +### 自动插入 usbmmidd 虚拟显示器(免假负载) + +Windows 安装包和便携包内置了 Amyuni 的 `usbmmidd_v2` 虚拟显示器驱动(位于安装目录的 `usbmmidd_v2` 文件夹,附带 Amyuni 的 License.txt)。无屏时 CrossDesk 会自动使用它,每次连接时: + +1. CrossDesk 检测到没有任何显示器且采集方式为“自动”; +2. 先以兼容模式开始传输,同时在后台通过 CrossDesk Service 的会话助手(管理员权限、交互会话内)安装驱动(仅首次,驱动带微软签名,无需测试模式)、插入一块虚拟显示器并设置为 1920×1080; +3. 虚拟显示器就绪后采集自动切到 DXGI/WGC 正常模式; +4. 会话结束时自动拔出虚拟显示器,接入真实显示器后不会残留。 + +未安装 CrossDesk Service 时,只有以管理员身份运行的 CrossDesk 才能自行插拔;否则日志会记录 `usbmmidd_installer_launch_failed:740` 并保持兼容模式。 + +卸载 CrossDesk 时,安装包会移除由 CrossDesk 安装的这块驱动;如果驱动是你为其他软件事先安装的,则保持不动。便携版不带卸载程序,需要移除时在便携目录的 `usbmmidd_v2` 下以管理员身份运行 `deviceinstaller64 enableidd 0`、`deviceinstaller64 stop usbmmidd`、`deviceinstaller64 remove usbmmidd`,或在设备管理器中卸载“USB Mobile Monitor Virtual Display”。 + +调试时不方便拔线,可以设置环境变量 `CROSSDESK_FORCE_HEADLESS=1` 再启动 CrossDesk:在没有 usbmmidd 显示器时按无屏处理,走完插屏流程后恢复真实检测。仅用于验证,不要在生产环境设置。 + ## 修改密码后连接失败 修改密码时使用 6 位数字或英文字母,并保持连接服务器。等待修改成功、客户端重新连接后,重新复制当前密码。控制端保存的旧密码会失效,按弹窗输入新密码再连接。 @@ -64,6 +85,8 @@ Wayland 画面捕获依赖启用了 Wayland 支持的构建、宿主系统 PipeW | Black screen / no input on macOS | Grant Screen Recording and Accessibility, then reopen the app. A changed app path or signature may require fresh permission grants. | | No capture on Linux Wayland | Check the Wayland build option, host PipeWire 0.3, and desktop portal permission. Compare with an X11 session. See [Linux build instructions](BUILD_EN.md#linux). | | Cannot control a Windows lock screen | Install/start CrossDesk Service and keep the host client running. See [service commands](../README_EN.md#windows-service). | +| Windows host has no monitor attached | The Windows installer and portable archive bundle Amyuni's `usbmmidd_v2` virtual display driver (in the `usbmmidd_v2` folder with its License.txt). On each connection to a host without a monitor (capture method Auto), CrossDesk starts streaming in compatibility mode and, in the background, asks the CrossDesk Service session helper to install the Microsoft-signed driver (first time only) and plug one virtual monitor at 1920×1080; capture then switches to DXGI/WGC on that monitor and it is unplugged when the session ends. Without the service only an elevated CrossDesk can plug it itself; otherwise the host stays in **headless compatibility mode**: GDI capture at an OS-fixed resolution (often 1024×768) with reduced frame rate; the host log records why. Input, privacy screen and file transfer still work. A real monitor or an HDMI/DP dummy plug also restores GPU capture. | +| Removing the bundled virtual display driver | The uninstaller removes the driver when CrossDesk installed it and leaves a driver you installed yourself. For the portable build run `deviceinstaller64 enableidd 0`, `deviceinstaller64 stop usbmmidd` and `deviceinstaller64 remove usbmmidd` as administrator from the `usbmmidd_v2` folder, or uninstall "USB Mobile Monitor Virtual Display" in Device Manager. | | Saved password no longer works | Use 6 ASCII letters or digits when changing the password, with the host connected to the server. Wait for the change and reconnection to complete, then copy the current password and enter it on the controller. | | Cannot find received files | Check Settings → File Save Path on desktop. Native iOS stores them in `Documents/Received` and provides a share action. | | Main window closed but app still running | Closing hides the window. Use the tray/menu-bar exit command to quit. |