feat: remote printer (#11231)

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

View File

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

View File

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