feat: remote printer (#11231)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-03-27 15:34:27 +08:00
committed by GitHub
parent 1cb53c1f7a
commit f4bbf82363
101 changed files with 3707 additions and 211 deletions

View File

@@ -159,14 +159,44 @@ jobs:
- name: Build rustdesk
run: |
# Windows: build RustDesk
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack
mv ./flutter/build/windows/x64/runner/Release ./rustdesk
# Download usbmmidd_v2.zip and extract it to ./rustdesk
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
Expand-Archive usbmmidd_v2.zip -DestinationPath .
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack
Remove-Item -Path usbmmidd_v2\Win32 -Recurse
Remove-Item -Path "usbmmidd_v2\deviceinstaller64.exe", "usbmmidd_v2\deviceinstaller.exe", "usbmmidd_v2\usbmmidd.bat"
mv ./flutter/build/windows/x64/runner/Release ./rustdesk
mv -Force .\usbmmidd_v2 ./rustdesk
# Download printer driver files and extract them to ./rustdesk
try {
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/rustdesk_printer_driver_v4.zip -OutFile rustdesk_printer_driver_v4.zip
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/printer_driver_adapter.zip -OutFile printer_driver_adapter.zip
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/sha256sums -OutFile sha256sums
# Check and move the files
$checksum_driver = (Select-String -Path .\sha256sums -Pattern '^([a-fA-F0-9]{64}) \*rustdesk_printer_driver_v4\.zip$').Matches.Groups[1].Value
$downloadsum_driver = Get-FileHash -Path rustdesk_printer_driver_v4.zip -Algorithm SHA256
$checksum_dll = (Select-String -Path .\sha256sums -Pattern '^([a-fA-F0-9]{64}) \*printer_driver_adapter\.zip$').Matches.Groups[1].Value
$downloadsum_dll = Get-FileHash -Path printer_driver_adapter.zip -Algorithm SHA256
if ($checksum_driver -eq $downloadsum_driver.Hash -and $checksum_dll -eq $downloadsum_dll.Hash) {
Write-Output "rustdesk_printer_driver_v4, checksums match, extract the file."
Expand-Archive rustdesk_printer_driver_v4.zip -DestinationPath .
mkdir ./rustdesk/drivers
mv -Force .\rustdesk_printer_driver_v4 ./rustdesk/drivers/RustDeskPrinterDriver
Expand-Archive printer_driver_adapter.zip -DestinationPath .
mv -Force .\printer_driver_adapter.dll ./rustdesk
} elseif ($checksum_driver -ne $downloadsum_driver.Hash) {
Write-Output "rustdesk_printer_driver_v4, checksums do not match, ignore the file."
} else {
Write-Output "printer_driver_adapter.dll, checksums do not match, ignore the file."
}
} catch {
Write-Host "Ingore the printer driver error."
}
- name: find Runner.res
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res
# Runner.rc does not contain actual version, but Runner.res does

25
Cargo.lock generated
View File

@@ -5559,6 +5559,15 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "remote_printer"
version = "0.1.0"
dependencies = [
"hbb_common",
"winapi 0.3.9",
"windows-strings",
]
[[package]]
name = "repng"
version = "0.2.2"
@@ -5805,6 +5814,7 @@ dependencies = [
"percent-encoding",
"qrcode-generator",
"rdev",
"remote_printer",
"repng",
"reqwest",
"ringbuf",
@@ -7833,6 +7843,12 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.1.2"
@@ -7853,6 +7869,15 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "windows-strings"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@@ -116,10 +116,12 @@ winapi = { version = "0.3", features = [
"cguid",
"cfgmgr32",
"ioapiset",
"winspool",
] }
winreg = "0.11"
windows-service = "0.6"
virtual_display = { path = "libs/virtual_display" }
remote_printer = { path = "libs/remote_printer" }
impersonate_system = { git = "https://github.com/rustdesk-org/impersonate-system" }
shared_memory = "0.12"
tauri-winrt-notification = "0.1.2"
@@ -177,7 +179,7 @@ jni = "0.21"
android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable"]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"]
exclude = ["vdi/host", "examples/custom_plugin"]
[package.metadata.winres]

View File

@@ -3789,3 +3789,29 @@ void updateTextAndPreserveSelection(
baseOffset: 0, extentOffset: controller.value.text.length);
}
}
List<String> getPrinterNames() {
final printerNamesJson = bind.mainGetPrinterNames();
if (printerNamesJson.isEmpty) {
return [];
}
try {
final List<dynamic> printerNamesList = jsonDecode(printerNamesJson);
final appPrinterName = '$appName Printer';
return printerNamesList
.map((e) => e.toString())
.where((name) => name != appPrinterName)
.toList();
} catch (e) {
debugPrint('failed to parse printer names, err: $e');
return [];
}
}
String _appName = '';
String get appName {
if (_appName.isEmpty) {
_appName = bind.mainGetAppNameSync();
}
return _appName;
}

View File

@@ -4,7 +4,6 @@ import 'dart:convert';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/consts.dart';

View File

@@ -98,6 +98,7 @@ const String kOptionVideoSaveDirectory = "video-save-directory";
const String kOptionAccessMode = "access-mode";
const String kOptionEnableKeyboard = "enable-keyboard";
// "Settings -> Security -> Permissions"
const String kOptionEnableRemotePrinter = "enable-remote-printer";
const String kOptionEnableClipboard = "enable-clipboard";
const String kOptionEnableFileTransfer = "enable-file-transfer";
const String kOptionEnableAudio = "enable-audio";
@@ -219,6 +220,14 @@ const double kDefaultQuality = 50;
const double kMaxQuality = 100;
const double kMaxMoreQuality = 2000;
const String kKeyPrinterIncommingJobAction = 'printer-incomming-job-action';
const String kValuePrinterIncomingJobDismiss = 'dismiss';
const String kValuePrinterIncomingJobDefault = '';
const String kValuePrinterIncomingJobSelected = 'selected';
const String kKeyPrinterSelected = 'printer-selected-name';
const String kKeyPrinterSave = 'allow-printer-dialog-save';
const String kKeyPrinterAllowAutoPrint = 'allow-printer-auto-print';
double kNewWindowOffset = isWindows
? 56.0
: isLinux

View File

@@ -13,6 +13,7 @@ import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/printer_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/plugin/manager.dart';
@@ -55,6 +56,7 @@ enum SettingsTabKey {
display,
plugin,
account,
printer,
about,
}
@@ -74,6 +76,7 @@ class DesktopSettingPage extends StatefulWidget {
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
SettingsTabKey.plugin,
if (!bind.isDisableAccount()) SettingsTabKey.account,
if (isWindows) SettingsTabKey.printer,
SettingsTabKey.about,
];
@@ -198,6 +201,10 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
settingTabs.add(
_TabInfo(tab, 'Account', Icons.person_outline, Icons.person));
break;
case SettingsTabKey.printer:
settingTabs
.add(_TabInfo(tab, 'Printer', Icons.print_outlined, Icons.print));
break;
case SettingsTabKey.about:
settingTabs
.add(_TabInfo(tab, 'About', Icons.info_outline, Icons.info));
@@ -229,6 +236,9 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
case SettingsTabKey.account:
children.add(const _Account());
break;
case SettingsTabKey.printer:
children.add(const _Printer());
break;
case SettingsTabKey.about:
children.add(const _About());
break;
@@ -963,6 +973,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
_OptionCheckBox(
context, 'Enable keyboard/mouse', kOptionEnableKeyboard,
enabled: enabled, fakeValue: fakeValue),
if (isWindows)
_OptionCheckBox(
context, 'Enable remote printer', kOptionEnableRemotePrinter,
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable clipboard', kOptionEnableClipboard,
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
@@ -1881,6 +1895,153 @@ class _PluginState extends State<_Plugin> {
}
}
class _Printer extends StatefulWidget {
const _Printer({super.key});
@override
State<_Printer> createState() => __PrinterState();
}
class __PrinterState extends State<_Printer> {
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
return ListView(controller: scrollController, children: [
outgoing(context),
incomming(context),
]).marginOnly(bottom: _kListViewBottomMargin);
}
Widget outgoing(BuildContext context) {
final isSupportPrinterDriver =
bind.mainGetCommonSync(key: 'is-support-printer-driver') == 'true';
Widget tipOsNotSupported() {
return Align(
alignment: Alignment.topLeft,
child: Text(translate('printer-os-requirement-tip')),
).marginOnly(left: _kCardLeftMargin);
}
Widget tipClientNotInstalled() {
return Align(
alignment: Alignment.topLeft,
child:
Text(translate('printer-requires-installed-{$appName}-client-tip')),
).marginOnly(left: _kCardLeftMargin);
}
Widget tipPrinterNotInstalled() {
final failedMsg = ''.obs;
platformFFI.registerEventHandler(
'install-printer-res', 'install-printer-res', (evt) async {
if (evt['success'] as bool) {
setState(() {});
} else {
failedMsg.value = evt['msg'] as String;
}
}, replace: true);
return Column(children: [
Obx(
() => failedMsg.value.isNotEmpty
? Offstage()
: Align(
alignment: Alignment.topLeft,
child: Text(translate('printer-{$appName}-not-installed-tip'))
.marginOnly(bottom: 10.0),
),
),
Obx(
() => failedMsg.value.isEmpty
? Offstage()
: Align(
alignment: Alignment.topLeft,
child: Text(failedMsg.value,
style: DefaultTextStyle.of(context)
.style
.copyWith(color: Colors.red))
.marginOnly(bottom: 10.0)),
),
_Button('Install {$appName} Printer', () {
failedMsg.value = '';
bind.mainSetCommon(key: 'install-printer', value: '');
})
]).marginOnly(left: _kCardLeftMargin, bottom: 2.0);
}
Widget tipReady() {
return Align(
alignment: Alignment.topLeft,
child: Text(translate('printer-{$appName}-ready-tip')),
).marginOnly(left: _kCardLeftMargin);
}
final installed = bind.mainIsInstalled();
// `is-printer-installed` may fail, but it's rare case.
// Add additional error message here if it's really needed.
final driver_installed =
bind.mainGetCommonSync(key: 'is-printer-installed') == 'true';
final List<Widget> children = [];
if (!isSupportPrinterDriver) {
children.add(tipOsNotSupported());
} else {
children.addAll([
if (!installed) tipClientNotInstalled(),
if (installed && !driver_installed) tipPrinterNotInstalled(),
if (installed && driver_installed) tipReady()
]);
}
return _Card(title: 'Outgoing Print Jobs', children: children);
}
Widget incomming(BuildContext context) {
onRadioChanged(String value) async {
await bind.mainSetLocalOption(
key: kKeyPrinterIncommingJobAction, value: value);
setState(() {});
}
PrinterOptions printerOptions = PrinterOptions.load();
return _Card(title: 'Incomming Print Jobs', children: [
_Radio(context,
value: kValuePrinterIncomingJobDismiss,
groupValue: printerOptions.action,
label: 'Dismiss',
onChanged: onRadioChanged),
_Radio(context,
value: kValuePrinterIncomingJobDefault,
groupValue: printerOptions.action,
label: 'use-the-default-printer-tip',
onChanged: onRadioChanged),
_Radio(context,
value: kValuePrinterIncomingJobSelected,
groupValue: printerOptions.action,
label: 'use-the-selected-printer-tip',
onChanged: onRadioChanged),
if (printerOptions.printerNames.isNotEmpty)
ComboBox(
initialKey: printerOptions.printerName,
keys: printerOptions.printerNames,
values: printerOptions.printerNames,
enabled: printerOptions.action == kValuePrinterIncomingJobSelected,
onChanged: (value) async {
await bind.mainSetLocalOption(
key: kKeyPrinterSelected, value: value);
setState(() {});
},
).marginOnly(left: 10),
_OptionCheckBox(
context,
'auto-print-tip',
kKeyPrinterAllowAutoPrint,
isServer: false,
enabled: printerOptions.action != kValuePrinterIncomingJobDismiss,
)
]);
}
}
class _About extends StatefulWidget {
const _About({Key? key}) : super(key: key);

View File

@@ -65,6 +65,7 @@ class _InstallPageBodyState extends State<_InstallPageBody>
late final TextEditingController controller;
final RxBool startmenu = true.obs;
final RxBool desktopicon = true.obs;
final RxBool printer = true.obs;
final RxBool showProgress = false.obs;
final RxBool btnEnabled = true.obs;
@@ -79,6 +80,7 @@ class _InstallPageBodyState extends State<_InstallPageBody>
final installOptions = jsonDecode(bind.installInstallOptions());
startmenu.value = installOptions['STARTMENUSHORTCUTS'] != '0';
desktopicon.value = installOptions['DESKTOPSHORTCUTS'] != '0';
printer.value = installOptions['PRINTER'] != '0';
}
@override
@@ -161,7 +163,9 @@ class _InstallPageBodyState extends State<_InstallPageBody>
).marginSymmetric(vertical: 2 * em),
Option(startmenu, label: 'Create start menu shortcuts')
.marginOnly(bottom: 7),
Option(desktopicon, label: 'Create desktop icon'),
Option(desktopicon, label: 'Create desktop icon')
.marginOnly(bottom: 7),
Option(printer, label: 'Install {$appName} Printer'),
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
@@ -253,6 +257,7 @@ class _InstallPageBodyState extends State<_InstallPageBody>
String args = '';
if (startmenu.value) args += ' startmenu';
if (desktopicon.value) args += ' desktopicon';
if (printer.value) args += ' printer';
bind.installInstallMe(options: args, path: controller.text);
}

View File

@@ -30,9 +30,16 @@ enum SortBy {
class JobID {
int _count = 0;
int next() {
String v = bind.mainGetCommonSync(key: 'transfer-job-id');
try {
return int.parse(v);
} catch (e) {
// unreachable. But we still handle it to make it safe.
// If we return -1, we have to check it in the caller.
_count++;
return _count;
}
}
}
typedef GetSessionID = SessionID Function();

View File

@@ -9,7 +9,6 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/ab_model.dart';
@@ -19,6 +18,7 @@ import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_hbb/models/group_model.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:flutter_hbb/models/printer_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
@@ -412,12 +412,186 @@ class FfiModel with ChangeNotifier {
isMobile) {
parent.target?.recordingModel.updateStatus(evt['start'] == 'true');
}
} else if (name == "printer_request") {
_handlePrinterRequest(evt, sessionId, peerId);
} else {
debugPrint('Event is not handled in the fixed branch: $name');
}
};
}
_handlePrinterRequest(
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
final id = evt['id'];
final path = evt['path'];
final dialogManager = parent.target!.dialogManager;
dialogManager.show((setState, close, context) {
PrinterOptions printerOptions = PrinterOptions.load();
final saveSettings = mainGetLocalBoolOptionSync(kKeyPrinterSave).obs;
final dontShowAgain = false.obs;
final Rx<String> selectedPrinterName = printerOptions.printerName.obs;
final printerNames = printerOptions.printerNames;
final defaultOrSelectedGroupValue =
(printerOptions.action == kValuePrinterIncomingJobDismiss
? kValuePrinterIncomingJobDefault
: printerOptions.action)
.obs;
onRatioChanged(String? value) {
defaultOrSelectedGroupValue.value =
value ?? kValuePrinterIncomingJobDefault;
}
onSubmit() {
final printerName = defaultOrSelectedGroupValue.isEmpty
? ''
: selectedPrinterName.value;
bind.sessionPrinterResponse(
sessionId: sessionId, id: id, path: path, printerName: printerName);
if (saveSettings.value || dontShowAgain.value) {
bind.mainSetLocalOption(key: kKeyPrinterSelected, value: printerName);
bind.mainSetLocalOption(
key: kKeyPrinterIncommingJobAction,
value: defaultOrSelectedGroupValue.value);
}
if (dontShowAgain.value) {
mainSetLocalBoolOption(kKeyPrinterAllowAutoPrint, true);
}
close();
}
onCancel() {
if (dontShowAgain.value) {
bind.mainSetLocalOption(
key: kKeyPrinterIncommingJobAction,
value: kValuePrinterIncomingJobDismiss);
}
close();
}
final printerItemHeight = 30.0;
final selectionAreaHeight =
printerItemHeight * min(8.0, max(printerNames.length, 3.0));
final content = Column(
children: [
Text(translate('print-incoming-job-confirm-tip')),
Row(
children: [
Obx(() => Radio<String>(
value: kValuePrinterIncomingJobDefault,
groupValue: defaultOrSelectedGroupValue.value,
onChanged: onRatioChanged)),
GestureDetector(
child: Text(translate('use-the-default-printer-tip')),
onTap: () => onRatioChanged(kValuePrinterIncomingJobDefault)),
],
),
Column(
children: [
Row(children: [
Obx(() => Radio<String>(
value: kValuePrinterIncomingJobSelected,
groupValue: defaultOrSelectedGroupValue.value,
onChanged: onRatioChanged)),
GestureDetector(
child: Text(translate('use-the-selected-printer-tip')),
onTap: () =>
onRatioChanged(kValuePrinterIncomingJobSelected)),
]),
SizedBox(
height: selectionAreaHeight,
width: 500,
child: ListView.builder(
itemBuilder: (context, index) {
return Obx(() => GestureDetector(
child: Container(
decoration: BoxDecoration(
color: selectedPrinterName.value ==
printerNames[index]
? (defaultOrSelectedGroupValue.value ==
kValuePrinterIncomingJobSelected
? MyTheme.button
: MyTheme.button.withOpacity(0.5))
: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
key: ValueKey(printerNames[index]),
height: printerItemHeight,
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
printerNames[index],
style: TextStyle(fontSize: 14),
),
),
),
),
onTap: defaultOrSelectedGroupValue.value ==
kValuePrinterIncomingJobSelected
? () {
selectedPrinterName.value =
printerNames[index];
}
: null,
));
},
itemCount: printerNames.length),
),
],
),
Row(
children: [
Obx(() => Checkbox(
value: saveSettings.value,
onChanged: (value) {
if (value != null) {
saveSettings.value = value;
mainSetLocalBoolOption(kKeyPrinterSave, value);
}
})),
GestureDetector(
child: Text(translate('save-settings-tip')),
onTap: () {
saveSettings.value = !saveSettings.value;
mainSetLocalBoolOption(kKeyPrinterSave, saveSettings.value);
}),
],
),
Row(
children: [
Obx(() => Checkbox(
value: dontShowAgain.value,
onChanged: (value) {
if (value != null) {
dontShowAgain.value = value;
}
})),
GestureDetector(
child: Text(translate('dont-show-again-tip')),
onTap: () {
dontShowAgain.value = !dontShowAgain.value;
}),
],
),
],
);
return CustomAlertDialog(
title: Text(translate('Incoming Print Job')),
content: content,
actions: [
dialogButton('OK', onPressed: onSubmit),
dialogButton('Cancel', onPressed: onCancel),
],
onSubmit: onSubmit,
onCancel: onCancel,
);
});
}
_handleUseTextureRender(
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
parent.target?.imageModel.setUseTextureRender(evt['v'] == 'Y');

View File

@@ -60,14 +60,14 @@ class PlatformFFI {
}
bool registerEventHandler(
String eventName, String handlerName, HandleEvent handler) {
String eventName, String handlerName, HandleEvent handler, {bool replace = false}) {
debugPrint('registerEventHandler $eventName $handlerName');
var handlers = _eventHandlers[eventName];
if (handlers == null) {
_eventHandlers[eventName] = {handlerName: handler};
return true;
} else {
if (handlers.containsKey(handlerName)) {
if (!replace && handlers.containsKey(handlerName)) {
return false;
} else {
handlers[handlerName] = handler;

View File

@@ -0,0 +1,48 @@
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/platform_model.dart';
class PrinterOptions {
String action;
List<String> printerNames;
String printerName;
PrinterOptions(
{required this.action,
required this.printerNames,
required this.printerName});
static PrinterOptions load() {
var action = bind.mainGetLocalOption(key: kKeyPrinterIncommingJobAction);
if (![
kValuePrinterIncomingJobDismiss,
kValuePrinterIncomingJobDefault,
kValuePrinterIncomingJobSelected
].contains(action)) {
action = kValuePrinterIncomingJobDefault;
}
final printerNames = getPrinterNames();
var selectedPrinterName = bind.mainGetLocalOption(key: kKeyPrinterSelected);
if (!printerNames.contains(selectedPrinterName)) {
if (action == kValuePrinterIncomingJobSelected) {
action = kValuePrinterIncomingJobDefault;
bind.mainSetLocalOption(
key: kKeyPrinterIncommingJobAction,
value: kValuePrinterIncomingJobDefault);
if (printerNames.isEmpty) {
selectedPrinterName = '';
} else {
selectedPrinterName = printerNames.first;
}
bind.mainSetLocalOption(
key: kKeyPrinterSelected, value: selectedPrinterName);
}
}
return PrinterOptions(
action: action,
printerNames: printerNames,
printerName: selectedPrinterName);
}
}

View File

@@ -0,0 +1,11 @@
[package]
name = "remote_printer"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[target.'cfg(target_os = "windows")'.dependencies]
hbb_common = { version = "0.1.0", path = "../hbb_common" }
winapi = { version = "0.3" }
windows-strings = "0.3.1"

View File

@@ -0,0 +1,34 @@
#[cfg(target_os = "windows")]
mod setup;
#[cfg(target_os = "windows")]
pub use setup::{
is_rd_printer_installed,
setup::{install_update_printer, uninstall_printer},
};
#[cfg(target_os = "windows")]
const RD_DRIVER_INF_PATH: &str = "drivers/RustDeskPrinterDriver/RustDeskPrinterDriver.inf";
#[cfg(target_os = "windows")]
fn get_printer_name(app_name: &str) -> Vec<u16> {
format!("{} Printer", app_name)
.encode_utf16()
.chain(Some(0))
.collect()
}
#[cfg(target_os = "windows")]
fn get_driver_name() -> Vec<u16> {
"RustDesk v4 Printer Driver"
.encode_utf16()
.chain(Some(0))
.collect()
}
#[cfg(target_os = "windows")]
fn get_port_name(app_name: &str) -> Vec<u16> {
format!("{} Printer", app_name)
.encode_utf16()
.chain(Some(0))
.collect()
}

View File

@@ -0,0 +1,202 @@
use super::{common_enum, get_wstr_bytes, is_name_equal};
use hbb_common::{bail, log, ResultType};
use std::{io, ptr::null_mut, time::Duration};
use winapi::{
shared::{
minwindef::{BOOL, DWORD, FALSE, LPBYTE, LPDWORD, MAX_PATH},
ntdef::{DWORDLONG, LPCWSTR},
winerror::{ERROR_UNKNOWN_PRINTER_DRIVER, S_OK},
},
um::{
winspool::{
DeletePrinterDriverExW, DeletePrinterDriverPackageW, EnumPrinterDriversW,
InstallPrinterDriverFromPackageW, UploadPrinterDriverPackageW, DPD_DELETE_ALL_FILES,
DRIVER_INFO_6W, DRIVER_INFO_8W, IPDFP_COPY_ALL_FILES, UPDP_SILENT_UPLOAD,
UPDP_UPLOAD_ALWAYS,
},
winuser::GetForegroundWindow,
},
};
use windows_strings::PCWSTR;
const HRESULT_ERR_ELEMENT_NOT_FOUND: u32 = 0x80070490;
fn enum_printer_driver(
level: DWORD,
p_driver_info: LPBYTE,
cb_buf: DWORD,
pcb_needed: LPDWORD,
pc_returned: LPDWORD,
) -> BOOL {
unsafe {
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinterdrivers
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
EnumPrinterDriversW(
null_mut(),
null_mut(),
level,
p_driver_info,
cb_buf,
pcb_needed,
pc_returned,
)
}
}
pub fn get_installed_driver_version(name: &PCWSTR) -> ResultType<Option<DWORDLONG>> {
common_enum(
"EnumPrinterDriversW",
enum_printer_driver,
6,
|info: &DRIVER_INFO_6W| {
if is_name_equal(name, info.pName) {
Some(info.dwlDriverVersion)
} else {
None
}
},
|| None,
)
}
fn find_inf(name: &PCWSTR) -> ResultType<Vec<u16>> {
let r = common_enum(
"EnumPrinterDriversW",
enum_printer_driver,
8,
|info: &DRIVER_INFO_8W| {
if is_name_equal(name, info.pName) {
Some(get_wstr_bytes(info.pszInfPath))
} else {
None
}
},
|| None,
)?;
Ok(r.unwrap_or(vec![]))
}
fn delete_printer_driver(name: &PCWSTR) -> ResultType<()> {
unsafe {
// If the printer is used after the spooler service is started. E.g., printing a document through RustDesk Printer.
// `DeletePrinterDriverExW()` may fail with `ERROR_PRINTER_DRIVER_IN_USE`(3001, 0xBB9).
// We can only ignore this error for now.
// Though restarting the spooler service is a solution, it's not a good idea to restart the service.
//
// Deleting the printer driver after deleting the printer is a common practice.
// No idea why `DeletePrinterDriverExW()` fails with `ERROR_UNKNOWN_PRINTER_DRIVER` after using the printer once.
// https://github.com/ChromiumWebApps/chromium/blob/c7361d39be8abd1574e6ce8957c8dbddd4c6ccf7/cloud_print/virtual_driver/win/install/setup.cc#L422
// AnyDesk printer driver and the simplest printer driver also have the same issue.
if FALSE
== DeletePrinterDriverExW(
null_mut(),
null_mut(),
name.as_ptr() as _,
DPD_DELETE_ALL_FILES,
0,
)
{
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_UNKNOWN_PRINTER_DRIVER as _) {
return Ok(());
} else {
bail!("Failed to delete the printer driver, {}", err)
}
}
}
Ok(())
}
// https://github.com/dvalter/chromium-android-ext-dev/blob/dab74f7d5bc5a8adf303090ee25c611b4d54e2db/cloud_print/virtual_driver/win/install/setup.cc#L190
fn delete_printer_driver_package(inf: Vec<u16>) -> ResultType<()> {
if inf.is_empty() {
return Ok(());
}
let slen = if inf[inf.len() - 1] == 0 {
inf.len() - 1
} else {
inf.len()
};
let inf_path = String::from_utf16_lossy(&inf[..slen]);
if !std::path::Path::new(&inf_path).exists() {
return Ok(());
}
let mut retries = 3;
loop {
unsafe {
let res = DeletePrinterDriverPackageW(null_mut(), inf.as_ptr(), null_mut());
if res == S_OK || res == HRESULT_ERR_ELEMENT_NOT_FOUND as i32 {
return Ok(());
}
log::error!("Failed to delete the printer driver, result: {}", res);
}
retries -= 1;
if retries <= 0 {
bail!("Failed to delete the printer driver");
}
std::thread::sleep(Duration::from_secs(2));
}
}
pub fn uninstall_driver(name: &PCWSTR) -> ResultType<()> {
// Note: inf must be found before `delete_printer_driver()`.
let inf = find_inf(name)?;
delete_printer_driver(name)?;
delete_printer_driver_package(inf)
}
pub fn install_driver(name: &PCWSTR, inf: LPCWSTR) -> ResultType<()> {
let mut size = (MAX_PATH * 10) as u32;
let mut package_path = [0u16; MAX_PATH * 10];
unsafe {
let mut res = UploadPrinterDriverPackageW(
null_mut(),
inf,
null_mut(),
UPDP_SILENT_UPLOAD | UPDP_UPLOAD_ALWAYS,
null_mut(),
package_path.as_mut_ptr(),
&mut size as _,
);
if res != S_OK {
log::error!(
"Failed to upload the printer driver package to the driver cache silently, {}. Will try with user UI.",
res
);
res = UploadPrinterDriverPackageW(
null_mut(),
inf,
null_mut(),
UPDP_UPLOAD_ALWAYS,
GetForegroundWindow(),
package_path.as_mut_ptr(),
&mut size as _,
);
if res != S_OK {
bail!(
"Failed to upload the printer driver package to the driver cache with UI, {}",
res
);
}
}
// https://learn.microsoft.com/en-us/windows/win32/printdocs/installprinterdriverfrompackage
res = InstallPrinterDriverFromPackageW(
null_mut(),
package_path.as_ptr(),
name.as_ptr(),
null_mut(),
IPDFP_COPY_ALL_FILES,
);
if res != S_OK {
bail!("Failed to install the printer driver from package, {}", res);
}
}
Ok(())
}

View File

@@ -0,0 +1,99 @@
use hbb_common::{bail, ResultType};
use std::{io, ptr::null_mut};
use winapi::{
shared::{
minwindef::{BOOL, DWORD, FALSE, LPBYTE, LPDWORD},
ntdef::{LPCWSTR, LPWSTR},
},
um::winbase::{lstrcmpiW, lstrlenW},
};
use windows_strings::PCWSTR;
mod driver;
mod port;
pub(crate) mod printer;
pub(crate) mod setup;
#[inline]
pub fn is_rd_printer_installed(app_name: &str) -> ResultType<bool> {
let printer_name = crate::get_printer_name(app_name);
let rd_printer_name = PCWSTR::from_raw(printer_name.as_ptr());
printer::is_printer_added(&rd_printer_name)
}
fn get_wstr_bytes(p: LPWSTR) -> Vec<u16> {
let mut vec_bytes = vec![];
unsafe {
let len: isize = lstrlenW(p) as _;
if len > 0 {
for i in 0..len + 1 {
vec_bytes.push(*p.offset(i));
}
}
}
vec_bytes
}
fn is_name_equal(name: &PCWSTR, name_from_api: LPCWSTR) -> bool {
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcmpiw
// For some locales, the lstrcmpi function may be insufficient.
// If this occurs, use `CompareStringEx` to ensure proper comparison.
// For example, in Japan call with the NORM_IGNORECASE, NORM_IGNOREKANATYPE, and NORM_IGNOREWIDTH values to achieve the most appropriate non-exact string comparison.
// Note that specifying these values slows performance, so use them only when necessary.
//
// No need to consider `CompareStringEx` for now.
unsafe { lstrcmpiW(name.as_ptr(), name_from_api) == 0 }
}
fn common_enum<T, R: Sized>(
enum_name: &str,
enum_fn: fn(
Level: DWORD,
pDriverInfo: LPBYTE,
cbBuf: DWORD,
pcbNeeded: LPDWORD,
pcReturned: LPDWORD,
) -> BOOL,
level: DWORD,
on_data: impl Fn(&T) -> Option<R>,
on_no_data: impl Fn() -> Option<R>,
) -> ResultType<Option<R>> {
let mut needed = 0;
let mut returned = 0;
enum_fn(level, null_mut(), 0, &mut needed, &mut returned);
if needed == 0 {
return Ok(on_no_data());
}
let mut buffer = vec![0u8; needed as usize];
if FALSE
== enum_fn(
level,
buffer.as_mut_ptr(),
needed,
&mut needed,
&mut returned,
)
{
bail!(
"Failed to call {}, error: {}",
enum_name,
io::Error::last_os_error()
)
}
// to-do: how to free the buffers in *const T?
let p_enum_info = buffer.as_ptr() as *const T;
unsafe {
for i in 0..returned {
let enum_info = p_enum_info.offset(i as isize);
let r = on_data(&*enum_info);
if r.is_some() {
return Ok(r);
}
}
}
Ok(on_no_data())
}

View File

@@ -0,0 +1,128 @@
use super::{common_enum, is_name_equal, printer::get_printer_installed_on_port};
use hbb_common::{bail, ResultType};
use std::{io, ptr::null_mut};
use winapi::{
shared::minwindef::{BOOL, DWORD, FALSE, LPBYTE, LPDWORD},
um::{
winnt::HANDLE,
winspool::{
ClosePrinter, EnumPortsW, OpenPrinterW, XcvDataW, PORT_INFO_2W, PRINTER_DEFAULTSW,
SERVER_WRITE,
},
},
};
use windows_strings::{w, PCWSTR};
const XCV_MONITOR_LOCAL_PORT: PCWSTR = w!(",XcvMonitor Local Port");
fn enum_printer_port(
level: DWORD,
p_port_info: LPBYTE,
cb_buf: DWORD,
pcb_needed: LPDWORD,
pc_returned: LPDWORD,
) -> BOOL {
unsafe {
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumports
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
EnumPortsW(
null_mut(),
level,
p_port_info,
cb_buf,
pcb_needed,
pc_returned,
)
}
}
fn is_port_exists(name: &PCWSTR) -> ResultType<bool> {
let r = common_enum(
"EnumPortsW",
enum_printer_port,
2,
|info: &PORT_INFO_2W| {
if is_name_equal(name, info.pPortName) {
Some(true)
} else {
None
}
},
|| None,
)?;
Ok(r.unwrap_or(false))
}
unsafe fn execute_on_local_port(port: &PCWSTR, command: &PCWSTR) -> ResultType<()> {
let mut dft = PRINTER_DEFAULTSW {
pDataType: null_mut(),
pDevMode: null_mut(),
DesiredAccess: SERVER_WRITE,
};
let mut h_monitor: HANDLE = null_mut();
if FALSE
== OpenPrinterW(
XCV_MONITOR_LOCAL_PORT.as_ptr() as _,
&mut h_monitor,
&mut dft as *mut PRINTER_DEFAULTSW as _,
)
{
bail!(format!(
"Failed to open Local Port monitor. Error: {}",
io::Error::last_os_error()
))
}
let mut output_needed: u32 = 0;
let mut status: u32 = 0;
if FALSE
== XcvDataW(
h_monitor,
command.as_ptr(),
port.as_ptr() as *mut u8,
(port.len() + 1) as u32 * 2,
null_mut(),
0,
&mut output_needed,
&mut status,
)
{
ClosePrinter(h_monitor);
bail!(format!(
"Failed to execute the command on the printer port, Error: {}",
io::Error::last_os_error()
))
}
ClosePrinter(h_monitor);
Ok(())
}
fn add_local_port(port: &PCWSTR) -> ResultType<()> {
unsafe { execute_on_local_port(port, &w!("AddPort")) }
}
fn delete_local_port(port: &PCWSTR) -> ResultType<()> {
unsafe { execute_on_local_port(port, &w!("DeletePort")) }
}
pub fn check_add_local_port(port: &PCWSTR) -> ResultType<()> {
if !is_port_exists(port)? {
return add_local_port(port);
}
Ok(())
}
pub fn check_delete_local_port(port: &PCWSTR) -> ResultType<()> {
if is_port_exists(port)? {
if get_printer_installed_on_port(port)?.is_some() {
bail!("The printer is installed on the port. Please remove the printer first.");
}
return delete_local_port(port);
}
Ok(())
}

View File

@@ -0,0 +1,161 @@
use super::{common_enum, get_wstr_bytes, is_name_equal};
use hbb_common::{bail, ResultType};
use std::{io, ptr::null_mut};
use winapi::{
shared::{
minwindef::{BOOL, DWORD, FALSE, LPBYTE, LPDWORD},
ntdef::HANDLE,
winerror::ERROR_INVALID_PRINTER_NAME,
},
um::winspool::{
AddPrinterW, ClosePrinter, DeletePrinter, EnumPrintersW, OpenPrinterW, SetPrinterW,
PRINTER_ALL_ACCESS, PRINTER_ATTRIBUTE_LOCAL, PRINTER_CONTROL_PURGE, PRINTER_DEFAULTSW,
PRINTER_ENUM_LOCAL, PRINTER_INFO_1W, PRINTER_INFO_2W,
},
};
use windows_strings::{w, PCWSTR};
fn enum_local_printer(
level: DWORD,
p_printer_info: LPBYTE,
cb_buf: DWORD,
pcb_needed: LPDWORD,
pc_returned: LPDWORD,
) -> BOOL {
unsafe {
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
EnumPrintersW(
PRINTER_ENUM_LOCAL,
null_mut(),
level,
p_printer_info,
cb_buf,
pcb_needed,
pc_returned,
)
}
}
#[inline]
pub fn is_printer_added(name: &PCWSTR) -> ResultType<bool> {
let r = common_enum(
"EnumPrintersW",
enum_local_printer,
1,
|info: &PRINTER_INFO_1W| {
if is_name_equal(name, info.pName) {
Some(true)
} else {
None
}
},
|| None,
)?;
Ok(r.unwrap_or(false))
}
// Only return the first matched printer
pub fn get_printer_installed_on_port(port: &PCWSTR) -> ResultType<Option<Vec<u16>>> {
common_enum(
"EnumPrintersW",
enum_local_printer,
2,
|info: &PRINTER_INFO_2W| {
if is_name_equal(port, info.pPortName) {
Some(get_wstr_bytes(info.pPrinterName))
} else {
None
}
},
|| None,
)
}
pub fn add_printer(name: &PCWSTR, driver: &PCWSTR, port: &PCWSTR) -> ResultType<()> {
let mut printer_info = PRINTER_INFO_2W {
pServerName: null_mut(),
pPrinterName: name.as_ptr() as _,
pShareName: null_mut(),
pPortName: port.as_ptr() as _,
pDriverName: driver.as_ptr() as _,
pComment: null_mut(),
pLocation: null_mut(),
pDevMode: null_mut(),
pSepFile: null_mut(),
pPrintProcessor: w!("WinPrint").as_ptr() as _,
pDatatype: w!("RAW").as_ptr() as _,
pParameters: null_mut(),
pSecurityDescriptor: null_mut(),
Attributes: PRINTER_ATTRIBUTE_LOCAL,
Priority: 0,
DefaultPriority: 0,
StartTime: 0,
UntilTime: 0,
Status: 0,
cJobs: 0,
AveragePPM: 0,
};
unsafe {
let h_printer = AddPrinterW(
null_mut(),
2,
&mut printer_info as *mut PRINTER_INFO_2W as _,
);
if h_printer.is_null() {
bail!(format!(
"Failed to add printer. Error: {}",
io::Error::last_os_error()
))
}
}
Ok(())
}
pub fn delete_printer(name: &PCWSTR) -> ResultType<()> {
let mut dft = PRINTER_DEFAULTSW {
pDataType: null_mut(),
pDevMode: null_mut(),
DesiredAccess: PRINTER_ALL_ACCESS,
};
let mut h_printer: HANDLE = null_mut();
unsafe {
if FALSE
== OpenPrinterW(
name.as_ptr() as _,
&mut h_printer,
&mut dft as *mut PRINTER_DEFAULTSW as _,
)
{
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_INVALID_PRINTER_NAME as _) {
return Ok(());
} else {
bail!(format!("Failed to open printer. Error: {}", err))
}
}
if FALSE == SetPrinterW(h_printer, 0, null_mut(), PRINTER_CONTROL_PURGE) {
ClosePrinter(h_printer);
bail!(format!(
"Failed to purge printer queue. Error: {}",
io::Error::last_os_error()
))
}
if FALSE == DeletePrinter(h_printer) {
ClosePrinter(h_printer);
bail!(format!(
"Failed to delete printer. Error: {}",
io::Error::last_os_error()
))
}
ClosePrinter(h_printer);
}
Ok(())
}

View File

@@ -0,0 +1,94 @@
use super::{
driver::{get_installed_driver_version, install_driver, uninstall_driver},
port::{check_add_local_port, check_delete_local_port},
printer::{add_printer, delete_printer},
};
use hbb_common::{allow_err, bail, lazy_static, log, ResultType};
use std::{path::PathBuf, sync::Mutex};
use windows_strings::PCWSTR;
lazy_static::lazy_static!(
static ref SETUP_MTX: Mutex<()> = Mutex::new(());
);
fn get_driver_inf_abs_path() -> ResultType<PathBuf> {
use crate::RD_DRIVER_INF_PATH;
let exe_file = std::env::current_exe()?;
let abs_path = match exe_file.parent() {
Some(parent) => parent.join(RD_DRIVER_INF_PATH),
None => bail!(
"Invalid exe parent for {}",
exe_file.to_string_lossy().as_ref()
),
};
if !abs_path.exists() {
bail!(
"The driver inf file \"{}\" does not exists",
RD_DRIVER_INF_PATH
)
}
Ok(abs_path)
}
// Note: This function must be called in a separate thread.
// Because many functions in this module are blocking or synchronous.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
// Steps:
// 1. Add the local port.
// 2. Check if the driver is installed.
// Uninstall the existing driver if it is installed.
// We should not check the driver version because the driver is deployed with the application.
// It's better to uninstall the existing driver and install the driver from the application.
// 3. Add the printer.
pub fn install_update_printer(app_name: &str) -> ResultType<()> {
let printer_name = crate::get_printer_name(app_name);
let driver_name = crate::get_driver_name();
let port = crate::get_port_name(app_name);
let rd_printer_name = PCWSTR::from_raw(printer_name.as_ptr());
let rd_printer_driver_name = PCWSTR::from_raw(driver_name.as_ptr());
let rd_printer_port = PCWSTR::from_raw(port.as_ptr());
let inf_file = get_driver_inf_abs_path()?;
let inf_file: Vec<u16> = inf_file
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let _lock = SETUP_MTX.lock().unwrap();
check_add_local_port(&rd_printer_port)?;
let should_install_driver = match get_installed_driver_version(&rd_printer_driver_name)? {
Some(_version) => {
delete_printer(&rd_printer_name)?;
allow_err!(uninstall_driver(&rd_printer_driver_name));
true
}
None => true,
};
if should_install_driver {
allow_err!(install_driver(&rd_printer_driver_name, inf_file.as_ptr()));
}
add_printer(&rd_printer_name, &rd_printer_driver_name, &rd_printer_port)?;
Ok(())
}
pub fn uninstall_printer(app_name: &str) {
let printer_name = crate::get_printer_name(app_name);
let driver_name = crate::get_driver_name();
let port = crate::get_port_name(app_name);
let rd_printer_name = PCWSTR::from_raw(printer_name.as_ptr());
let rd_printer_driver_name = PCWSTR::from_raw(driver_name.as_ptr());
let rd_printer_port = PCWSTR::from_raw(port.as_ptr());
let _lock = SETUP_MTX.lock().unwrap();
allow_err!(delete_printer(&rd_printer_name));
allow_err!(uninstall_driver(&rd_printer_driver_name));
allow_err!(check_delete_local_port(&rd_printer_port));
}

View File

@@ -23,7 +23,8 @@ remote = open('src/ui/remote.html').read() \
.replace('include "grid.tis";', open('src/ui/grid.tis').read()) \
.replace('include "header.tis";', open('src/ui/header.tis').read()) \
.replace('include "file_transfer.tis";', open('src/ui/file_transfer.tis').read()) \
.replace('include "port_forward.tis";', open('src/ui/port_forward.tis').read())
.replace('include "port_forward.tis";', open('src/ui/port_forward.tis').read()) \
.replace('include "printer.tis";', open('src/ui/printer.tis').read())
chatbox = open('src/ui/chatbox.html').read()
install = open('src/ui/install.html').read().replace('include "install.tis";', open('src/ui/install.tis').read())

View File

@@ -15,3 +15,9 @@ bool MyStopServiceW(LPCWSTR serviceName);
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key);
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired);
namespace RemotePrinter
{
VOID installUpdatePrinter(const std::wstring& installFolder);
VOID uninstallPrinter();
}

View File

@@ -878,3 +878,55 @@ void TryStopDeleteServiceByShell(LPWSTR svcName)
WcaLog(LOGMSG_STANDARD, "Failed to delete service: \"%ls\" with shell, current status: %d.", svcName, svcStatus.dwCurrentState);
}
}
UINT __stdcall InstallPrinter(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
int nResult = 0;
LPWSTR installFolder = NULL;
LPWSTR pwz = NULL;
LPWSTR pwzData = NULL;
hr = WcaInitialize(hInstall, "InstallPrinter");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"CustomActionData", &pwzData);
ExitOnFailure(hr, "failed to get CustomActionData");
pwz = pwzData;
hr = WcaReadStringFromCaData(&pwz, &installFolder);
ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
WcaLog(LOGMSG_STANDARD, "Try to install RD printer in : %ls", installFolder);
RemotePrinter::installUpdatePrinter(installFolder);
WcaLog(LOGMSG_STANDARD, "Install RD printer done");
LExit:
if (pwzData) {
ReleaseStr(pwzData);
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UninstallPrinter(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "UninstallPrinter");
ExitOnFailure(hr, "Failed to initialize");
WcaLog(LOGMSG_STANDARD, "Try to uninstall RD printer");
RemotePrinter::uninstallPrinter();
WcaLog(LOGMSG_STANDARD, "Uninstall RD printer done");
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}

View File

@@ -12,3 +12,5 @@ EXPORTS
SetPropertyFromConfig
AddRegSoftwareSASGeneration
RemoveAmyuniIdd
InstallPrinter
UninstallPrinter

View File

@@ -67,6 +67,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ReadConfig.cpp" />
<ClCompile Include="RemotePrinter.cpp" />
<ClCompile Include="ServiceUtils.cpp" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,517 @@
#include "pch.h"
#include <Windows.h>
#include <winspool.h>
#include <setupapi.h>
#include <memory>
#include <string>
#include <functional>
#include <vector>
#include <iostream>
#include "Common.h"
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "winspool.lib")
namespace RemotePrinter
{
#define HRESULT_ERR_ELEMENT_NOT_FOUND 0x80070490
LPCWCH RD_DRIVER_INF_PATH = L"drivers\\RustDeskPrinterDriver\\RustDeskPrinterDriver.inf";
LPCWCH RD_PRINTER_PORT = L"RustDesk Printer";
LPCWCH RD_PRINTER_NAME = L"RustDesk Printer";
LPCWCH RD_PRINTER_DRIVER_NAME = L"RustDesk v4 Printer Driver";
LPCWCH XCV_MONITOR_LOCAL_PORT = L",XcvMonitor Local Port";
using FuncEnum = std::function<BOOL(DWORD level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)>;
template <typename T, typename R>
using FuncOnData = std::function<std::shared_ptr<R>(const T &)>;
template <typename R>
using FuncOnNoData = std::function<std::shared_ptr<R>()>;
template <class T, class R>
std::shared_ptr<R> commonEnum(std::wstring funcName, FuncEnum func, DWORD level, FuncOnData<T, R> onData, FuncOnNoData<R> onNoData)
{
DWORD needed = 0;
DWORD returned = 0;
func(level, NULL, 0, &needed, &returned);
if (needed == 0)
{
return onNoData();
}
std::vector<BYTE> buffer(needed);
if (!func(level, buffer.data(), needed, &needed, &returned))
{
return nullptr;
}
T *pPortInfo = reinterpret_cast<T *>(buffer.data());
for (DWORD i = 0; i < returned; i++)
{
auto r = onData(pPortInfo[i]);
if (r)
{
return r;
}
}
return onNoData();
}
BOOL isNameEqual(LPCWSTR lhs, LPCWSTR rhs)
{
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcmpiw
// For some locales, the lstrcmpi function may be insufficient.
// If this occurs, use `CompareStringEx` to ensure proper comparison.
// For example, in Japan call with the NORM_IGNORECASE, NORM_IGNOREKANATYPE, and NORM_IGNOREWIDTH values to achieve the most appropriate non-exact string comparison.
// Note that specifying these values slows performance, so use them only when necessary.
//
// No need to consider `CompareStringEx` for now.
return lstrcmpiW(lhs, rhs) == 0 ? TRUE : FALSE;
}
BOOL enumPrinterPort(
DWORD level,
LPBYTE pPortInfo,
DWORD cbBuf,
LPDWORD pcbNeeded,
LPDWORD pcReturned)
{
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumports
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
return EnumPortsW(NULL, level, pPortInfo, cbBuf, pcbNeeded, pcReturned);
}
BOOL isPortExists(LPCWSTR port)
{
auto onData = [port](const PORT_INFO_2 &info)
{
if (isNameEqual(info.pPortName, port) == TRUE) {
return std::shared_ptr<BOOL>(new BOOL(TRUE));
}
else {
return std::shared_ptr<BOOL>(nullptr);
} };
auto onNoData = []()
{ return nullptr; };
auto res = commonEnum<PORT_INFO_2, BOOL>(L"EnumPortsW", enumPrinterPort, 2, onData, onNoData);
if (res == nullptr)
{
return false;
}
else
{
return *res;
}
}
BOOL executeOnLocalPort(LPCWSTR port, LPCWSTR command)
{
PRINTER_DEFAULTSW dft = {0};
dft.DesiredAccess = SERVER_WRITE;
HANDLE hMonitor = NULL;
if (OpenPrinterW(const_cast<LPWSTR>(XCV_MONITOR_LOCAL_PORT), &hMonitor, &dft) == FALSE)
{
return FALSE;
}
DWORD outputNeeded = 0;
DWORD status = 0;
if (XcvDataW(hMonitor, command, (LPBYTE)port, (lstrlenW(port) + 1) * 2, NULL, 0, &outputNeeded, &status) == FALSE)
{
ClosePrinter(hMonitor);
return FALSE;
}
ClosePrinter(hMonitor);
return TRUE;
}
BOOL addLocalPort(LPCWSTR port)
{
return executeOnLocalPort(port, L"AddPort");
}
BOOL deleteLocalPort(LPCWSTR port)
{
return executeOnLocalPort(port, L"DeletePort");
}
BOOL checkAddLocalPort(LPCWSTR port)
{
if (!isPortExists(port))
{
return addLocalPort(port);
}
return TRUE;
}
std::wstring getPrinterInstalledOnPort(LPCWSTR port);
BOOL checkDeleteLocalPort(LPCWSTR port)
{
if (isPortExists(port))
{
if (getPrinterInstalledOnPort(port) != L"")
{
WcaLog(LOGMSG_STANDARD, "The printer is installed on the port. Please remove the printer first.\n");
return FALSE;
}
return deleteLocalPort(port);
}
return TRUE;
}
BOOL enumPrinterDriver(
DWORD level,
LPBYTE pDriverInfo,
DWORD cbBuf,
LPDWORD pcbNeeded,
LPDWORD pcReturned)
{
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinterdrivers
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
return EnumPrinterDriversW(
NULL,
NULL,
level,
pDriverInfo,
cbBuf,
pcbNeeded,
pcReturned);
}
DWORDLONG getInstalledDriverVersion(LPCWSTR name)
{
auto onData = [name](const DRIVER_INFO_6W &info)
{
if (isNameEqual(name, info.pName) == TRUE)
{
return std::shared_ptr<DWORDLONG>(new DWORDLONG(info.dwlDriverVersion));
}
else
{
return std::shared_ptr<DWORDLONG>(nullptr);
} };
auto onNoData = []()
{ return nullptr; };
auto res = commonEnum<DRIVER_INFO_6W, DWORDLONG>(L"EnumPrinterDriversW", enumPrinterDriver, 6, onData, onNoData);
if (res == nullptr)
{
return 0;
}
else
{
return *res;
}
}
std::wstring findInf(LPCWSTR name)
{
auto onData = [name](const DRIVER_INFO_8W &info)
{
if (isNameEqual(name, info.pName) == TRUE)
{
return std::shared_ptr<std::wstring>(new std::wstring(info.pszInfPath));
}
else
{
return std::shared_ptr<std::wstring>(nullptr);
} };
auto onNoData = []()
{ return nullptr; };
auto res = commonEnum<DRIVER_INFO_8W, std::wstring>(L"EnumPrinterDriversW", enumPrinterDriver, 8, onData, onNoData);
if (res == nullptr)
{
return L"";
}
else
{
return *res;
}
}
BOOL deletePrinterDriver(LPCWSTR name)
{
// If the printer is used after the spooler service is started. E.g., printing a document through RustDesk Printer.
// `DeletePrinterDriverExW()` may fail with `ERROR_PRINTER_DRIVER_IN_USE`(3001, 0xBB9).
// We can only ignore this error for now.
// Though restarting the spooler service is a solution, it's not a good idea to restart the service.
//
// Deleting the printer driver after deleting the printer is a common practice.
// No idea why `DeletePrinterDriverExW()` fails with `ERROR_UNKNOWN_PRINTER_DRIVER` after using the printer once.
// https://github.com/ChromiumWebApps/chromium/blob/c7361d39be8abd1574e6ce8957c8dbddd4c6ccf7/cloud_print/virtual_driver/win/install/setup.cc#L422
// AnyDesk printer driver and the simplest printer driver also have the same issue.
BOOL res = DeletePrinterDriverExW(NULL, NULL, const_cast<LPWSTR>(name), DPD_DELETE_ALL_FILES, 0);
if (res == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_UNKNOWN_PRINTER_DRIVER)
{
return TRUE;
}
else
{
WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver. Error (%d)\n", error);
}
}
return res;
}
BOOL deletePrinterDriverPackage(const std::wstring &inf)
{
// https://learn.microsoft.com/en-us/windows/win32/printdocs/deleteprinterdriverpackage
// This function is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
int tries = 3;
HRESULT result = S_FALSE;
while ((result = DeletePrinterDriverPackage(NULL, inf.c_str(), NULL)) != S_OK)
{
if (result == HRESULT_ERR_ELEMENT_NOT_FOUND)
{
return TRUE;
}
WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver package. HRESULT (%d)\n", result);
tries--;
if (tries <= 0)
{
return FALSE;
}
Sleep(2000);
}
return S_OK;
}
BOOL uninstallDriver(LPCWSTR name)
{
auto infFile = findInf(name);
if (!deletePrinterDriver(name))
{
return FALSE;
}
if (infFile != L"" && !deletePrinterDriverPackage(infFile))
{
return FALSE;
}
return TRUE;
}
BOOL installDriver(LPCWSTR name, LPCWSTR inf)
{
DWORD size = MAX_PATH * 10;
wchar_t package_path[MAX_PATH * 10] = {0};
HRESULT result = UploadPrinterDriverPackage(
NULL, inf, NULL,
UPDP_SILENT_UPLOAD | UPDP_UPLOAD_ALWAYS, NULL, package_path, &size);
if (result != S_OK)
{
WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache silently, failed. Will retry with user UI. HRESULT (%d)\n", result);
result = UploadPrinterDriverPackage(
NULL, inf, NULL, UPDP_UPLOAD_ALWAYS,
GetForegroundWindow(), package_path, &size);
if (result != S_OK)
{
WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache failed with user UI. Aborting...\n");
return FALSE;
}
}
result = InstallPrinterDriverFromPackage(
NULL, package_path, name, NULL, IPDFP_COPY_ALL_FILES);
if (result != S_OK)
{
WcaLog(LOGMSG_STANDARD, "Installing the printer driver failed. HRESULT (%d)\n", result);
}
return result == S_OK;
}
BOOL enumLocalPrinter(
DWORD level,
LPBYTE pPrinterInfo,
DWORD cbBuf,
LPDWORD pcbNeeded,
LPDWORD pcReturned)
{
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters
// This is a blocking or synchronous function and might not return immediately.
// How quickly this function returns depends on run-time factors
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
return EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, level, pPrinterInfo, cbBuf, pcbNeeded, pcReturned);
}
BOOL isPrinterAdded(LPCWSTR name)
{
auto onData = [name](const PRINTER_INFO_1W &info)
{
if (isNameEqual(name, info.pName) == TRUE)
{
return std::shared_ptr<BOOL>(new BOOL(TRUE));
}
else
{
return std::shared_ptr<BOOL>(nullptr);
} };
auto onNoData = []()
{ return nullptr; };
auto res = commonEnum<PRINTER_INFO_1W, BOOL>(L"EnumPrintersW", enumLocalPrinter, 1, onData, onNoData);
if (res == nullptr)
{
return FALSE;
}
else
{
return *res;
}
}
std::wstring getPrinterInstalledOnPort(LPCWSTR port)
{
auto onData = [port](const PRINTER_INFO_2W &info)
{
if (isNameEqual(port, info.pPortName) == TRUE)
{
return std::shared_ptr<std::wstring>(new std::wstring(info.pPrinterName));
}
else
{
return std::shared_ptr<std::wstring>(nullptr);
} };
auto onNoData = []()
{ return nullptr; };
auto res = commonEnum<PRINTER_INFO_2W, std::wstring>(L"EnumPrintersW", enumLocalPrinter, 2, onData, onNoData);
if (res == nullptr)
{
return L"";
}
else
{
return *res;
}
}
BOOL addPrinter(LPCWSTR name, LPCWSTR driver, LPCWSTR port)
{
PRINTER_INFO_2W printerInfo = {0};
printerInfo.pPrinterName = const_cast<LPWSTR>(name);
printerInfo.pPortName = const_cast<LPWSTR>(port);
printerInfo.pDriverName = const_cast<LPWSTR>(driver);
printerInfo.pPrintProcessor = const_cast<LPWSTR>(L"WinPrint");
printerInfo.pDatatype = const_cast<LPWSTR>(L"RAW");
printerInfo.Attributes = PRINTER_ATTRIBUTE_LOCAL;
HANDLE hPrinter = AddPrinterW(NULL, 2, (LPBYTE)&printerInfo);
return hPrinter == NULL ? FALSE : TRUE;
}
VOID deletePrinter(LPCWSTR name)
{
PRINTER_DEFAULTSW dft = {0};
dft.DesiredAccess = PRINTER_ALL_ACCESS;
HANDLE hPrinter = NULL;
if (OpenPrinterW(const_cast<LPWSTR>(name), &hPrinter, &dft) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_INVALID_PRINTER_NAME)
{
return;
}
WcaLog(LOGMSG_STANDARD, "Failed to open printer. error (%d)\n", error);
return;
}
if (SetPrinterW(hPrinter, 0, NULL, PRINTER_CONTROL_PURGE) == FALSE)
{
ClosePrinter(hPrinter);
WcaLog(LOGMSG_STANDARD, "Failed to purge printer queue. error (%d)\n", GetLastError());
return;
}
if (DeletePrinter(hPrinter) == FALSE)
{
ClosePrinter(hPrinter);
WcaLog(LOGMSG_STANDARD, "Failed to delete printer. error (%d)\n", GetLastError());
return;
}
ClosePrinter(hPrinter);
}
bool FileExists(const std::wstring &filePath)
{
DWORD fileAttributes = GetFileAttributes(filePath.c_str());
return (fileAttributes != INVALID_FILE_ATTRIBUTES && !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY));
}
// Steps:
// 1. Add the local port.
// 2. Check if the driver is installed.
// Uninstall the existing driver if it is installed.
// We should not check the driver version because the driver is deployed with the application.
// It's better to uninstall the existing driver and install the driver from the application.
// 3. Add the printer.
VOID installUpdatePrinter(const std::wstring &installFolder)
{
const std::wstring infFile = installFolder + L"\\" + RemotePrinter::RD_DRIVER_INF_PATH;
if (!FileExists(infFile))
{
WcaLog(LOGMSG_STANDARD, "Printer driver INF file not found, aborting...\n");
return;
}
if (!checkAddLocalPort(RD_PRINTER_PORT))
{
WcaLog(LOGMSG_STANDARD, "Failed to check add local port, error (%d)\n", GetLastError());
return;
}
else
{
WcaLog(LOGMSG_STANDARD, "Local port added successfully\n");
}
if (getInstalledDriverVersion(RD_PRINTER_DRIVER_NAME) > 0)
{
deletePrinter(RD_PRINTER_NAME);
if (FALSE == uninstallDriver(RD_PRINTER_DRIVER_NAME))
{
WcaLog(LOGMSG_STANDARD, "Failed to uninstall previous printer driver, error (%d)\n", GetLastError());
}
}
if (FALSE == installDriver(RD_PRINTER_DRIVER_NAME, infFile.c_str()))
{
WcaLog(LOGMSG_STANDARD, "Driver installation failed, still try to add the printer\n");
}
else
{
WcaLog(LOGMSG_STANDARD, "Driver installed successfully\n");
}
if (FALSE == addPrinter(RD_PRINTER_NAME, RD_PRINTER_DRIVER_NAME, RD_PRINTER_PORT))
{
WcaLog(LOGMSG_STANDARD, "Failed to add printer, error (%d)\n", GetLastError());
}
else
{
WcaLog(LOGMSG_STANDARD, "Printer installed successfully\n");
}
}
VOID uninstallPrinter()
{
deletePrinter(RD_PRINTER_NAME);
WcaLog(LOGMSG_STANDARD, "Deleted the printer\n");
uninstallDriver(RD_PRINTER_DRIVER_NAME);
WcaLog(LOGMSG_STANDARD, "Uninstalled the printer driver\n");
checkDeleteLocalPort(RD_PRINTER_PORT);
WcaLog(LOGMSG_STANDARD, "Deleted the local port\n");
}
}

View File

@@ -30,6 +30,7 @@
<CustomAction Id="SetPropertyServiceStop.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
<CustomAction Id="TryDeleteStartupShortcut.SetParam" Return="check" Property="ShortcutName" Value="$(var.Product) Tray" />
<CustomAction Id="RemoveAmyuniIdd.SetParam" Return="check" Property="RemoveAmyuniIdd" Value="[INSTALLFOLDER_INNER]" />
<CustomAction Id="InstallPrinter.SetParam" Return="check" Property="InstallPrinter" Value="[INSTALLFOLDER_INNER]" />
<InstallExecuteSequence>
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
@@ -57,6 +58,18 @@
<Custom Action="LaunchApp" After="InstallFinalize" Condition="(NOT UILevel=2) AND (NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) "/>
<Custom Action="LaunchAppTray" After="InstallFinalize" Condition="(NOT UILevel=2) AND (NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) AND (NOT STOP_SERVICE=&quot;&apos;Y&apos;&quot;) AND (NOT CC_CONNECTION_TYPE=&quot;outgoing&quot;)"/>
<!-- https://learn.microsoft.com/en-us/windows/win32/msi/operating-system-property-values -->
<!-- We have to use `VersionNT` to instead of `IsWindows10OrGreater()` in the custom action.
Because `IsWindows10OrGreater()` requires the manifest file to be embedded in the executable/dll file.
Even I have embedded the manifest file, it still does not work correctly in my case.
https://learn.microsoft.com/en-us/windows/win32/sysinfo/version-helper-apis -->
<!-- VersionNT >= 603 means can't differentiate between Windows 8.1 and Windows 10.
Some msi packages reset the `VersionNT` value to 1000 on Windows 10.
https://www.advancedinstaller.com/user-guide/qa-OS-dependent-install.html -->
<!-- Remote printer also works on Win8.1 in my test. -->
<Custom Action="InstallPrinter" Before="InstallFinalize" Condition="VersionNT &gt;= 603 AND PRINTER = 1 OR PRINTER = &quot;Y&quot; OR PRINTER = &quot;y&quot;" />
<Custom Action="InstallPrinter.SetParam" Before="InstallPrinter" Condition="VersionNT &gt;= 603" />
<!--Workaround of "fire:FirewallException". If Outbound="Yes" or Outbound="true", the following error occurs.-->
<!--ExecFirewallExceptions: Error 0x80070057: failed to add app to the authorized apps list-->
<Custom Action="AddFirewallRules" Before="InstallFinalize" Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)"/>
@@ -72,6 +85,8 @@
<Custom Action="RemoveFirewallRules" Before="RemoveFiles"/>
<Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/>
<Custom Action="UninstallPrinter" Before="RemoveInstallFolder" Condition="VersionNT &gt;= 603" />
<Custom Action="TerminateProcesses" Before="RemoveInstallFolder"/>
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
<Custom Action="TerminateBrokers" Before="RemoveInstallFolder"/>

View File

@@ -17,5 +17,7 @@
<CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="InstallPrinter" DllEntry="InstallPrinter" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="UninstallPrinter" DllEntry="UninstallPrinter" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
</Fragment>
</Wix>

View File

@@ -14,6 +14,7 @@
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
<Property Id="STARTUPSHORTCUTS" Value="1" Secure="yes"></Property>
<Property Id="PRINTER" Value="1" Secure="yes"></Property>
<!-- These properties get set from either the command line, bundle or registry value,
if set they update the properties above with their value. -->
@@ -23,6 +24,9 @@
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
</Property>
<Property Id="INSTALLPRINTER" Secure="yes">
<RegistrySearch Id="InstallPrinterSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="PRINTER" Type="raw" />
</Property>
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
<DirectoryRef Id="INSTALLFOLDER_INNER">
@@ -46,6 +50,16 @@
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="0" />
</RegistryKey>
</Component>
<Component Id="Product.Registry.PersistedPrinterProperties1" Guid="AF617116-2502-EB3D-5B52-B47AA89EB4B0" Condition="PRINTER = 1 OR PRINTER = &quot;Y&quot; OR PRINTER = &quot;y&quot;">
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
<RegistryValue Type="string" Name="PRINTER" Value="1" />
</RegistryKey>
</Component>
<Component Id="Product.Registry.PersistedPrinterProperties0" Guid="51F944D3-AAEB-F167-03A1-081A38E9468A" Condition="NOT (PRINTER = 1 OR PRINTER = &quot;Y&quot; OR PRINTER = &quot;y&quot;)">
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
<RegistryValue Type="string" Name="PRINTER" Value="0" />
</RegistryKey>
</Component>
</DirectoryRef>
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
@@ -53,14 +67,17 @@
is performed so they can be restored after the registry search is complete -->
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
<SetProperty Id="SavedPrinterCmdLineValue" Value="[INSTALLPRINTER]" Before="AppSearch" Sequence="first" Condition="INSTALLPRINTER" />
<!-- If a command line value was stored, restore it after the registry search has been performed -->
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
<SetProperty Action="RestoreSavedPrinterValue" Id="INSTALLPRINTER" Value="[SavedPrinterCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedPrinterCmdLineValue" />
<!-- If a command line value or registry value was set, update the main properties with the value -->
<SetProperty Id="STARTMENUSHORTCUTS" Value="" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS AND NOT (CREATESTARTMENUSHORTCUTS = 1 OR CREATESTARTMENUSHORTCUTS = &quot;Y&quot; OR CREATESTARTMENUSHORTCUTS = &quot;y&quot;)" />
<SetProperty Id="DESKTOPSHORTCUTS" Value="" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS AND NOT (CREATEDESKTOPSHORTCUTS = 1 OR CREATEDESKTOPSHORTCUTS = &quot;Y&quot; OR CREATEDESKTOPSHORTCUTS = &quot;y&quot;)" />
<SetProperty Id="PRINTER" Value="" After="RestoreSavedPrinterValue" Sequence="first" Condition="INSTALLPRINTER AND NOT (INSTALLPRINTER = 1 OR INSTALLPRINTER = &quot;Y&quot; OR INSTALLPRINTER = &quot;y&quot;)" />
</Fragment>
</Wix>

View File

@@ -51,5 +51,6 @@ This file contains the declaration of all the localizable strings.
<String Id="MyInstallDirDlgDesktopShortcuts" Value="Create desktop icon" />
<String Id="MyInstallDirDlgStartMenuShortcuts" Value="Create start menu shortcuts" />
<String Id="MyInstallDirDlgPrinter" Value="Install RustDesk Printer" />
</WixLocalization>

View File

@@ -51,6 +51,8 @@
<ComponentRef Id="Product.Registry.PersistedStartMenuShortcutProperties0" />
<ComponentRef Id="Product.Registry.PersistedDesktopShortcutProperties1" />
<ComponentRef Id="Product.Registry.PersistedDesktopShortcutProperties0" />
<ComponentRef Id="Product.Registry.PersistedPrinterProperties1" />
<ComponentRef Id="Product.Registry.PersistedPrinterProperties0" />
</Feature>
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->

View File

@@ -25,6 +25,7 @@
<Control Id="ChkBoxStartMenuShortcuts" Type="CheckBox" X="20" Y="140" Width="290" Height="17" Property="STARTMENUSHORTCUTS" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgStartMenuShortcuts)" />
<Control Id="ChkBoxDesktopShortcuts" Type="CheckBox" X="20" Y="160" Width="290" Height="17" Property="DESKTOPSHORTCUTS" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgDesktopShortcuts)" />
<Control Id="ChkBoxInstallPrinter" Type="CheckBox" X="20" Y="180" Width="290" Height="17" Property="PRINTER" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgPrinter)" />
</Dialog>
</UI>
</Fragment>

View File

@@ -8,7 +8,9 @@ import argparse
import datetime
import subprocess
import re
import platform
from pathlib import Path
from itertools import chain
import shutil
g_indent_unit = "\t"
@@ -187,6 +189,17 @@ def replace_app_name_in_langs(app_name):
with open(file_path, "w", encoding="utf-8") as f:
f.writelines(lines)
def replace_app_name_in_custom_actions(app_name):
custion_actions_dir = Path(sys.argv[0]).parent.joinpath("CustomActions")
for file_path in chain(custion_actions_dir.glob("*.cpp"), custion_actions_dir.glob("*.h")):
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
for i, line in enumerate(lines):
line = re.sub(r"\bRustDesk\b", app_name, line)
line = line.replace(f"{app_name} v4 Printer Driver", "RustDesk v4 Printer Driver")
lines[i] = line
with open(file_path, "w", encoding="utf-8") as f:
f.writelines(lines)
def gen_upgrade_info():
def func(lines, index_start):
@@ -542,3 +555,4 @@ if __name__ == "__main__":
sys.exit(-1)
replace_app_name_in_langs(args.app_name)
replace_app_name_in_custom_actions(args.app_name)

View File

@@ -49,6 +49,7 @@ use hbb_common::{
self, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT,
READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_PORT, RENDEZVOUS_SERVERS,
},
fs::JobType,
get_version_number, log,
message_proto::{option_message::BoolOption, *},
protobuf::{Message as _, MessageField},
@@ -3297,7 +3298,7 @@ pub enum Data {
Close,
Login((String, String, String, bool)),
Message(Message),
SendFiles((i32, String, String, i32, bool, bool)),
SendFiles((i32, JobType, String, String, i32, bool, bool)),
RemoveDirAll((i32, String, bool, bool)),
ConfirmDeleteFiles((i32, i32)),
SetNoConfirm(i32),
@@ -3311,7 +3312,7 @@ pub enum Data {
ToggleClipboardFile,
NewRDP,
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
AddJob((i32, String, String, i32, bool, bool)),
AddJob((i32, JobType, String, String, i32, bool, bool)),
ResumeJob((i32, bool)),
RecordScreen(bool),
ElevateDirect,

View File

@@ -7,6 +7,14 @@ pub trait FileManager: Interface {
fs::get_home_as_string()
}
fn get_next_job_id(&self) -> i32 {
fs::get_next_job_id()
}
fn update_next_job_id(&self, id: i32) {
fs::update_next_job_id(id);
}
#[cfg(not(any(
target_os = "android",
target_os = "ios",
@@ -98,6 +106,7 @@ pub trait FileManager: Interface {
fn send_files(
&self,
id: i32,
r#type: i32,
path: String,
to: String,
file_num: i32,
@@ -106,6 +115,7 @@ pub trait FileManager: Interface {
) {
self.send(Data::SendFiles((
id,
r#type.into(),
path,
to,
file_num,
@@ -117,6 +127,7 @@ pub trait FileManager: Interface {
fn add_job(
&self,
id: i32,
r#type: i32,
path: String,
to: String,
file_num: i32,
@@ -125,6 +136,7 @@ pub trait FileManager: Interface {
) {
self.send(Data::AddJob((
id,
r#type.into(),
path,
to,
file_num,

View File

@@ -46,6 +46,7 @@ use std::{
collections::HashMap,
ffi::c_void,
num::NonZeroI64,
path::PathBuf,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, RwLock,
@@ -549,13 +550,20 @@ impl<T: InvokeUiSession> Remote<T> {
}
allow_err!(peer.send(&msg).await);
}
Data::SendFiles((id, path, to, file_num, include_hidden, is_remote)) => {
Data::SendFiles((id, r#type, path, to, file_num, include_hidden, is_remote)) => {
log::info!("send files, is remote {}", is_remote);
let od = can_enable_overwrite_detection(self.handler.lc.read().unwrap().version);
if is_remote {
log::debug!("New job {}, write to {} from remote {}", id, to, path);
let to = match r#type {
fs::JobType::Generic => fs::DataSource::FilePath(PathBuf::from(&to)),
fs::JobType::Printer => {
fs::DataSource::MemoryCursor(std::io::Cursor::new(Vec::new()))
}
};
self.write_jobs.push(fs::TransferJob::new_write(
id,
r#type,
path.clone(),
to,
file_num,
@@ -565,14 +573,15 @@ impl<T: InvokeUiSession> Remote<T> {
od,
));
allow_err!(
peer.send(&fs::new_send(id, path, file_num, include_hidden))
peer.send(&fs::new_send(id, r#type, path, file_num, include_hidden))
.await
);
} else {
match fs::TransferJob::new_read(
id,
r#type,
to.clone(),
path.clone(),
fs::DataSource::FilePath(PathBuf::from(&path)),
file_num,
include_hidden,
is_remote,
@@ -616,7 +625,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
Data::AddJob((id, path, to, file_num, include_hidden, is_remote)) => {
Data::AddJob((id, r#type, path, to, file_num, include_hidden, is_remote)) => {
let od = can_enable_overwrite_detection(self.handler.lc.read().unwrap().version);
if is_remote {
log::debug!(
@@ -627,8 +636,9 @@ impl<T: InvokeUiSession> Remote<T> {
);
let mut job = fs::TransferJob::new_write(
id,
r#type,
path.clone(),
to,
fs::DataSource::FilePath(PathBuf::from(&to)),
file_num,
include_hidden,
is_remote,
@@ -640,8 +650,9 @@ impl<T: InvokeUiSession> Remote<T> {
} else {
match fs::TransferJob::new_read(
id,
r#type,
to.clone(),
path.clone(),
fs::DataSource::FilePath(PathBuf::from(&path)),
file_num,
include_hidden,
is_remote,
@@ -679,6 +690,7 @@ impl<T: InvokeUiSession> Remote<T> {
allow_err!(
peer.send(&fs::new_send(
id,
fs::JobType::Generic,
job.remote.clone(),
job.file_num,
job.show_hidden
@@ -688,11 +700,13 @@ impl<T: InvokeUiSession> Remote<T> {
}
} else {
if let Some(job) = get_job(id, &mut self.read_jobs) {
match &job.data_source {
fs::DataSource::FilePath(p) => {
job.is_last_job = false;
allow_err!(
peer.send(&fs::new_receive(
id,
job.path.to_string_lossy().to_string(),
p.to_string_lossy().to_string(),
job.file_num,
job.files.clone(),
job.total_size(),
@@ -700,6 +714,12 @@ impl<T: InvokeUiSession> Remote<T> {
.await
);
}
fs::DataSource::MemoryCursor(_) => {
// unreachable!()
log::error!("Resume job with memory cursor");
}
}
}
}
}
Data::SetNoConfirm(id) => {
@@ -803,11 +823,10 @@ impl<T: InvokeUiSession> Remote<T> {
});
msg_out.set_file_action(file_action);
allow_err!(peer.send(&msg_out).await);
if let Some(job) = fs::get_job(id, &mut self.write_jobs) {
if let Some(job) = fs::remove_job(id, &mut self.write_jobs) {
job.remove_download_file();
fs::remove_job(id, &mut self.write_jobs);
}
fs::remove_job(id, &mut self.read_jobs);
let _ = fs::remove_job(id, &mut self.read_jobs);
self.remove_jobs.remove(&id);
}
Data::RemoveDir((id, path)) => {
@@ -1402,8 +1421,11 @@ impl<T: InvokeUiSession> Remote<T> {
if digest.is_upload {
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) {
let read_path = get_string(&job.join(&file.name));
let overwrite_strategy = job.default_overwrite_strategy();
if let fs::DataSource::FilePath(p) = &job.data_source {
let read_path =
get_string(&fs::TransferJob::join(p, &file.name));
let overwrite_strategy =
job.default_overwrite_strategy();
if let Some(overwrite) = overwrite_strategy {
let req = FileTransferSendConfirmRequest {
id: digest.id,
@@ -1431,12 +1453,19 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
}
} else {
if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) {
let write_path = get_string(&job.join(&file.name));
let overwrite_strategy = job.default_overwrite_strategy();
match fs::is_write_need_confirmation(&write_path, &digest) {
if let fs::DataSource::FilePath(p) = &job.data_source {
let write_path =
get_string(&fs::TransferJob::join(p, &file.name));
let overwrite_strategy =
job.default_overwrite_strategy();
match fs::is_write_need_confirmation(
&write_path,
&digest,
) {
Ok(res) => match res {
DigestCheckResult::IsSame => {
let req = FileTransferSendConfirmRequest {
@@ -1450,8 +1479,10 @@ impl<T: InvokeUiSession> Remote<T> {
allow_err!(peer.send(&msg).await);
}
DigestCheckResult::NeedConfirm(digest) => {
if let Some(overwrite) = overwrite_strategy {
let req = FileTransferSendConfirmRequest {
if let Some(overwrite) = overwrite_strategy
{
let req =
FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
@@ -1494,29 +1525,63 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
}
Some(file_response::Union::Block(block)) => {
if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Err(_err) = job.write(block).await {
// to-do: add "skip" for writing job
}
if job.r#type == fs::JobType::Generic {
self.update_jobs_status();
}
}
}
Some(file_response::Union::Done(d)) => {
let mut err: Option<String> = None;
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
let mut job_type = fs::JobType::Generic;
let mut printer_data = None;
if let Some(job) = fs::remove_job(d.id, &mut self.write_jobs) {
job.modify_time();
err = job.job_error();
fs::remove_job(d.id, &mut self.write_jobs);
job_type = job.r#type;
printer_data = job.get_buf_data();
}
match job_type {
fs::JobType::Generic => {
self.handle_job_status(d.id, d.file_num, err);
}
Some(file_response::Union::Error(e)) => {
if let Some(_job) = fs::get_job(e.id, &mut self.write_jobs) {
fs::remove_job(e.id, &mut self.write_jobs);
fs::JobType::Printer =>
{
#[cfg(target_os = "windows")]
if let Some(data) = printer_data {
let printer_name = self
.handler
.printer_names
.write()
.unwrap()
.remove(&d.id);
crate::platform::send_raw_data_to_printer(
printer_name,
data,
)
.ok();
}
}
}
}
Some(file_response::Union::Error(e)) => {
let job_type = fs::remove_job(e.id, &mut self.write_jobs)
.map(|j| j.r#type)
.unwrap_or(fs::JobType::Generic);
match job_type {
fs::JobType::Generic => {
self.handle_job_status(e.id, e.file_num, Some(e.error));
}
fs::JobType::Printer => {
log::error!("Printer job error: {}", e.error);
}
}
}
_ => {}
}
}
@@ -1739,6 +1804,41 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
Some(message::Union::FileAction(action)) => match action.union {
Some(file_action::Union::Send(_s)) => match _s.file_type.enum_value() {
#[cfg(target_os = "windows")]
Ok(file_transfer_send_request::FileType::Printer) => {
#[cfg(feature = "flutter")]
let action = LocalConfig::get_option(
config::keys::OPTION_PRINTER_INCOMING_JOB_ACTION,
);
#[cfg(not(feature = "flutter"))]
let action = "";
if action == "dismiss" {
// Just ignore the incoming print job.
} else {
let id = fs::get_next_job_id();
#[cfg(feature = "flutter")]
let allow_auto_print = LocalConfig::get_bool_option(
config::keys::OPTION_PRINTER_ALLOW_AUTO_PRINT,
);
#[cfg(not(feature = "flutter"))]
let allow_auto_print = false;
if allow_auto_print {
let printer_name = if action == "" {
"".to_string()
} else {
LocalConfig::get_option(
config::keys::OPTION_PRINTER_SELECTED_NAME,
)
};
self.handler.printer_response(id, _s.path, printer_name);
} else {
self.handler.printer_request(id, _s.path);
}
}
}
_ => {}
},
Some(file_action::Union::SendConfirm(c)) => {
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
job.confirm(&c);
@@ -2004,7 +2104,7 @@ impl<T: InvokeUiSession> Remote<T> {
async fn handle_cliprdr_msg(
&mut self,
clip: hbb_common::message_proto::Cliprdr,
peer: &mut Stream,
_peer: &mut Stream,
) {
log::debug!("handling cliprdr msg from server peer");
#[cfg(feature = "flutter")]
@@ -2074,7 +2174,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
if let Some(msg) = out_msg {
allow_err!(peer.send(&msg).await);
allow_err!(_peer.send(&msg).await);
}
}
}

View File

@@ -139,6 +139,10 @@ pub fn is_support_file_copy_paste_num(ver: i64) -> bool {
ver >= hbb_common::get_version_number("1.3.8")
}
pub fn is_support_remote_print(ver: &str) -> bool {
hbb_common::get_version_number(ver) >= hbb_common::get_version_number("1.3.9")
}
pub fn is_support_file_paste_if_macos(ver: &str) -> bool {
hbb_common::get_version_number(ver) >= hbb_common::get_version_number("1.3.9")
}

View File

@@ -196,12 +196,11 @@ pub fn core_main() -> Option<Vec<String>> {
if config::is_disable_installation() {
return None;
}
let res = platform::install_me(
"desktopicon startmenu",
"".to_owned(),
true,
args.len() > 1,
);
#[cfg(not(windows))]
let options = "desktopicon startmenu";
#[cfg(windows)]
let options = "desktopicon startmenu printer";
let res = platform::install_me(options, "".to_owned(), true, args.len() > 1);
let text = match res {
Ok(_) => translate("Installation Successful!".to_string()),
Err(err) => {

View File

@@ -1059,6 +1059,14 @@ impl InvokeUiSession for FlutterHandler {
fn update_record_status(&self, start: bool) {
self.push_event("record_status", &[("start", &start.to_string())], &[]);
}
fn printer_request(&self, id: i32, path: String) {
self.push_event(
"printer_request",
&[("id", json!(id)), ("path", json!(path))],
&[],
);
}
}
impl FlutterHandler {

View File

@@ -624,7 +624,15 @@ pub fn session_send_files(
_is_dir: bool,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
session.send_files(
act_id,
fs::JobType::Generic.into(),
path,
to,
file_num,
include_hidden,
is_remote,
);
}
}
@@ -749,7 +757,15 @@ pub fn session_add_job(
is_remote: bool,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
session.add_job(
act_id,
fs::JobType::Generic.into(),
path,
to,
file_num,
include_hidden,
is_remote,
);
}
}
@@ -1668,6 +1684,17 @@ pub fn session_toggle_virtual_display(session_id: SessionID, index: i32, on: boo
}
}
pub fn session_printer_response(
session_id: SessionID,
id: i32,
path: String,
printer_name: String,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.printer_response(id, path, printer_name);
}
}
pub fn main_set_home_dir(_home: String) {
#[cfg(any(target_os = "android", target_os = "ios"))]
{
@@ -2362,6 +2389,68 @@ pub fn main_audio_support_loopback() -> SyncReturn<bool> {
SyncReturn(is_surpport)
}
pub fn main_get_printer_names() -> SyncReturn<String> {
#[cfg(target_os = "windows")]
return SyncReturn(
serde_json::to_string(&crate::platform::windows::get_printer_names().unwrap_or_default())
.unwrap_or_default(),
);
#[cfg(not(target_os = "windows"))]
return SyncReturn("".to_owned());
}
pub fn main_get_common(key: String) -> String {
if key == "is-printer-installed" {
#[cfg(target_os = "windows")]
{
return match remote_printer::is_rd_printer_installed(&get_app_name()) {
Ok(r) => r.to_string(),
Err(e) => e.to_string(),
};
}
#[cfg(not(target_os = "windows"))]
return false.to_string();
} else if key == "is-support-printer-driver" {
#[cfg(target_os = "windows")]
return crate::platform::is_win_10_or_greater().to_string();
#[cfg(not(target_os = "windows"))]
return false.to_string();
} else if key == "transfer-job-id" {
return hbb_common::fs::get_next_job_id().to_string();
} else {
"".to_owned()
}
}
pub fn main_get_common_sync(key: String) -> SyncReturn<String> {
SyncReturn(main_get_common(key))
}
pub fn main_set_common(_key: String, _value: String) {
#[cfg(target_os = "windows")]
if _key == "install-printer" && crate::platform::is_win_10_or_greater() {
std::thread::spawn(move || {
let (success, msg) = match remote_printer::install_update_printer(&get_app_name()) {
Ok(_) => (true, "".to_owned()),
Err(e) => {
let err = e.to_string();
log::error!("Failed to install/update rd printer: {}", &err);
(false, err)
}
};
let data = HashMap::from([
("name", serde_json::json!("install-printer-res")),
("success", serde_json::json!(success)),
("msg", serde_json::json!(msg)),
]);
let _res = flutter::push_global_event(
flutter::APP_TYPE_MAIN,
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
);
});
}
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::{config, log};

View File

@@ -272,6 +272,8 @@ pub enum Data {
HwCodecConfig(Option<String>),
RemoveTrustedDevices(Vec<Bytes>),
ClearTrustedDevices,
#[cfg(all(target_os = "windows", feature = "flutter"))]
PrinterData(Vec<u8>),
}
#[tokio::main(flavor = "current_thread")]
@@ -461,7 +463,7 @@ async fn handle(data: Data, stream: &mut Connection) {
.lock()
.unwrap()
.iter()
.filter(|x| x.1 == crate::server::AuthConnType::Remote)
.filter(|x| x.conn_type == crate::server::AuthConnType::Remote)
.count();
allow_err!(stream.send(&Data::VideoConnCount(Some(n))).await);
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "没有摄像头"),
("d3d_render_tip", "当启用 D3D 渲染时,某些机器可能无法显示远程画面。"),
("Use D3D rendering", "使用 D3D 渲染"),
("Printer", "打印机"),
("printer-os-requirement-tip", "打印机的传出功能需要 Windows 10 或更高版本。"),
("printer-requires-installed-{}-client-tip", "请先安装 {} 客户端。"),
("printer-{}-not-installed-tip", "未安装 {} 打印机。"),
("printer-{}-ready-tip", "{} 打印机已安装,您可以使用打印功能了。"),
("Install {} Printer", "安装 {} 打印机"),
("Outgoing Print Jobs", "传出的打印任务"),
("Incomming Print Jobs", "传入的打印任务"),
("Incoming Print Job", "传入的打印任务"),
("use-the-default-printer-tip", "使用默认的打印机执行"),
("use-the-selected-printer-tip", "使用选择的打印机执行"),
("auto-print-tip", "使用选择的打印机自动执行"),
("print-incoming-job-confirm-tip", "您收到一个远程打印任务,您想在本地执行它吗?"),
("remote-printing-disallowed-tile-tip", "不允许远程打印"),
("remote-printing-disallowed-text-tip", "被控端的权限设置拒绝了远程打印。"),
("save-settings-tip", "保存设置"),
("dont-show-again-tip", "不再显示此信息"),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Keine Kameras"),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -241,5 +241,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("upgrade_remote_rustdesk_client_to_{}_tip", "Please upgrade the RustDesk client to version {} or newer on the remote side!"),
("view_camera_unsupported_tip", "The remote device does not support viewing the camera."),
("d3d_render_tip", "When D3D rendering is enabled, the remote control screen may be black on some machines."),
("printer-requires-installed-{}-client-tip", "In order to use remote printing, {} needs to be installed on this device."),
("printer-os-requirement-tip", "The printer outgoing function requires Windows 10 or higher."),
("printer-{}-not-installed-tip", "The {} Printer is not installed."),
("printer-{}-ready-tip", "The {} Printer is installed and ready to use."),
("auto-print-tip", "Print automatically using the selected printer."),
("print-incoming-job-confirm-tip", "You received a print job from remote. Do you want to execute it at your side?"),
("use-the-default-printer-tip", "Use the default printer"),
("use-the-selected-printer-tip", "Use the selected printer"),
("remote-printing-disallowed-tile-tip", "Remote Printing disallowed"),
("remote-printing-disallowed-text-tip", "The permission settings of the controlled side deny Remote Printing."),
("save-settings-tip", "Save settings"),
("dont-show-again-tip", "Don't show this again"),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "No hay cámaras"),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Aucune caméra"),
("d3d_render_tip", "Sur certaines machines, lécran du contrôle à distance peut rester noir lors de lutilisation du rendu D3D."),
("Use D3D rendering", "Utiliser le rendu D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Nessuna camera"),
("d3d_render_tip", "Quando è abilitato il rendering D3D, in alcuni computer la schermata del telecomando potrebbe essere nera."),
("Use D3D rendering", "Usa rendering D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Nav kameru"),
("d3d_render_tip", "Ja ir iespējota D3D renderēšana, dažās ierīcēs tālvadības pults ekrāns var būt melns."),
("Use D3D rendering", "Izmantot D3D renderēšanu"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Geen camera's"),
("d3d_render_tip", "Wanneer D3D-rendering is ingeschakeld kan het externe scherm op sommige apparaten, zwart zijn."),
("Use D3D rendering", "Gebruik D3D-rendering"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Камера отсутствует"),
("d3d_render_tip", "При включении визуализации D3D на некоторых устройствах удалённый экран может быть чёрным."),
("Use D3D rendering", "Использовать визуализацию D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Peruna càmera"),
("d3d_render_tip", "Cando sa renderizatzione D3D est abilitada, s'ischermu de controllu remotu diat pòdere èssere nieddu in unas cantas màchinas"),
("Use D3D rendering", "Imprea sa renderizatzione D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "沒有鏡頭"),
("d3d_render_tip", "當啟用 D3D 渲染時,某些機器可能會無法顯示遠端畫面。"),
("Use D3D rendering", "使用 D3D 渲染"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -1,6 +1,8 @@
#include <windows.h>
#include <wtsapi32.h>
#include <tlhelp32.h>
#include <comdef.h>
#include <xpsprint.h>
#include <cstdio>
#include <cstdint>
#include <intrin.h>
@@ -11,6 +13,7 @@
#include <versionhelpers.h>
#include <vector>
#include <sddl.h>
#include <memory>
extern "C" uint32_t get_session_user_info(PWSTR bufin, uint32_t nin, uint32_t id);
@@ -860,3 +863,113 @@ extern "C"
return isRunning;
}
} // end of extern "C"
// Remote printing
extern "C"
{
#pragma comment(lib, "XpsPrint.lib")
#pragma warning(push)
#pragma warning(disable : 4995)
#define PRINT_XPS_CHECK_HR(hr, msg) \
if (FAILED(hr)) \
{ \
_com_error err(hr); \
flog("%s Error: %s\n", msg, err.ErrorMessage()); \
return -1; \
}
int PrintXPSRawData(LPWSTR printerName, BYTE *rawData, ULONG dataSize)
{
BOOL isCoInitializeOk = FALSE;
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (hr == RPC_E_CHANGED_MODE)
{
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
}
if (hr == S_OK)
{
isCoInitializeOk = TRUE;
}
std::shared_ptr<int> coInitGuard(nullptr, [isCoInitializeOk](int *) {
if (isCoInitializeOk) CoUninitialize();
});
IXpsOMObjectFactory *xpsFactory = nullptr;
hr = CoCreateInstance(
__uuidof(XpsOMObjectFactory),
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IXpsOMObjectFactory),
reinterpret_cast<LPVOID *>(&xpsFactory));
PRINT_XPS_CHECK_HR(hr, "Failed to create XPS object factory.");
std::shared_ptr<IXpsOMObjectFactory> xpsFactoryGuard(
xpsFactory,
[](IXpsOMObjectFactory *xpsFactory) {
xpsFactory->Release();
});
HANDLE completionEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (completionEvent == nullptr)
{
flog("Failed to create completion event. Last error: %d\n", GetLastError());
return -1;
}
std::shared_ptr<HANDLE> completionEventGuard(
&completionEvent,
[](HANDLE *completionEvent) {
CloseHandle(*completionEvent);
});
IXpsPrintJob *job = nullptr;
IXpsPrintJobStream *jobStream = nullptr;
// `StartXpsPrintJob()` is deprecated, but we still use it for compatibility.
// We may change to use the `Print Document Package API` in the future.
// https://learn.microsoft.com/en-us/windows/win32/printdocs/xpsprint-functions
hr = StartXpsPrintJob(
printerName,
L"Print Job 1",
nullptr,
nullptr,
completionEvent,
nullptr,
0,
&job,
&jobStream,
nullptr);
PRINT_XPS_CHECK_HR(hr, "Failed to start XPS print job.");
std::shared_ptr<IXpsPrintJobStream> jobStreamGuard(jobStream, [](IXpsPrintJobStream *jobStream) {
jobStream->Release();
});
BOOL jobOk = FALSE;
std::shared_ptr<IXpsPrintJob> jobGuard(job, [&jobOk](IXpsPrintJob* job) {
if (jobOk == FALSE)
{
job->Cancel();
}
job->Release();
});
DWORD bytesWritten = 0;
hr = jobStream->Write(rawData, dataSize, &bytesWritten);
PRINT_XPS_CHECK_HR(hr, "Failed to write data to print job stream.");
hr = jobStream->Close();
PRINT_XPS_CHECK_HR(hr, "Failed to close print job stream.");
// Wait about 5 minutes for the print job to complete.
DWORD waitMillis = 300 * 1000;
DWORD waitResult = WaitForSingleObject(completionEvent, waitMillis);
if (waitResult != WAIT_OBJECT_0)
{
flog("Wait for print job completion failed. Last error: %d\n", GetLastError());
return -1;
}
jobOk = TRUE;
return 0;
}
#pragma warning(pop)
}

View File

@@ -21,23 +21,22 @@ use std::{
fs,
io::{self, prelude::*},
mem,
os::windows::process::CommandExt,
os::{raw::c_ulong, windows::process::CommandExt},
path::*,
ptr::null_mut,
sync::{atomic::Ordering, Arc, Mutex},
time::{Duration, Instant},
};
use wallpaper;
#[cfg(not(debug_assertions))]
use winapi::um::libloaderapi::{LoadLibraryExW, LOAD_LIBRARY_SEARCH_USER_DIRS};
use winapi::{
ctypes::c_void,
shared::{minwindef::*, ntdef::NULL, windef::*, winerror::*},
um::{
errhandlingapi::GetLastError,
handleapi::CloseHandle,
libloaderapi::{
GetProcAddress, LoadLibraryExA, LoadLibraryExW, LOAD_LIBRARY_SEARCH_SYSTEM32,
LOAD_LIBRARY_SEARCH_USER_DIRS,
},
libloaderapi::{GetProcAddress, LoadLibraryExA, LOAD_LIBRARY_SEARCH_SYSTEM32},
minwinbase::STILL_ACTIVE,
processthreadsapi::{
GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, OpenProcess,
@@ -54,6 +53,10 @@ use winapi::{
TOKEN_ELEVATION, TOKEN_QUERY,
},
winreg::HKEY_CURRENT_USER,
winspool::{
EnumPrintersW, GetDefaultPrinterW, PRINTER_ENUM_CONNECTIONS, PRINTER_ENUM_LOCAL,
PRINTER_INFO_1W,
},
winuser::*,
},
};
@@ -73,6 +76,7 @@ pub const SET_FOREGROUND_WINDOW: &'static str = "SET_FOREGROUND_WINDOW";
const REG_NAME_INSTALL_DESKTOPSHORTCUTS: &str = "DESKTOPSHORTCUTS";
const REG_NAME_INSTALL_STARTMENUSHORTCUTS: &str = "STARTMENUSHORTCUTS";
const REG_NAME_INSTALL_PRINTER: &str = "PRINTER";
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
unsafe {
@@ -1011,6 +1015,10 @@ pub fn get_install_options() -> String {
if let Some(start_menu_shortcuts) = start_menu_shortcuts {
opts.insert(REG_NAME_INSTALL_STARTMENUSHORTCUTS, start_menu_shortcuts);
}
let printer = get_reg_of_hkcr(&subkey, REG_NAME_INSTALL_PRINTER);
if let Some(printer) = printer {
opts.insert(REG_NAME_INSTALL_PRINTER, printer);
}
serde_json::to_string(&opts).unwrap_or("{}".to_owned())
}
@@ -1136,6 +1144,7 @@ fn get_after_install(
exe: &str,
reg_value_start_menu_shortcuts: Option<String>,
reg_value_desktop_shortcuts: Option<String>,
reg_value_printer: Option<String>,
) -> String {
let app_name = crate::get_app_name();
let ext = app_name.to_lowercase();
@@ -1159,12 +1168,20 @@ fn get_after_install(
)
})
.unwrap_or_default();
let reg_printer = reg_value_printer
.map(|v| {
format!(
"reg add HKEY_CLASSES_ROOT\\.{ext} /f /v {REG_NAME_INSTALL_PRINTER} /t REG_SZ /d \"{v}\""
)
})
.unwrap_or_default();
format!("
chcp 65001
reg add HKEY_CLASSES_ROOT\\.{ext} /f
{desktop_shortcuts}
{start_menu_shortcuts}
{reg_printer}
reg add HKEY_CLASSES_ROOT\\.{ext}\\DefaultIcon /f
reg add HKEY_CLASSES_ROOT\\.{ext}\\DefaultIcon /f /ve /t REG_SZ /d \"\\\"{exe}\\\",0\"
reg add HKEY_CLASSES_ROOT\\.{ext}\\shell /f
@@ -1249,6 +1266,7 @@ oLink.Save
let tray_shortcut = get_tray_shortcut(&exe, &tmp_path)?;
let mut reg_value_desktop_shortcuts = "0".to_owned();
let mut reg_value_start_menu_shortcuts = "0".to_owned();
let mut reg_value_printer = "0".to_owned();
let mut shortcuts = Default::default();
if options.contains("desktopicon") {
shortcuts = format!(
@@ -1268,6 +1286,10 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\"
);
reg_value_start_menu_shortcuts = "1".to_owned();
}
let install_printer = options.contains("printer") && crate::platform::is_win_10_or_greater();
if install_printer {
reg_value_printer = "1".to_owned();
}
let meta = std::fs::symlink_metadata(std::env::current_exe()?)?;
let size = meta.len() / 1024;
@@ -1338,7 +1360,8 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
after_install = get_after_install(
&exe,
Some(reg_value_start_menu_shortcuts),
Some(reg_value_desktop_shortcuts)
Some(reg_value_desktop_shortcuts),
Some(reg_value_printer)
),
sleep = if debug { "timeout 300" } else { "" },
dels = if debug { "" } else { &dels },
@@ -1346,13 +1369,22 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
import_config = get_import_config(&exe),
);
run_cmds(cmds, debug, "install")?;
if install_printer {
allow_err!(remote_printer::install_update_printer(
&crate::get_app_name()
));
}
run_after_run_cmds(silent);
Ok(())
}
pub fn run_after_install() -> ResultType<()> {
let (_, _, _, exe) = get_install_info();
run_cmds(get_after_install(&exe, None, None), true, "after_install")
run_cmds(
get_after_install(&exe, None, None, None),
true,
"after_install",
)
}
pub fn run_before_uninstall() -> ResultType<()> {
@@ -1413,6 +1445,9 @@ fn get_uninstall(kill_self: bool) -> String {
}
pub fn uninstall_me(kill_self: bool) -> ResultType<()> {
if crate::platform::is_win_10_or_greater() {
remote_printer::uninstall_printer(&crate::get_app_name());
}
run_cmds(get_uninstall(kill_self), true, "uninstall")
}
@@ -1570,9 +1605,18 @@ pub fn bootstrap() -> bool {
*config::EXE_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
}
#[cfg(debug_assertions)]
{
true
}
#[cfg(not(debug_assertions))]
{
// This function will cause `'sciter.dll' was not found neither in PATH nor near the current executable.` when debugging RustDesk.
set_safe_load_dll()
}
}
#[cfg(not(debug_assertions))]
fn set_safe_load_dll() -> bool {
if !unsafe { set_default_dll_directories() } {
return false;
@@ -1589,6 +1633,7 @@ fn set_safe_load_dll() -> bool {
}
// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-setdefaultdlldirectories
#[cfg(not(debug_assertions))]
unsafe fn set_default_dll_directories() -> bool {
let module = LoadLibraryExW(
wide_string("Kernel32.dll").as_ptr(),
@@ -2728,3 +2773,119 @@ pub mod reg_display_settings {
}
}
}
pub fn get_printer_names() -> ResultType<Vec<String>> {
let mut needed_bytes = 0;
let mut returned_count = 0;
unsafe {
// First call to get required buffer size
EnumPrintersW(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
std::ptr::null_mut(),
1,
std::ptr::null_mut(),
0,
&mut needed_bytes,
&mut returned_count,
);
let mut buffer = vec![0u8; needed_bytes as usize];
if EnumPrintersW(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
std::ptr::null_mut(),
1,
buffer.as_mut_ptr() as *mut _,
needed_bytes,
&mut needed_bytes,
&mut returned_count,
) == 0
{
return Err(anyhow!("Failed to enumerate printers"));
}
let ptr = buffer.as_ptr() as *const PRINTER_INFO_1W;
let printers = std::slice::from_raw_parts(ptr, returned_count as usize);
Ok(printers
.iter()
.filter_map(|p| {
let name = p.pName;
if !name.is_null() {
let mut len = 0;
while len < 500 {
if name.add(len).is_null() || *name.add(len) == 0 {
break;
}
len += 1;
}
if len > 0 && len < 500 {
Some(String::from_utf16_lossy(std::slice::from_raw_parts(
name, len,
)))
} else {
None
}
} else {
None
}
})
.collect())
}
}
extern "C" {
fn PrintXPSRawData(printer_name: *const u16, raw_data: *const u8, data_size: c_ulong) -> DWORD;
}
pub fn send_raw_data_to_printer(printer_name: Option<String>, data: Vec<u8>) -> ResultType<()> {
let mut printer_name = printer_name.unwrap_or_default();
if printer_name.is_empty() {
// use GetDefaultPrinter to get the default printer name
let mut needed_bytes = 0;
unsafe {
GetDefaultPrinterW(std::ptr::null_mut(), &mut needed_bytes);
}
if needed_bytes > 0 {
let mut default_printer_name = vec![0u16; needed_bytes as usize];
unsafe {
GetDefaultPrinterW(
default_printer_name.as_mut_ptr() as *mut _,
&mut needed_bytes,
);
}
printer_name = String::from_utf16_lossy(&default_printer_name[..needed_bytes as usize]);
}
} else {
if let Ok(names) = crate::platform::windows::get_printer_names() {
if !names.contains(&printer_name) {
// Don't set the first printer as current printer.
// It may not be the desired printer.
log::error!(
"Printer name \"{}\" not found, ignore the print job",
printer_name
);
bail!("Printer name \"{}\" not found", &printer_name);
}
}
}
if printer_name.is_empty() {
log::error!("Failed to get printer name");
return Err(anyhow!("Failed to get printer name"));
}
let printer_name = wide_string(&printer_name);
unsafe {
let res = PrintXPSRawData(
printer_name.as_ptr(),
data.as_ptr() as *const u8,
data.len() as c_ulong,
);
if res != 0 {
bail!("Failed to send file to printer, see logs in C:\\Windows\\temp\\test_rustdesk.log for more details.");
}
}
Ok(())
}

View File

@@ -70,6 +70,9 @@ mod service;
mod video_qos;
pub mod video_service;
#[cfg(all(target_os = "windows", feature = "flutter"))]
pub mod printer_service;
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
type ConnMap = HashMap<i32, ConnInner>;
@@ -129,6 +132,20 @@ pub fn new() -> ServerPtr {
server.add_service(Box::new(input_service::new_window_focus()));
}
}
#[cfg(all(target_os = "windows", feature = "flutter"))]
{
match printer_service::init(&crate::get_app_name()) {
Ok(()) => {
log::info!("printer service initialized");
server.add_service(Box::new(printer_service::new(
printer_service::NAME.to_owned(),
)));
}
Err(e) => {
log::error!("printer service init failed: {}", e);
}
}
}
Arc::new(RwLock::new(server))
}

View File

@@ -28,7 +28,7 @@ use hbb_common::platform::linux::run_cmds;
use hbb_common::protobuf::EnumOrUnknown;
use hbb_common::{
config::{self, keys, Config, TrustedDevice},
fs::{self, can_enable_overwrite_detection},
fs::{self, can_enable_overwrite_detection, JobType},
futures::{SinkExt, StreamExt},
get_time, get_version_number,
message_proto::{option_message::BoolOption, permission_info::Permission},
@@ -67,7 +67,7 @@ lazy_static::lazy_static! {
static ref LOGIN_FAILURES: [Arc::<Mutex<HashMap<String, (i32, i32, i32)>>>; 2] = Default::default();
static ref SESSIONS: Arc::<Mutex<HashMap<SessionKey, Session>>> = Default::default();
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
pub static ref AUTHED_CONNS: Arc::<Mutex<Vec<(i32, AuthConnType, SessionKey)>>> = Default::default();
pub static ref AUTHED_CONNS: Arc::<Mutex<Vec<AuthedConn>>> = Default::default();
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
static ref WAKELOCK_SENDER: Arc::<Mutex<std::sync::mpsc::Sender<(usize, usize)>>> = Arc::new(Mutex::new(start_wakelock_thread()));
}
@@ -245,6 +245,8 @@ pub struct Connection {
follow_remote_cursor: bool,
follow_remote_window: bool,
multi_ui_session: bool,
tx_from_authed: mpsc::UnboundedSender<ipc::Data>,
printer_data: Vec<(Instant, String, Vec<u8>)>,
}
impl ConnInner {
@@ -309,6 +311,7 @@ impl Connection {
let (tx, mut rx) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
let (tx_video, mut rx_video) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
let (tx_input, _rx_input) = std_mpsc::channel();
let (tx_from_authed, mut rx_from_authed) = mpsc::unbounded_channel::<ipc::Data>();
let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1);
@@ -396,6 +399,8 @@ impl Connection {
delayed_read_dir: None,
#[cfg(target_os = "macos")]
retina: Retina::default(),
tx_from_authed,
printer_data: Vec::new(),
};
let addr = hbb_common::try_into_v4(addr);
if !conn.on_open(addr).await {
@@ -758,6 +763,19 @@ impl Connection {
break;
}
},
Some(data) = rx_from_authed.recv() => {
match data {
#[cfg(all(target_os = "windows", feature = "flutter"))]
ipc::Data::PrinterData(data) => {
if config::Config::get_bool_option(config::keys::OPTION_ENABLE_REMOTE_PRINTER) {
conn.send_printer_request(data).await;
} else {
conn.send_remote_printing_disallowed().await;
}
}
_ => {}
}
}
_ = second_timer.tick() => {
#[cfg(windows)]
conn.portable_check();
@@ -1104,6 +1122,22 @@ impl Connection {
});
}
fn get_files_for_audit(job_type: fs::JobType, mut files: Vec<FileEntry>) -> Vec<(String, i64)> {
files
.drain(..)
.map(|f| {
(
if job_type == fs::JobType::Printer {
"Remote print".to_owned()
} else {
f.name
},
f.size as _,
)
})
.collect()
}
fn post_file_audit(
&self,
r#type: FileAuditType,
@@ -1212,6 +1246,8 @@ impl Connection {
self.inner.id(),
auth_conn_type,
self.session_key(),
self.tx_from_authed.clone(),
self.lr.clone(),
));
self.session_last_recv_time = SESSIONS
.lock()
@@ -2318,7 +2354,15 @@ impl Connection {
}
}
Some(message::Union::FileAction(fa)) => {
if self.file_transfer.is_some() {
let mut handle_fa = self.file_transfer.is_some();
if !handle_fa {
if let Some(file_action::Union::Send(s)) = fa.union.as_ref() {
if JobType::from_proto(s.file_type) == JobType::Printer {
handle_fa = true;
}
}
}
if handle_fa {
if self.delayed_read_dir.is_some() {
if let Some(file_action::Union::ReadDir(rd)) = fa.union {
self.delayed_read_dir = Some((rd.path, rd.include_hidden));
@@ -2375,10 +2419,32 @@ impl Connection {
&self.lr.version,
));
let path = s.path.clone();
let r#type = JobType::from_proto(s.file_type);
let data_source;
match r#type {
JobType::Generic => {
data_source =
fs::DataSource::FilePath(PathBuf::from(&path));
}
JobType::Printer => {
if let Some(pd) =
self.printer_data.iter().find(|(_, p, _)| *p == path)
{
data_source = fs::DataSource::MemoryCursor(
std::io::Cursor::new(pd.2.clone()),
);
self.printer_data.retain(|f| f.1 != path);
} else {
// Ignore this message if the printer data is not found
return true;
}
}
};
match fs::TransferJob::new_read(
id,
r#type,
"".to_string(),
path.clone(),
data_source,
s.file_num,
s.include_hidden,
false,
@@ -2390,19 +2456,21 @@ impl Connection {
Ok(mut job) => {
self.send(fs::new_dir(id, path, job.files().to_vec()))
.await;
let mut files = job.files().to_owned();
let files = job.files().to_owned();
job.is_remote = true;
job.conn_id = self.inner.id();
let job_type = job.r#type;
self.read_jobs.push(job);
self.file_timer =
crate::rustdesk_interval(time::interval(MILLI1));
self.post_file_audit(
FileAuditType::RemoteSend,
&s.path,
files
.drain(..)
.map(|f| (f.name, f.size as _))
.collect(),
if job_type == fs::JobType::Printer {
"Remote print"
} else {
&s.path
},
Self::get_files_for_audit(job_type, files),
json!({}),
);
}
@@ -2433,11 +2501,7 @@ impl Connection {
self.post_file_audit(
FileAuditType::RemoteReceive,
&r.path,
r.files
.to_vec()
.drain(..)
.map(|f| (f.name, f.size as _))
.collect(),
Self::get_files_for_audit(fs::JobType::Generic, r.files),
json!({}),
);
self.file_transferred = true;
@@ -2476,13 +2540,12 @@ impl Connection {
}
Some(file_action::Union::Cancel(c)) => {
self.send_fs(ipc::FS::CancelWrite { id: c.id });
if let Some(job) = fs::get_job_immutable(c.id, &self.read_jobs) {
if let Some(job) = fs::remove_job(c.id, &mut self.read_jobs) {
self.send_to_cm(ipc::Data::FileTransferLog((
"transfer".to_string(),
fs::serialize_transfer_job(job, false, true, ""),
fs::serialize_transfer_job(&job, false, true, ""),
)));
}
fs::remove_job(c.id, &mut self.read_jobs);
}
Some(file_action::Union::SendConfirm(r)) => {
if let Some(job) = fs::get_job(r.id, &mut self.read_jobs) {
@@ -3635,6 +3698,32 @@ impl Connection {
fn try_empty_file_clipboard(&mut self) {
try_empty_clipboard_files(ClipboardSide::Host, self.inner.id());
}
#[cfg(all(target_os = "windows", feature = "flutter"))]
async fn send_printer_request(&mut self, data: Vec<u8>) {
// This path is only used to identify the printer job.
let path = format!("RustDesk://FsJob//Printer/{}", get_time());
let msg = fs::new_send(0, fs::JobType::Printer, path.clone(), 1, false);
self.send(msg).await;
self.printer_data
.retain(|(t, _, _)| t.elapsed().as_secs() < 60);
self.printer_data.push((Instant::now(), path, data));
}
#[cfg(all(target_os = "windows", feature = "flutter"))]
async fn send_remote_printing_disallowed(&mut self) {
let mut msg_out = Message::new();
let res = MessageBox {
msgtype: "custom-nook-nocancel-hasclose".to_owned(),
title: "remote-printing-disallowed-tile-tip".to_owned(),
text: "remote-printing-disallowed-text-tip".to_owned(),
link: "".to_owned(),
..Default::default()
};
msg_out.set_message_box(res);
self.send(msg_out).await;
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@@ -3970,6 +4059,19 @@ fn start_wakelock_thread() -> std::sync::mpsc::Sender<(usize, usize)> {
tx
}
#[cfg(all(target_os = "windows", feature = "flutter"))]
pub fn on_printer_data(data: Vec<u8>) {
crate::server::AUTHED_CONNS
.lock()
.unwrap()
.iter()
.filter(|c| c.printer)
.next()
.map(|c| {
c.sender.send(Data::PrinterData(data)).ok();
});
}
#[cfg(windows)]
pub struct PortableState {
pub last_uac: bool,
@@ -4103,6 +4205,14 @@ impl Retina {
}
}
pub struct AuthedConn {
pub conn_id: i32,
pub conn_type: AuthConnType,
pub session_key: SessionKey,
pub sender: mpsc::UnboundedSender<Data>,
pub printer: bool,
}
mod raii {
// ALIVE_CONNS: all connections, including unauthorized connections
// AUTHED_CONNS: all authorized connections
@@ -4127,11 +4237,23 @@ mod raii {
pub struct AuthedConnID(i32, AuthConnType);
impl AuthedConnID {
pub fn new(conn_id: i32, conn_type: AuthConnType, session_key: SessionKey) -> Self {
AUTHED_CONNS
.lock()
.unwrap()
.push((conn_id, conn_type, session_key));
pub fn new(
conn_id: i32,
conn_type: AuthConnType,
session_key: SessionKey,
sender: mpsc::UnboundedSender<Data>,
lr: LoginRequest,
) -> Self {
let printer = conn_type == crate::server::AuthConnType::Remote
&& crate::is_support_remote_print(&lr.version)
&& lr.my_platform == whoami::Platform::Windows.to_string();
AUTHED_CONNS.lock().unwrap().push(AuthedConn {
conn_id,
conn_type,
session_key,
sender,
printer,
});
Self::check_wake_lock();
use std::sync::Once;
static _ONCE: Once = Once::new();
@@ -4153,7 +4275,7 @@ mod raii {
.lock()
.unwrap()
.iter()
.filter(|c| c.1 == AuthConnType::Remote)
.filter(|c| c.conn_type == AuthConnType::Remote)
.count();
allow_err!(WAKELOCK_SENDER
.lock()
@@ -4166,7 +4288,7 @@ mod raii {
.lock()
.unwrap()
.iter()
.filter(|c| c.1 != AuthConnType::PortForward)
.filter(|c| c.conn_type != AuthConnType::PortForward)
.count()
}
@@ -4179,16 +4301,16 @@ mod raii {
.lock()
.unwrap()
.iter()
.any(|c| c.0 == conn_id && c.1 == AuthConnType::Remote);
.any(|c| c.conn_id == conn_id && c.conn_type == AuthConnType::Remote);
// If there are 2 connections with the same peer_id and session_id, a remote connection and a file transfer or port forward connection,
// If any of the connections is closed allowing retry, this will not be called;
// If the file transfer/port forward connection is closed with no retry, the session should be kept for remote control menu action;
// If the remote connection is closed with no retry, keep the session is not reasonable in case there is a retry button in the remote side, and ignore network fluctuations.
let another_remote = AUTHED_CONNS
.lock()
.unwrap()
.iter()
.any(|c| c.0 != conn_id && c.2 == key && c.1 == AuthConnType::Remote);
let another_remote = AUTHED_CONNS.lock().unwrap().iter().any(|c| {
c.conn_id != conn_id
&& c.session_key == key
&& c.conn_type == AuthConnType::Remote
});
if is_remote || !another_remote {
lock.remove(&key);
log::info!("remove session");
@@ -4256,12 +4378,12 @@ mod raii {
.unwrap()
.on_connection_close(self.0);
}
AUTHED_CONNS.lock().unwrap().retain(|c| c.0 != self.0);
AUTHED_CONNS.lock().unwrap().retain(|c| c.conn_id != self.0);
let remote_count = AUTHED_CONNS
.lock()
.unwrap()
.iter()
.filter(|c| c.1 == AuthConnType::Remote)
.filter(|c| c.conn_type == AuthConnType::Remote)
.count();
if remote_count == 0 {
#[cfg(any(target_os = "windows", target_os = "linux"))]

View File

@@ -505,7 +505,7 @@ pub fn try_stop_record_cursor_pos() {
.lock()
.unwrap()
.iter()
.filter(|c| c.1 == AuthConnType::Remote)
.filter(|c| c.conn_type == AuthConnType::Remote)
.count();
if remote_count > 0 {
return;

View File

@@ -812,7 +812,7 @@ pub mod client {
.lock()
.unwrap()
.iter()
.filter(|c| c.1 == crate::server::AuthConnType::Remote)
.filter(|c| c.conn_type == crate::server::AuthConnType::Remote)
.count();
stream.send(&Data::DataPortableService(ConnCount(Some(remote_count)))).await.ok();
}

View File

@@ -0,0 +1,163 @@
use super::service::{EmptyExtraFieldService, GenericService, Service};
use hbb_common::{bail, dlopen::symbor::Library, log, ResultType};
use std::{
sync::{Arc, Mutex},
thread,
time::Duration,
};
pub const NAME: &'static str = "remote-printer";
const LIB_NAME_PRINTER_DRIVER_ADAPTER: &str = "printer_driver_adapter";
// Return 0 if success, otherwise return error code.
pub type Init = fn(tag_name: *const i8) -> i32;
pub type Uninit = fn();
// dur_mills: Get the file generated in the last `dur_mills` milliseconds.
// data: The raw prn data, xps format.
// data_len: The length of the raw prn data.
pub type GetPrnData = fn(dur_mills: u32, data: *mut *mut i8, data_len: *mut u32);
macro_rules! make_lib_wrapper {
($($field:ident : $tp:ty),+) => {
struct LibWrapper {
_lib: Option<Library>,
$($field: Option<$tp>),+
}
impl LibWrapper {
fn new() -> Self {
let lib_name = match get_lib_name() {
Ok(name) => name,
Err(e) => {
log::warn!("Failed to get lib name, {}", e);
return Self {
_lib: None,
$( $field: None ),+
};
}
};
let lib = match Library::open(&lib_name) {
Ok(lib) => Some(lib),
Err(e) => {
log::warn!("Failed to load library {}, {}", &lib_name, e);
None
}
};
$(let $field = if let Some(lib) = &lib {
match unsafe { lib.symbol::<$tp>(stringify!($field)) } {
Ok(m) => {
log::info!("method found {}", stringify!($field));
Some(*m)
},
Err(e) => {
log::warn!("Failed to load func {}, {}", stringify!($field), e);
None
}
}
} else {
None
};)+
Self {
_lib: lib,
$( $field ),+
}
}
}
impl Default for LibWrapper {
fn default() -> Self {
Self::new()
}
}
}
}
make_lib_wrapper!(
init: Init,
uninit: Uninit,
get_prn_data: GetPrnData
);
lazy_static::lazy_static! {
static ref LIB_WRAPPER: Arc<Mutex<LibWrapper>> = Default::default();
}
fn get_lib_name() -> ResultType<String> {
let exe_file = std::env::current_exe()?;
if let Some(cur_dir) = exe_file.parent() {
let dll_name = format!("{}.dll", LIB_NAME_PRINTER_DRIVER_ADAPTER);
let full_path = cur_dir.join(dll_name);
if !full_path.exists() {
bail!("{} not found", full_path.to_string_lossy().as_ref());
} else {
Ok(full_path.to_string_lossy().into_owned())
}
} else {
bail!(
"Invalid exe parent for {}",
exe_file.to_string_lossy().as_ref()
);
}
}
pub fn init(app_name: &str) -> ResultType<()> {
let lib_wrapper = LIB_WRAPPER.lock().unwrap();
let Some(fn_init) = lib_wrapper.init.as_ref() else {
bail!("Failed to load func init");
};
let tag_name = std::ffi::CString::new(app_name)?;
let ret = fn_init(tag_name.as_ptr());
if ret != 0 {
bail!("Failed to init printer driver");
}
Ok(())
}
pub fn uninit() {
let lib_wrapper = LIB_WRAPPER.lock().unwrap();
if let Some(fn_uninit) = lib_wrapper.uninit.as_ref() {
fn_uninit();
}
}
fn get_prn_data(dur_mills: u32) -> ResultType<Vec<u8>> {
let lib_wrapper = LIB_WRAPPER.lock().unwrap();
if let Some(fn_get_prn_data) = lib_wrapper.get_prn_data.as_ref() {
let mut data = std::ptr::null_mut();
let mut data_len = 0u32;
fn_get_prn_data(dur_mills, &mut data, &mut data_len);
if data.is_null() || data_len == 0 {
return Ok(Vec::new());
}
let bytes =
Vec::from(unsafe { std::slice::from_raw_parts(data as *const u8, data_len as usize) });
unsafe {
hbb_common::libc::free(data as *mut std::ffi::c_void);
}
Ok(bytes)
} else {
bail!("Failed to load func get_prn_file");
}
}
pub fn new(name: String) -> GenericService {
let svc = EmptyExtraFieldService::new(name, false);
GenericService::run(&svc.clone(), run);
svc.sp
}
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
while sp.ok() {
let bytes = get_prn_data(1000)?;
if !bytes.is_empty() {
log::info!("Got prn data, data len: {}", bytes.len());
crate::server::on_printer_data(bytes);
}
thread::sleep(Duration::from_millis(300));
}
Ok(())
}

View File

@@ -69,8 +69,6 @@ function getExt(name) {
return "";
}
var jobIdCounter = 1;
class JobTable: Reactor.Component {
this var jobs = [];
this var job_map = {};
@@ -126,8 +124,7 @@ class JobTable: Reactor.Component {
}
if (!to) return;
to += handler.get_path_sep(!is_remote) + getFileName(is_remote, path);
var id = jobIdCounter;
jobIdCounter += 1;
var id = handler.get_next_job_id();
this.jobs.push({ type: "transfer",
id: id, path: path, to: to,
include_hidden: show_hidden,
@@ -135,7 +132,7 @@ class JobTable: Reactor.Component {
is_last: false
});
this.job_map[id] = this.jobs[this.jobs.length - 1];
handler.send_files(id, path, to, 0, show_hidden, is_remote);
handler.send_files(id, 0, path, to, 0, show_hidden, is_remote);
var self = this;
self.timer(30ms, function() { self.update(); });
}
@@ -147,8 +144,8 @@ class JobTable: Reactor.Component {
is_remote: is_remote, is_last: true, file_num: file_num };
this.jobs.push(job);
this.job_map[id] = this.jobs[this.jobs.length - 1];
jobIdCounter = id + 1;
handler.add_job(id, path, to, file_num, show_hidden, is_remote);
handler.update_next_job_id(id + 1);
handler.add_job(id, 0, path, to, file_num, show_hidden, is_remote);
stdout.println(JSON.stringify(job));
}
@@ -162,16 +159,14 @@ class JobTable: Reactor.Component {
}
function addDelDir(path, is_remote) {
var id = jobIdCounter;
jobIdCounter += 1;
var id = handler.get_next_job_id();
this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote });
this.job_map[id] = this.jobs[this.jobs.length - 1];
this.update();
}
function addDelFile(path, is_remote) {
var id = jobIdCounter;
jobIdCounter += 1;
var id = handler.get_next_job_id();
this.jobs.push({ type: "del-file", id: id, path: path, is_remote: is_remote });
this.job_map[id] = this.jobs[this.jobs.length - 1];
this.update();
@@ -552,9 +547,9 @@ class FolderView : Reactor.Component {
return;
}
var path = me.joinPath(name);
handler.create_dir(jobIdCounter, path, me.is_remote);
create_dir_jobs[jobIdCounter] = { is_remote: me.is_remote, path: path };
jobIdCounter += 1;
var id = handler.get_next_job_id();
handler.create_dir(id, path, me.is_remote);
create_dir_jobs[id] = { is_remote: me.is_remote, path: path };
});
}

View File

@@ -317,6 +317,9 @@ class MsgboxComponent: Reactor.Component {
if (this.type == "multiple-sessions-nocancel") {
values.sid = (this.$$(select))[0].value;
}
if (this.type == "remote-printer-selector") {
values.name = (this.$$(select))[0].value;
}
return values;
}

41
src/ui/printer.tis Normal file
View File

@@ -0,0 +1,41 @@
include "sciter:reactor.tis";
handler.printerRequest = function(id, path) {
show_printer_selector(id, path);
};
function show_printer_selector(id, path)
{
var names = handler.get_printer_names();
msgbox("remote-printer-selector", "Incoming Print Job", <PrinterComponent names={names} />, "", function(res=null) {
if (res && res.name) {
handler.on_printer_selected(id, path, res.name);
}
}, 180);
}
class PrinterComponent extends Reactor.Component {
this var names = [];
this var jobTip = translate("print-incoming-job-confirm-tip");
function this(params) {
if (params && params.names) {
this.names = params.names;
}
}
function render() {
return <div>
<div>{translate("print-incoming-job-confirm-tip")}</div>
<div style="margin-top: 1em;" />
<div>
<select style="width: 450; margin: 1em 0; font-size: 1.15em;">
{this.names.map(function(name) {
return <option value={name}>{name}</option>;
})}
</select>
</div>
<div style="margin-top: 1em;" />
</div>;
}
}

View File

@@ -15,6 +15,7 @@
include "port_forward.tis";
include "grid.tis";
include "header.tis";
include "printer.tis";
</script>
</head>
<header>

View File

@@ -379,6 +379,10 @@ impl InvokeUiSession for SciterHandler {
fn update_record_status(&self, start: bool) {
self.call("updateRecordStatus", &make_args!(start));
}
fn printer_request(&self, id: i32, path: String) {
self.call("printerRequest", &make_args!(id, path));
}
}
pub struct SciterSession(Session<SciterHandler>);
@@ -491,6 +495,8 @@ impl sciter::EventHandler for SciterSession {
fn get_chatbox();
fn get_icon();
fn get_home_dir();
fn get_next_job_id();
fn update_next_job_id(i32);
fn read_dir(String, bool);
fn remove_dir(i32, String, bool);
fn create_dir(i32, String, bool);
@@ -502,8 +508,8 @@ impl sciter::EventHandler for SciterSession {
fn confirm_delete_files(i32, i32);
fn set_no_confirm(i32);
fn cancel_job(i32);
fn send_files(i32, String, String, i32, bool, bool);
fn add_job(i32, String, String, i32, bool, bool);
fn send_files(i32, i32, String, String, i32, bool, bool);
fn add_job(i32, i32, String, String, i32, bool, bool);
fn resume_job(i32, bool);
fn get_platform(bool);
fn get_path_sep(bool);
@@ -541,6 +547,8 @@ impl sciter::EventHandler for SciterSession {
fn set_selected_windows_session_id(String);
fn is_recording();
fn has_file_clipboard();
fn get_printer_names();
fn on_printer_selected(i32, String, String);
}
}
@@ -842,6 +850,22 @@ impl SciterSession {
fn version_cmp(&self, v1: String, v2: String) -> i32 {
(hbb_common::get_version_number(&v1) - hbb_common::get_version_number(&v2)) as i32
}
fn get_printer_names(&self) -> Value {
#[cfg(target_os = "windows")]
let printer_names = crate::platform::windows::get_printer_names().unwrap_or_default();
#[cfg(not(target_os = "windows"))]
let printer_names: Vec<String> = vec![];
let mut v = Value::array(0);
for name in printer_names {
v.push(name);
}
v
}
fn on_printer_selected(&self, id: i32, path: String, printer_name: String) {
self.printer_response(id, path, printer_name);
}
}
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {

View File

@@ -744,6 +744,8 @@ async fn handle_fs(
tx: &UnboundedSender<Data>,
tx_log: Option<&UnboundedSender<String>>,
) {
use std::path::PathBuf;
use hbb_common::fs::serialize_transfer_job;
match fs {
@@ -785,8 +787,9 @@ async fn handle_fs(
// dummy remote, show_hidden, is_remote
let mut job = fs::TransferJob::new_write(
id,
fs::JobType::Generic,
"".to_string(),
path,
fs::DataSource::FilePath(PathBuf::from(&path)),
file_num,
false,
false,
@@ -805,27 +808,24 @@ async fn handle_fs(
write_jobs.push(job);
}
ipc::FS::CancelWrite { id } => {
if let Some(job) = fs::get_job(id, write_jobs) {
if let Some(job) = fs::remove_job(id, write_jobs) {
job.remove_download_file();
tx_log.map(|tx: &UnboundedSender<String>| {
tx.send(serialize_transfer_job(job, false, true, ""))
tx.send(serialize_transfer_job(&job, false, true, ""))
});
fs::remove_job(id, write_jobs);
}
}
ipc::FS::WriteDone { id, file_num } => {
if let Some(job) = fs::get_job(id, write_jobs) {
if let Some(job) = fs::remove_job(id, write_jobs) {
job.modify_time();
send_raw(fs::new_done(id, file_num), tx);
tx_log.map(|tx| tx.send(serialize_transfer_job(job, true, false, "")));
fs::remove_job(id, write_jobs);
tx_log.map(|tx| tx.send(serialize_transfer_job(&job, true, false, "")));
}
}
ipc::FS::WriteError { id, file_num, err } => {
if let Some(job) = fs::get_job(id, write_jobs) {
tx_log.map(|tx| tx.send(serialize_transfer_job(job, false, false, &err)));
if let Some(job) = fs::remove_job(id, write_jobs) {
tx_log.map(|tx| tx.send(serialize_transfer_job(&job, false, false, &err)));
send_raw(fs::new_error(job.id(), err, file_num), tx);
fs::remove_job(job.id(), write_jobs);
}
}
ipc::FS::WriteBlock {
@@ -871,7 +871,8 @@ async fn handle_fs(
..Default::default()
};
if let Some(file) = job.files().get(file_num as usize) {
let path = get_string(&job.join(&file.name));
if let fs::DataSource::FilePath(p) = &job.data_source {
let path = get_string(&fs::TransferJob::join(p, &file.name));
match is_write_need_confirmation(&path, &digest) {
Ok(digest_result) => {
match digest_result {
@@ -902,6 +903,7 @@ async fn handle_fs(
}
}
}
}
ipc::FS::Rename { id, path, new_name } => {
rename_file(path, new_name, id, tx).await;
}

Some files were not shown because too many files have changed in this diff Show More