From 4b9805b0f3211569680cab2f9b23e1966f237c89 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 31 Aug 2022 18:41:55 +0800 Subject: [PATCH] flutter_desktop: custom image quality Signed-off-by: fufesou --- flutter/lib/common.dart | 86 +++++++------ flutter/lib/consts.dart | 7 +- .../desktop/pages/connection_tab_page.dart | 3 +- flutter/lib/desktop/pages/remote_page.dart | 15 +-- flutter/lib/desktop/widgets/popup_menu.dart | 116 ++++++++++++------ .../lib/desktop/widgets/remote_menubar.dart | 89 ++++++++++---- flutter/lib/models/model.dart | 15 ++- src/flutter_ffi.rs | 30 +++-- 8 files changed, 240 insertions(+), 121 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 6ba917bf3..e8632caaa 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -427,7 +427,45 @@ class CustomAlertDialog extends StatelessWidget { void msgBox( String type, String title, String text, OverlayDialogManager dialogManager, {bool? hasCancel}) { - var wrap = (String text, void Function() onPressed) => ButtonTheme( + dialogManager.dismissAll(); + List buttons = []; + if (type != "connecting" && type != "success" && !type.contains("nook")) { + buttons.insert( + 0, + getMsgBoxButton(translate('OK'), () { + dialogManager.dismissAll(); + // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 + if (!type.contains("custom")) { + closeConnection(); + } + })); + } + hasCancel ??= !type.contains("error") && + !type.contains("nocancel") && + type != "restarting"; + if (hasCancel) { + buttons.insert( + 0, + getMsgBoxButton(translate('Cancel'), () { + dialogManager.dismissAll(); + })); + } + // TODO: test this button + if (type.contains("hasclose")) { + buttons.insert( + 0, + getMsgBoxButton(translate('Close'), () { + dialogManager.dismissAll(); + })); + } + dialogManager.show((setState, close) => CustomAlertDialog( + title: Text(translate(title), style: TextStyle(fontSize: 21)), + content: Text(translate(text), style: TextStyle(fontSize: 15)), + actions: buttons)); +} + +Widget getMsgBoxButton(String text, void Function() onPressed) { + return ButtonTheme( padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, //limits the touch area to the button area @@ -439,44 +477,14 @@ void msgBox( onPressed: onPressed, child: Text(translate(text), style: TextStyle(color: MyTheme.accent)))); +} +void msgBoxCommon(OverlayDialogManager dialogManager, String title, + Widget content, List buttons) { dialogManager.dismissAll(); - List buttons = []; - if (type != "connecting" && type != "success" && type.indexOf("nook") < 0) { - buttons.insert( - 0, - wrap(translate('OK'), () { - dialogManager.dismissAll(); - // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 - if (type.indexOf("custom") < 0) { - closeConnection(); - } - })); - } - if (hasCancel == null) { - // hasCancel = type != 'error'; - hasCancel = type.indexOf("error") < 0 && - type.indexOf("nocancel") < 0 && - type != "restarting"; - } - if (hasCancel) { - buttons.insert( - 0, - wrap(translate('Cancel'), () { - dialogManager.dismissAll(); - })); - } - // TODO: test this button - if (type.indexOf("hasclose") >= 0) { - buttons.insert( - 0, - wrap(translate('Close'), () { - dialogManager.dismissAll(); - })); - } dialogManager.show((setState, close) => CustomAlertDialog( title: Text(translate(title), style: TextStyle(fontSize: 21)), - content: Text(translate(text), style: TextStyle(fontSize: 15)), + content: content, actions: buttons)); } @@ -495,13 +503,13 @@ const G = M * K; String readableFileSize(double size) { if (size < K) { - return size.toStringAsFixed(2) + " B"; + return "${size.toStringAsFixed(2)} B"; } else if (size < M) { - return (size / K).toStringAsFixed(2) + " KB"; + return "${(size / K).toStringAsFixed(2)} KB"; } else if (size < G) { - return (size / M).toStringAsFixed(2) + " MB"; + return "${(size / M).toStringAsFixed(2)} MB"; } else { - return (size / G).toStringAsFixed(2) + " GB"; + return "${(size / G).toStringAsFixed(2)} GB"; } } diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 6c67e2ab9..95a6faaa2 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -8,7 +8,10 @@ const String kAppTypeDesktopPortForward = "port forward"; const String kTabLabelHomePage = "Home"; const String kTabLabelSettingPage = "Settings"; -const int kDefaultDisplayWidth = 1280; -const int kDefaultDisplayHeight = 720; +const int kMobileDefaultDisplayWidth = 720; +const int kMobileDefaultDisplayHeight = 1280; + +const int kDesktopDefaultDisplayWidth = 1080; +const int kDesktopDefaultDisplayHeight = 720; const kInvalidValueStr = "InvalidValueStr"; diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index 4175bd11b..5687c5c7e 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -61,6 +61,7 @@ class _ConnectionTabPageState extends State { final args = jsonDecode(call.arguments); final id = args['id']; window_on_top(windowId()); + ConnectionTypeState.init(id); tabController.add(TabInfo( key: id, label: id, @@ -108,7 +109,7 @@ class _ConnectionTabPageState extends State { }, tabBuilder: (key, icon, label, themeConf) => Obx(() { final connectionType = ConnectionTypeState.find(key); - if (!ConnectionTypeState.find(key).isValid()) { + if (!connectionType.isValid()) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f723b17d0..16c04f572 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -689,11 +689,11 @@ class ImagePaint extends StatelessWidget { width: c.getDisplayWidth() * s, height: c.getDisplayHeight() * s, child: CustomPaint( - painter: new ImagePainter(image: m.image, x: 0, y: 0, scale: s), + painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), )); return Center( child: NotificationListener( - onNotification: (_notification) { + onNotification: (notification) { final percentX = _horizontal.position.extentBefore / (_horizontal.position.extentBefore + _horizontal.position.extentInside + @@ -716,8 +716,8 @@ class ImagePaint extends StatelessWidget { width: c.size.width, height: c.size.height, child: CustomPaint( - painter: new ImagePainter( - image: m.image, x: c.x / s, y: c.y / s, scale: s), + painter: + ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s), )); return _buildListener(imageWidget); } @@ -771,7 +771,7 @@ class CursorPaint extends StatelessWidget { // final adjust = m.adjustForKeyboard(); var s = c.scale; return CustomPaint( - painter: new ImagePainter( + painter: ImagePainter( image: m.image, x: m.x * s - m.hotx + c.x, y: m.y * s - m.hoty + c.y, @@ -796,15 +796,16 @@ class ImagePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { if (image == null) return; + if (x.isNaN || y.isNaN) return; canvas.scale(scale, scale); // https://github.com/flutter/flutter/issues/76187#issuecomment-784628161 // https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html - var paint = new Paint(); + var paint = Paint(); paint.filterQuality = FilterQuality.medium; if (scale > 10.00000) { paint.filterQuality = FilterQuality.high; } - canvas.drawImage(image!, new Offset(x, y), paint); + canvas.drawImage(image!, Offset(x, y), paint); } @override diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 6dbe4f8cd..3512d640f 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -97,6 +97,9 @@ class MenuConfig { } abstract class MenuEntryBase { + bool dismissOnClicked; + + MenuEntryBase({this.dismissOnClicked = false}); List> build(BuildContext context, MenuConfig conf); } @@ -112,9 +115,19 @@ class MenuEntryDivider extends MenuEntryBase { } } -typedef RadioOptionsGetter = List> Function(); +class MenuEntryRadioOption { + String text; + String value; + bool dismissOnClicked; + + MenuEntryRadioOption( + {required this.text, required this.value, this.dismissOnClicked = false}); +} + +typedef RadioOptionsGetter = List Function(); typedef RadioCurOptionGetter = Future Function(); -typedef RadioOptionSetter = Future Function(String); +typedef RadioOptionSetter = Future Function( + String oldValue, String newValue); class MenuEntryRadioUtils {} @@ -129,24 +142,28 @@ class MenuEntryRadios extends MenuEntryBase { {required this.text, required this.optionsGetter, required this.curOptionGetter, - required this.optionSetter}) { + required this.optionSetter, + dismissOnClicked = false}) + : super(dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await curOptionGetter(); }(); } - List> get options => optionsGetter(); + List get options => optionsGetter(); RxString get curOption => _curOption; setOption(String option) async { - await optionSetter(option); - final opt = await curOptionGetter(); - if (_curOption.value != opt) { - _curOption.value = opt; + await optionSetter(_curOption.value, option); + if (_curOption.value != option) { + final opt = await curOptionGetter(); + if (_curOption.value != opt) { + _curOption.value = opt; + } } } mod_menu.PopupMenuEntry _buildMenuItem( - BuildContext context, MenuConfig conf, Tuple2 opt) { + BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) { return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, @@ -157,7 +174,7 @@ class MenuEntryRadios extends MenuEntryBase { child: Row( children: [ Text( - opt.item1, + opt.text, style: const TextStyle( color: Colors.black, fontSize: MenuConfig.fontSize, @@ -169,7 +186,7 @@ class MenuEntryRadios extends MenuEntryBase { child: SizedBox( width: 20.0, height: 20.0, - child: Obx(() => opt.item2 == curOption.value + child: Obx(() => opt.value == curOption.value ? Icon( Icons.check, color: conf.commonColor, @@ -180,9 +197,10 @@ class MenuEntryRadios extends MenuEntryBase { ), ), onPressed: () { - if (opt.item2 != curOption.value) { - setOption(opt.item2); + if (opt.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); } + setOption(opt.value); }, ), ); @@ -206,24 +224,28 @@ class MenuEntrySubRadios extends MenuEntryBase { {required this.text, required this.optionsGetter, required this.curOptionGetter, - required this.optionSetter}) { + required this.optionSetter, + dismissOnClicked = false}) + : super(dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await curOptionGetter(); }(); } - List> get options => optionsGetter(); + List get options => optionsGetter(); RxString get curOption => _curOption; setOption(String option) async { - await optionSetter(option); - final opt = await curOptionGetter(); - if (_curOption.value != opt) { - _curOption.value = opt; + await optionSetter(_curOption.value, option); + if (_curOption.value != option) { + final opt = await curOptionGetter(); + if (_curOption.value != opt) { + _curOption.value = opt; + } } } mod_menu.PopupMenuEntry _buildSecondMenu( - BuildContext context, MenuConfig conf, Tuple2 opt) { + BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) { return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, @@ -234,7 +256,7 @@ class MenuEntrySubRadios extends MenuEntryBase { child: Row( children: [ Text( - opt.item1, + opt.text, style: const TextStyle( color: Colors.black, fontSize: MenuConfig.fontSize, @@ -246,7 +268,7 @@ class MenuEntrySubRadios extends MenuEntryBase { child: SizedBox( width: 20.0, height: 20.0, - child: Obx(() => opt.item2 == curOption.value + child: Obx(() => opt.value == curOption.value ? Icon( Icons.check, color: conf.commonColor, @@ -257,9 +279,10 @@ class MenuEntrySubRadios extends MenuEntryBase { ), ), onPressed: () { - if (opt.item2 != curOption.value) { - setOption(opt.item2); + if (opt.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); } + setOption(opt.value); }, ), ); @@ -303,7 +326,8 @@ typedef SwitchSetter = Future Function(bool); abstract class MenuEntrySwitchBase extends MenuEntryBase { final String text; - MenuEntrySwitchBase({required this.text}); + MenuEntrySwitchBase({required this.text, required dismissOnClicked}) + : super(dismissOnClicked: dismissOnClicked); RxBool get curOption; Future setOption(bool option); @@ -333,11 +357,20 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { alignment: Alignment.centerRight, child: Obx(() => Switch( value: curOption.value, - onChanged: (v) => setOption(v), + onChanged: (v) { + if (super.dismissOnClicked && + Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(v); + }, )), )) ])), onPressed: () { + if (super.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } setOption(!curOption.value); }, ), @@ -352,8 +385,11 @@ class MenuEntrySwitch extends MenuEntrySwitchBase { final RxBool _curOption = false.obs; MenuEntrySwitch( - {required String text, required this.getter, required this.setter}) - : super(text: text) { + {required String text, + required this.getter, + required this.setter, + dismissOnClicked = false}) + : super(text: text, dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await getter(); }(); @@ -379,8 +415,11 @@ class MenuEntrySwitch2 extends MenuEntrySwitchBase { final SwitchSetter setter; MenuEntrySwitch2( - {required String text, required this.getter, required this.setter}) - : super(text: text); + {required String text, + required this.getter, + required this.setter, + dismissOnClicked = false}) + : super(text: text, dismissOnClicked: dismissOnClicked); @override RxBool get curOption => getter(); @@ -394,10 +433,7 @@ class MenuEntrySubMenu extends MenuEntryBase { final String text; final List> entries; - MenuEntrySubMenu({ - required this.text, - required this.entries, - }); + MenuEntrySubMenu({required this.text, required this.entries}); @override List> build( @@ -438,10 +474,11 @@ class MenuEntryButton extends MenuEntryBase { final Widget Function(TextStyle? style) childBuilder; Function() proc; - MenuEntryButton({ - required this.childBuilder, - required this.proc, - }); + MenuEntryButton( + {required this.childBuilder, + required this.proc, + dismissOnClicked = false}) + : super(dismissOnClicked: dismissOnClicked); @override List> build( @@ -461,6 +498,9 @@ class MenuEntryButton extends MenuEntryBase { fontWeight: FontWeight.normal), )), onPressed: () { + if (super.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } proc(); }, ), diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 2e8c7fb63..26789ac4f 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -290,9 +290,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); bind.sessionRefresh(id: widget.id); }, + dismissOnClicked: true, )); } displayMenu.add(MenuEntryButton( @@ -301,9 +301,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); showSetOSPassword(widget.id, false, widget.ffi.dialogManager); }, + dismissOnClicked: true, )); if (!isWebDesktop) { @@ -314,7 +314,6 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); () async { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); @@ -323,6 +322,7 @@ class _RemoteMenubarState extends State { } }(); }, + dismissOnClicked: true, )); } @@ -332,9 +332,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); widget.ffi.cursorModel.reset(); }, + dismissOnClicked: true, )); } @@ -346,9 +346,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); bind.sessionCtrlAltDel(id: widget.id); }, + dismissOnClicked: true, )); } @@ -358,9 +358,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); bind.sessionLockScreen(id: widget.id); }, + dismissOnClicked: true, )); if (pi.platform == 'Windows') { @@ -371,13 +371,13 @@ class _RemoteMenubarState extends State { style: style, )), proc: () { - Navigator.pop(context); RxBool blockInput = BlockInputState.find(widget.id); bind.sessionToggleOption( id: widget.id, value: '${blockInput.value ? "un" : ""}block-input'); blockInput.value = !blockInput.value; }, + dismissOnClicked: true, )); } } @@ -392,9 +392,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager); }, + dismissOnClicked: true, )); } @@ -406,44 +406,54 @@ class _RemoteMenubarState extends State { MenuEntryRadios( text: translate('Ratio'), optionsGetter: () => [ - Tuple2(translate('Scale original'), 'original'), - Tuple2(translate('Scale adaptive'), 'adaptive'), + MenuEntryRadioOption( + text: translate('Scale original'), value: 'original'), + MenuEntryRadioOption( + text: translate('Scale adaptive'), value: 'adaptive'), ], curOptionGetter: () async { return await bind.sessionGetOption( id: widget.id, arg: 'view-style') ?? 'adaptive'; }, - optionSetter: (String v) async { + optionSetter: (String oldValue, String newValue) async { await bind.sessionPeerOption( - id: widget.id, name: "view-style", value: v); + id: widget.id, name: "view-style", value: newValue); widget.ffi.canvasModel.updateViewStyle(); }), MenuEntryDivider(), MenuEntryRadios( text: translate('Scroll Style'), optionsGetter: () => [ - Tuple2(translate('ScrollAuto'), 'scrollauto'), - Tuple2(translate('Scrollbar'), 'scrollbar'), + MenuEntryRadioOption( + text: translate('ScrollAuto'), value: 'scrollauto'), + MenuEntryRadioOption( + text: translate('Scrollbar'), value: 'scrollbar'), ], curOptionGetter: () async { return await bind.sessionGetOption( id: widget.id, arg: 'scroll-style') ?? ''; }, - optionSetter: (String v) async { + optionSetter: (String oldValue, String newValue) async { await bind.sessionPeerOption( - id: widget.id, name: "scroll-style", value: v); + id: widget.id, name: "scroll-style", value: newValue); widget.ffi.canvasModel.updateScrollStyle(); }), MenuEntryDivider(), MenuEntryRadios( text: translate('Image Quality'), optionsGetter: () => [ - Tuple2(translate('Good image quality'), 'best'), - Tuple2(translate('Balanced'), 'balanced'), - Tuple2( - translate('Optimize reaction time'), 'low'), + MenuEntryRadioOption( + text: translate('Good image quality'), value: 'best'), + MenuEntryRadioOption( + text: translate('Balanced'), value: 'balanced'), + MenuEntryRadioOption( + text: translate('Optimize reaction time'), value: 'low'), + MenuEntryRadioOption( + text: translate('Custom'), + value: 'custom', + dismissOnClicked: true), ], curOptionGetter: () async { String quality = @@ -451,8 +461,43 @@ class _RemoteMenubarState extends State { if (quality == '') quality = 'balanced'; return quality; }, - optionSetter: (String v) async { - await bind.sessionSetImageQuality(id: widget.id, value: v); + optionSetter: (String oldValue, String newValue) async { + if (oldValue != newValue) { + await bind.sessionSetImageQuality(id: widget.id, value: newValue); + } + + if (newValue == 'custom') { + final btnCancel = getMsgBoxButton(translate('Cancel'), () { + widget.ffi.dialogManager.dismissAll(); + }); + final quality = + await bind.sessionGetCustomImageQuality(id: widget.id); + final double initValue = quality != null && quality.isNotEmpty + ? quality[0].toDouble() + : 50.0; + // final slider = _ImageCustomQualitySlider( + // id: widget.id, v: RxDouble(initValue)); + final RxDouble sliderValue = RxDouble(initValue); + final slider = Obx(() => Slider( + value: sliderValue.value, + max: 100, + label: sliderValue.value.round().toString(), + onChanged: (double value) { + () async { + await bind.sessionSetCustomImageQuality( + id: widget.id, value: value.toInt()); + final quality = await bind.sessionGetCustomImageQuality( + id: widget.id); + sliderValue.value = + quality != null && quality.isNotEmpty + ? quality[0].toDouble() + : 50.0; + }(); + }, + )); + msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality', + slider, [btnCancel]); + } }), MenuEntryDivider(), MenuEntrySwitch( diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index da06b7fb9..ba9981db6 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -7,6 +7,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/generated_bridge.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/chat_model.dart'; @@ -499,8 +500,8 @@ class CanvasModel with ChangeNotifier { _scale = 1.0; if (style == 'adaptive') { - final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720); - final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280); + final s1 = size.width / getDisplayWidth(); + final s2 = size.height / getDisplayHeight(); _scale = s1 < s2 ? s1 : s2; } @@ -529,11 +530,17 @@ class CanvasModel with ChangeNotifier { } int getDisplayWidth() { - return parent.target?.ffiModel.display.width ?? 1080; + final defaultWidth = (isDesktop || isWebDesktop) + ? kDesktopDefaultDisplayWidth + : kMobileDefaultDisplayWidth; + return parent.target?.ffiModel.display.width ?? defaultWidth; } int getDisplayHeight() { - return parent.target?.ffiModel.display.height ?? 720; + final defaultHeight = (isDesktop || isWebDesktop) + ? kDesktopDefaultDisplayHeight + : kMobileDefaultDisplayHeight; + return parent.target?.ffiModel.display.height ?? defaultHeight; } Size get size { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 167124212..716afacb9 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -143,14 +143,6 @@ pub fn session_get_toggle_option_sync(id: String, arg: String) -> SyncReturn Option { - if let Some(session) = SESSIONS.read().unwrap().get(&id) { - Some(session.get_image_quality()) - } else { - None - } -} - pub fn session_get_option(id: String, arg: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_option(arg)) @@ -190,12 +182,34 @@ pub fn session_toggle_option(id: String, value: String) { } } +pub fn session_get_image_quality(id: String) -> Option { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + Some(session.get_image_quality()) + } else { + None + } +} + pub fn session_set_image_quality(id: String, value: String) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { session.save_image_quality(value); } } +pub fn session_get_custom_image_quality(id: String) -> Option> { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + Some(session.get_custom_image_quality()) + } else { + None + } +} + +pub fn session_set_custom_image_quality(id: String, value: i32) { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + session.set_custom_image_quality(value); + } +} + pub fn session_lock_screen(id: String) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { session.lock_screen();