From c5d3c7f39036120657f2778197d19b045da2e2c3 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:28:05 +0800 Subject: [PATCH] Feat/android more actions (#8496) * feat: android volume and power actions Signed-off-by: fufesou * Add translations and refact action menus Signed-off-by: fufesou * Remove divider Signed-off-by: fufesou * fix: recover deleted translations Signed-off-by: fufesou --------- Signed-off-by: fufesou --- .../com/carriez/flutter_hbb/InputService.kt | 63 +++++++++++++-- .../flutter_hbb/KeyboardKeyEventMapper.kt | 6 ++ .../carriez/flutter_hbb/VolumeController.kt | 78 +++++++++++++++++++ flutter/lib/common.dart | 10 +-- flutter/lib/common/widgets/toolbar.dart | 31 ++++++++ flutter/lib/consts.dart | 2 + .../lib/desktop/widgets/remote_toolbar.dart | 35 +++++++++ flutter/lib/models/input_model.dart | 23 ++++++ flutter/lib/models/model.dart | 2 +- libs/hbb_common/protos/message.proto | 4 + src/flutter.rs | 2 +- src/lang/ar.rs | 5 ++ src/lang/be.rs | 5 ++ src/lang/bg.rs | 5 ++ src/lang/ca.rs | 5 ++ src/lang/cn.rs | 5 ++ src/lang/cs.rs | 5 ++ src/lang/da.rs | 5 ++ src/lang/de.rs | 5 ++ src/lang/el.rs | 5 ++ src/lang/eo.rs | 5 ++ src/lang/es.rs | 5 ++ src/lang/et.rs | 5 ++ src/lang/fa.rs | 5 ++ src/lang/fr.rs | 5 ++ src/lang/he.rs | 5 ++ src/lang/hr.rs | 5 ++ src/lang/hu.rs | 5 ++ src/lang/id.rs | 5 ++ src/lang/it.rs | 5 ++ src/lang/ja.rs | 5 ++ src/lang/ko.rs | 5 ++ src/lang/kz.rs | 5 ++ src/lang/lt.rs | 5 ++ src/lang/lv.rs | 5 ++ src/lang/nb.rs | 5 ++ src/lang/nl.rs | 5 ++ src/lang/pl.rs | 5 ++ src/lang/pt_PT.rs | 5 ++ src/lang/ptbr.rs | 5 ++ src/lang/ro.rs | 5 ++ src/lang/ru.rs | 5 ++ src/lang/sk.rs | 5 ++ src/lang/sl.rs | 5 ++ src/lang/sq.rs | 5 ++ src/lang/sr.rs | 5 ++ src/lang/sv.rs | 5 ++ src/lang/template.rs | 5 ++ src/lang/th.rs | 5 ++ src/lang/tr.rs | 5 ++ src/lang/tw.rs | 5 ++ src/lang/ua.rs | 5 ++ src/lang/vn.rs | 5 ++ src/ui_session_interface.rs | 49 +++++++++++- 54 files changed, 500 insertions(+), 15 deletions(-) create mode 100644 flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt index 47c8f302c..3fcc72df3 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt @@ -18,7 +18,9 @@ import android.widget.EditText import android.view.accessibility.AccessibilityEvent import android.view.ViewGroup.LayoutParams import android.view.accessibility.AccessibilityNodeInfo +import android.view.KeyEvent as KeyEventAndroid import android.graphics.Rect +import android.media.AudioManager import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS @@ -75,6 +77,8 @@ class InputService : AccessibilityService() { private var fakeEditTextForTextStateCalculation: EditText? = null + private val volumeController: VolumeController by lazy { VolumeController(applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager) } + @RequiresApi(Build.VERSION_CODES.N) fun onMouseInput(mask: Int, _x: Int, _y: Int) { val x = max(0, _x) @@ -294,6 +298,18 @@ class InputService : AccessibilityService() { Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit") + var ke: KeyEventAndroid? = null + if (Build.VERSION.SDK_INT < 33 || textToCommit == null) { + ke = KeyEventConverter.toAndroidKeyEvent(keyEvent) + } + ke?.let { event -> + if (tryHandleVolumeKeyEvent(event)) { + return + } else if (tryHandlePowerKeyEvent(event)) { + return + } + } + if (Build.VERSION.SDK_INT >= 33) { getInputMethod()?.let { inputMethod -> inputMethod.getCurrentInputConnection()?.let { inputConnection -> @@ -302,7 +318,7 @@ class InputService : AccessibilityService() { inputConnection.commitText(text, 1, null) } } else { - KeyEventConverter.toAndroidKeyEvent(keyEvent).let { event -> + ke?.let { event -> inputConnection.sendKeyEvent(event) } } @@ -311,7 +327,7 @@ class InputService : AccessibilityService() { } else { val handler = Handler(Looper.getMainLooper()) handler.post { - KeyEventConverter.toAndroidKeyEvent(keyEvent)?.let { event -> + ke?.let { event -> val possibleNodes = possibleAccessibiltyNodes() Log.d(logTag, "possibleNodes:$possibleNodes") for (item in possibleNodes) { @@ -325,6 +341,43 @@ class InputService : AccessibilityService() { } } + private fun tryHandleVolumeKeyEvent(event: KeyEventAndroid): Boolean { + when (event.keyCode) { + KeyEventAndroid.KEYCODE_VOLUME_UP -> { + if (event.action == KeyEventAndroid.ACTION_DOWN) { + volumeController.raiseVolume(null, true, AudioManager.STREAM_SYSTEM) + } + return true + } + KeyEventAndroid.KEYCODE_VOLUME_DOWN -> { + if (event.action == KeyEventAndroid.ACTION_DOWN) { + volumeController.lowerVolume(null, true, AudioManager.STREAM_SYSTEM) + } + return true + } + KeyEventAndroid.KEYCODE_VOLUME_MUTE -> { + if (event.action == KeyEventAndroid.ACTION_DOWN) { + volumeController.toggleMute(true, AudioManager.STREAM_SYSTEM) + } + return true + } + else -> { + return false + } + } + } + + private fun tryHandlePowerKeyEvent(event: KeyEventAndroid): Boolean { + if (event.keyCode == KeyEventAndroid.KEYCODE_POWER) { + // Perform power dialog action when action is up + if (event.action == KeyEventAndroid.ACTION_UP) { + performGlobalAction(GLOBAL_ACTION_POWER_DIALOG); + } + return true + } + return false + } + private fun insertAccessibilityNode(list: LinkedList, node: AccessibilityNodeInfo) { if (node == null) { return @@ -422,7 +475,7 @@ class InputService : AccessibilityService() { return linkedList } - private fun trySendKeyEvent(event: android.view.KeyEvent, node: AccessibilityNodeInfo, textToCommit: String?): Boolean { + private fun trySendKeyEvent(event: KeyEventAndroid, node: AccessibilityNodeInfo, textToCommit: String?): Boolean { node.refresh() this.fakeEditTextForTextStateCalculation?.setSelection(0,0) this.fakeEditTextForTextStateCalculation?.setText(null) @@ -487,10 +540,10 @@ class InputService : AccessibilityService() { it.layout(rect.left, rect.top, rect.right, rect.bottom) it.onPreDraw() - if (event.action == android.view.KeyEvent.ACTION_DOWN) { + if (event.action == KeyEventAndroid.ACTION_DOWN) { val succ = it.onKeyDown(event.getKeyCode(), event) Log.d(logTag, "onKeyDown $succ") - } else if (event.action == android.view.KeyEvent.ACTION_UP) { + } else if (event.action == KeyEventAndroid.ACTION_UP) { val success = it.onKeyUp(event.getKeyCode(), event) Log.d(logTag, "keyup $success") } else {} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt index effa3b2aa..1e63df405 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt @@ -37,6 +37,8 @@ object KeyEventConverter { action = KeyEvent.ACTION_UP } + // FIXME: The last parameter is the repeat count, not modifiers ? + // https://developer.android.com/reference/android/view/KeyEvent#KeyEvent(long,%20long,%20int,%20int,%20int) return KeyEvent(0, 0, action, chrValue, 0, modifiers) } @@ -112,6 +114,10 @@ object KeyEventConverter { ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR ControlKey.Pause -> KeyEvent.KEYCODE_BREAK + ControlKey.VolumeMute -> KeyEvent.KEYCODE_VOLUME_MUTE + ControlKey.VolumeUp -> KeyEvent.KEYCODE_VOLUME_UP + ControlKey.VolumeDown -> KeyEvent.KEYCODE_VOLUME_DOWN + ControlKey.Power -> KeyEvent.KEYCODE_POWER else -> 0 // Default to unknown. } } diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt new file mode 100644 index 000000000..be30b653e --- /dev/null +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt @@ -0,0 +1,78 @@ +package com.carriez.flutter_hbb + +// Inspired by https://github.com/yosemiteyss/flutter_volume_controller/blob/main/android/src/main/kotlin/com/yosemiteyss/flutter_volume_controller/VolumeController.kt + +import android.media.AudioManager +import android.os.Build +import android.util.Log + +class VolumeController(private val audioManager: AudioManager) { + private val logTag = "volume controller" + + fun getVolume(streamType: Int): Double { + val current = audioManager.getStreamVolume(streamType) + val max = audioManager.getStreamMaxVolume(streamType) + return current.toDouble() / max + } + + fun setVolume(volume: Double, showSystemUI: Boolean, streamType: Int) { + val max = audioManager.getStreamMaxVolume(streamType) + audioManager.setStreamVolume( + streamType, + (max * volume).toInt(), + if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 + ) + } + + fun raiseVolume(step: Double?, showSystemUI: Boolean, streamType: Int) { + if (step == null) { + audioManager.adjustStreamVolume( + streamType, + AudioManager.ADJUST_RAISE, + if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 + ) + } else { + val target = getVolume(streamType) + step + setVolume(target, showSystemUI, streamType) + } + } + + fun lowerVolume(step: Double?, showSystemUI: Boolean, streamType: Int) { + if (step == null) { + audioManager.adjustStreamVolume( + streamType, + AudioManager.ADJUST_LOWER, + if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 + ) + } else { + val target = getVolume(streamType) - step + setVolume(target, showSystemUI, streamType) + } + } + + fun getMute(streamType: Int): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + audioManager.isStreamMute(streamType) + } else { + audioManager.getStreamVolume(streamType) == 0 + } + } + + private fun setMute(isMuted: Boolean, showSystemUI: Boolean, streamType: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + audioManager.adjustStreamVolume( + streamType, + if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE, + if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 + ) + } else { + audioManager.setStreamMute(streamType, isMuted) + } + } + + fun toggleMute(showSystemUI: Boolean, streamType: Int) { + val isMuted = getMute(streamType) + setMute(!isMuted, showSystemUI, streamType) + } +} + diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index cf5c4d161..5313a9e85 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -928,13 +928,9 @@ makeMobileActionsOverlayEntry(VoidCallback? onHide, {FFI? ffi}) { position: draggablePositions.mobileActions, width: overlayW, height: overlayH, - onBackPressed: () => session.inputModel.tap(MouseButtons.right), - onHomePressed: () => session.inputModel.tap(MouseButtons.wheel), - onRecentPressed: () async { - session.inputModel.sendMouse('down', MouseButtons.wheel); - await Future.delayed(const Duration(milliseconds: 500)); - session.inputModel.sendMouse('up', MouseButtons.wheel); - }, + onBackPressed: session.inputModel.onMobileBack, + onHomePressed: session.inputModel.onMobileHome, + onRecentPressed: session.inputModel.onMobileApps, onHidePressed: onHide, ); } diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 0c2494c94..27629d486 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -6,6 +6,7 @@ import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/models/input_model.dart'; import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; @@ -76,6 +77,36 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { final sessionId = ffi.sessionId; List v = []; + if (isMobile && + pi.platform == kPeerPlatformAndroid && + perms['keyboard'] != false) { + v.addAll([ + TTextMenu( + child: Text(translate('Back')), + onPressed: () => ffi.inputModel.onMobileBack(), + ), + TTextMenu( + child: Text(translate('Home')), + onPressed: () => ffi.inputModel.onMobileHome(), + ), + TTextMenu( + child: Text(translate('Apps')), + onPressed: () => ffi.inputModel.onMobileApps(), + ), + TTextMenu( + child: Text(translate('Volume up')), + onPressed: () => ffi.inputModel.onMobileVolumeUp(), + ), + TTextMenu( + child: Text(translate('Volume down')), + onPressed: () => ffi.inputModel.onMobileVolumeDown(), + ), + TTextMenu( + child: Text(translate('Power')), + onPressed: () => ffi.inputModel.onMobilePower(), + ), + ]); + } // elevation if (perms['keyboard'] != false && ffi.elevationModel.showRequestMenu) { v.add( diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index ba93b7fdd..b78d298e5 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -155,6 +155,8 @@ const int kWindowMainId = 0; const String kPointerEventKindTouch = "touch"; const String kPointerEventKindMouse = "mouse"; +const String kKeyFlutterKey = "flutter_key"; + const String kKeyShowDisplaysAsIndividualWindows = 'displays_as_individual_windows'; const String kKeyUseAllMyDisplaysForTheRemoteSession = diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index e75d75c23..1baae26d3 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/audio_input.dart'; import 'package:flutter_hbb/common/widgets/toolbar.dart'; import 'package:flutter_hbb/models/chat_model.dart'; +import 'package:flutter_hbb/models/input_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; @@ -1741,6 +1742,7 @@ class _KeyboardMenu extends StatelessWidget { viewMode(), Divider(), ...toolbarToggles(), + ...mobileActions(), ]); } @@ -1877,6 +1879,39 @@ class _KeyboardMenu extends StatelessWidget { ffi: ffi, child: Text(translate('View Mode'))); } + + mobileActions() { + if (pi.platform != kPeerPlatformAndroid) return []; + final enabled = versionCmp(pi.version, '1.2.6') >= 0; + if (!enabled) return []; + return [ + Divider(), + MenuButton( + child: Text(translate('Back')), + onPressed: () => ffi.inputModel.onMobileBack(), + ffi: ffi), + MenuButton( + child: Text(translate('Home')), + onPressed: () => ffi.inputModel.onMobileHome(), + ffi: ffi), + MenuButton( + child: Text(translate('Apps')), + onPressed: () => ffi.inputModel.onMobileApps(), + ffi: ffi), + MenuButton( + child: Text(translate('Volume up')), + onPressed: () => ffi.inputModel.onMobileVolumeUp(), + ffi: ffi), + MenuButton( + child: Text(translate('Volume down')), + onPressed: () => ffi.inputModel.onMobileVolumeDown(), + ffi: ffi), + MenuButton( + child: Text(translate('Power')), + onPressed: () => ffi.inputModel.onMobilePower(), + ffi: ffi), + ]; + } } class _ChatMenu extends StatefulWidget { diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index f3c53f558..dde815789 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -1152,4 +1152,27 @@ class InputModel { platformFFI.stopDesktopWebListener(); } } + + void onMobileBack() => tap(MouseButtons.right); + void onMobileHome() => tap(MouseButtons.wheel); + Future onMobileApps() async { + sendMouse('down', MouseButtons.wheel); + await Future.delayed(const Duration(milliseconds: 500)); + sendMouse('up', MouseButtons.wheel); + } + + // Simulate a key press event. + // `usbHidUsage` is the USB HID usage code of the key. + Future tapHidKey(int usbHidUsage) async { + inputRawKey(kKeyFlutterKey, usbHidUsage, 0, true); + await Future.delayed(Duration(milliseconds: 100)); + inputRawKey(kKeyFlutterKey, usbHidUsage, 0, false); + } + + Future onMobileVolumeUp() async => + await tapHidKey(PhysicalKeyboardKey.audioVolumeUp.usbHidUsage); + Future onMobileVolumeDown() async => + await tapHidKey(PhysicalKeyboardKey.audioVolumeDown.usbHidUsage); + Future onMobilePower() async => + await tapHidKey(PhysicalKeyboardKey.power.usbHidUsage); } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index e1b024f0d..5b8b24d97 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -195,7 +195,7 @@ class FfiModel with ChangeNotifier { if (desktopType == DesktopType.remote) { KeyboardEnabledState.find(id).value = _permissions['keyboard'] != false; } - debugPrint('$_permissions'); + debugPrint('updatePermission: $_permissions'); notifyListeners(); } diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 483e12c13..be8539a13 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -271,6 +271,10 @@ enum ControlKey { RShift = 73; RControl = 74; RAlt = 75; + VolumeMute = 76; // mainly used on mobile devices as controlled side + VolumeUp = 77; + VolumeDown = 78; + Power = 79; // mainly used on mobile devices as controlled side CtrlAltDel = 100; LockScreen = 101; } diff --git a/src/flutter.rs b/src/flutter.rs index 5f5065881..0cce2ae71 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -44,7 +44,7 @@ pub(crate) const APP_TYPE_CM: &str = "main"; pub type FlutterSession = Arc>; lazy_static::lazy_static! { - pub(crate) static ref CUR_SESSION_ID: RwLock = Default::default(); + pub(crate) static ref CUR_SESSION_ID: RwLock = Default::default(); // For desktop only static ref GLOBAL_EVENT_STREAM: RwLock>> = Default::default(); // rust to dart event channel } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index ab5e6a629..6aa6cd419 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index ab587e91a..799935357 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Пры кіраванні"), ("During service is on", "Пры запушчанай службе"), ("Capture screen using DirectX", "Захоп экрана з выкарыстаннем DirectX"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index b72b24545..6f0634fd7 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 1a59ae9c7..d06c80702 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 553a4b27a..733fba236 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "被控期间"), ("During service is on", "服务开启期间"), ("Capture screen using DirectX", "使用 DirectX 捕获屏幕"), + ("Back", "回退"), + ("Apps", "应用"), + ("Volume up", "提升音量"), + ("Volume down", "降低音量"), + ("Power", "电源"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 01cfd9256..76ec131db 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Během řízeného"), ("During service is on", "Během služby je v provozu"), ("Capture screen using DirectX", "Snímání obrazovky pomocí DirectX"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 3e4a673bd..0930102d1 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 90f4c898b..b04c85749 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Wenn kontrolliert"), ("During service is on", "Wenn der Dienst läuft"), ("Capture screen using DirectX", "Bildschirm mit DirectX aufnehmen"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 69fa86276..49829b276 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 1afae0870..36a500b09 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 8fc97a9e9..b65390f0a 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Mientras está siendo controlado"), ("During service is on", "Mientras el servicio está activo"), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index f1d04fe27..e9a8a22a1 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 189fd4e34..bd45b2f58 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 0d8525dbf..e4fe38f8f 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 1b415de90..00630c4a1 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index e919325d4..335ae8ad6 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 7bb267a86..060c363e8 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index e77ad4504..9d97e6da0 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 27d8f19a0..eaaf994b0 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Durante il controllo"), ("During service is on", "Quando il servizio è attivo"), ("Capture screen using DirectX", "Cattura schermo usando DirectX"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index c0bbf7038..9a95f1a05 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index c101d1513..e1bb638cd 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 8d5a214dd..980d90a45 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index ab2dde495..202bf2f45 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 1b00e7de2..2ee85c013 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Lietošanas laikā"), ("During service is on", "Kamēr pakalpojums ir ieslēgts"), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index edebfc7af..60d79ac6c 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 85d1d63a5..4c4c70ee9 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 0f4707ddb..6a726030b 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index e5b53e559..243114576 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 072426ff2..afb364837 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index acdd4a987..baa3dd36e 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index aabbadf02..68627f493 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "При управлении"), ("During service is on", "При запущенной службе"), ("Capture screen using DirectX", "Захват экрана с помощью DirectX"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 137a9bea3..007123d60 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Počas kontrolovaného"), ("During service is on", "Počas služby je v prevádzke"), ("Capture screen using DirectX", "Snímanie obrazovky pomocou DirectX"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index dcf5192d9..31666c546 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index d7abb5b4a..9830a9494 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 8c3cea8b6..0e993c863 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 2aceeba28..f329ab920 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 3166f9194..3d84f6624 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 2c4e32aaa..d1fdd8aab 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 47a7a133a..c6ae97b81 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index a1046ed00..b30e9223a 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "被控期間"), ("During service is on", "服務開啟期間"), ("Capture screen using DirectX", "使用 DirectX 擷取螢幕"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index a393d5123..3a17b5223 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", "Коли керується"), ("During service is on", "Коли запущена служба"), ("Capture screen using DirectX", "Захоплення екрана з використанням DirectX"), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index acefe2fa5..dbf392dc0 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During controlled", ""), ("During service is on", ""), ("Capture screen using DirectX", ""), + ("Back", ""), + ("Apps", ""), + ("Volume up", ""), + ("Volume down", ""), + ("Power", ""), ].iter().cloned().collect(); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index bb9c68163..38144d5db 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -804,7 +804,54 @@ impl Session { pub fn handle_flutter_key_event( &self, keyboard_mode: &str, - _name: &str, + name: &str, + platform_code: i32, + position_code: i32, + lock_modes: i32, + down_or_up: bool, + ) { + if name == "flutter_key" { + self._handle_key_flutter_simulation(keyboard_mode, platform_code, down_or_up); + } else { + self._handle_key_non_flutter_simulation( + keyboard_mode, + platform_code, + position_code, + lock_modes, + down_or_up, + ); + } + } + + #[cfg(not(any(target_os = "ios")))] + fn _handle_key_flutter_simulation( + &self, + _keyboard_mode: &str, + platform_code: i32, + down_or_up: bool, + ) { + // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L4356 + let ctrl_key = match platform_code { + 0x0007007f => Some(ControlKey::VolumeMute), + 0x00070080 => Some(ControlKey::VolumeUp), + 0x00070081 => Some(ControlKey::VolumeDown), + 0x00070066 => Some(ControlKey::Power), + _ => None, + }; + let Some(ctrl_key) = ctrl_key else { return }; + let mut key_event = KeyEvent { + mode: KeyboardMode::Translate.into(), + down: down_or_up, + ..Default::default() + }; + key_event.set_control_key(ctrl_key); + self.send_key_event(&key_event); + } + + #[cfg(not(any(target_os = "ios")))] + fn _handle_key_non_flutter_simulation( + &self, + keyboard_mode: &str, platform_code: i32, position_code: i32, lock_modes: i32,