mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 14:07:28 +08:00
feat: remote printer (#11231)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
34
.github/workflows/flutter-build.yml
vendored
34
.github/workflows/flutter-build.yml
vendored
@@ -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
25
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
48
flutter/lib/models/printer_model.dart
Normal file
48
flutter/lib/models/printer_model.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
Submodule libs/hbb_common updated: 1819875476...9ede5d49f6
11
libs/remote_printer/Cargo.toml
Normal file
11
libs/remote_printer/Cargo.toml
Normal 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"
|
||||
34
libs/remote_printer/src/lib.rs
Normal file
34
libs/remote_printer/src/lib.rs
Normal 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()
|
||||
}
|
||||
202
libs/remote_printer/src/setup/driver.rs
Normal file
202
libs/remote_printer/src/setup/driver.rs
Normal 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(())
|
||||
}
|
||||
99
libs/remote_printer/src/setup/mod.rs
Normal file
99
libs/remote_printer/src/setup/mod.rs
Normal 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())
|
||||
}
|
||||
128
libs/remote_printer/src/setup/port.rs
Normal file
128
libs/remote_printer/src/setup/port.rs
Normal 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(())
|
||||
}
|
||||
161
libs/remote_printer/src/setup/printer.rs
Normal file
161
libs/remote_printer/src/setup/printer.rs
Normal 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(())
|
||||
}
|
||||
94
libs/remote_printer/src/setup/setup.rs
Normal file
94
libs/remote_printer/src/setup/setup.rs
Normal 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));
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ EXPORTS
|
||||
SetPropertyFromConfig
|
||||
AddRegSoftwareSASGeneration
|
||||
RemoveAmyuniIdd
|
||||
InstallPrinter
|
||||
UninstallPrinter
|
||||
|
||||
@@ -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>
|
||||
|
||||
517
res/msi/CustomActions/RemotePrinter.cpp
Normal file
517
res/msi/CustomActions/RemotePrinter.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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="'Y'") AND (NOT CC_CONNECTION_TYPE="outgoing")"/>
|
||||
|
||||
<!-- 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 >= 603 AND PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y"" />
|
||||
<Custom Action="InstallPrinter.SetParam" Before="InstallPrinter" Condition="VersionNT >= 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 >= 603" />
|
||||
|
||||
<Custom Action="TerminateProcesses" Before="RemoveInstallFolder"/>
|
||||
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
|
||||
<Custom Action="TerminateBrokers" Before="RemoveInstallFolder"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = "Y" OR PRINTER = "y"">
|
||||
<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 = "Y" OR PRINTER = "y")">
|
||||
<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 = "Y" OR CREATESTARTMENUSHORTCUTS = "y")" />
|
||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS AND NOT (CREATEDESKTOPSHORTCUTS = 1 OR CREATEDESKTOPSHORTCUTS = "Y" OR CREATEDESKTOPSHORTCUTS = "y")" />
|
||||
<SetProperty Id="PRINTER" Value="" After="RestoreSavedPrinterValue" Sequence="first" Condition="INSTALLPRINTER AND NOT (INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y")" />
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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-->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 l’utilisation 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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
163
src/server/printer_service.rs
Normal file
163
src/server/printer_service.rs
Normal 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(())
|
||||
}
|
||||
@@ -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 };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
41
src/ui/printer.tis
Normal 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>;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
include "port_forward.tis";
|
||||
include "grid.tis";
|
||||
include "header.tis";
|
||||
include "printer.tis";
|
||||
</script>
|
||||
</head>
|
||||
<header>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user