Feat/android more actions (#8496)

* feat: android volume and power actions

Signed-off-by: fufesou <linlong1266@gmail.com>

* Add translations and refact action menus

Signed-off-by: fufesou <linlong1266@gmail.com>

* Remove divider

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: recover deleted translations

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-06-27 13:28:05 +08:00 committed by GitHub
parent b047730830
commit c5d3c7f390
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 500 additions and 15 deletions

View File

@ -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<AccessibilityNodeInfo>, 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 {}

View File

@ -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.
}
}

View File

@ -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)
}
}

View File

@ -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,
);
}

View File

@ -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<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
final sessionId = ffi.sessionId;
List<TTextMenu> 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(

View File

@ -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 =

View File

@ -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 {

View File

@ -1152,4 +1152,27 @@ class InputModel {
platformFFI.stopDesktopWebListener();
}
}
void onMobileBack() => tap(MouseButtons.right);
void onMobileHome() => tap(MouseButtons.wheel);
Future<void> 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<void> tapHidKey(int usbHidUsage) async {
inputRawKey(kKeyFlutterKey, usbHidUsage, 0, true);
await Future.delayed(Duration(milliseconds: 100));
inputRawKey(kKeyFlutterKey, usbHidUsage, 0, false);
}
Future<void> onMobileVolumeUp() async =>
await tapHidKey(PhysicalKeyboardKey.audioVolumeUp.usbHidUsage);
Future<void> onMobileVolumeDown() async =>
await tapHidKey(PhysicalKeyboardKey.audioVolumeDown.usbHidUsage);
Future<void> onMobilePower() async =>
await tapHidKey(PhysicalKeyboardKey.power.usbHidUsage);
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -44,7 +44,7 @@ pub(crate) const APP_TYPE_CM: &str = "main";
pub type FlutterSession = Arc<Session<FlutterHandler>>;
lazy_static::lazy_static! {
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default(); // For desktop only
static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -804,7 +804,54 @@ impl<T: InvokeUiSession> Session<T> {
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,