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.accessibility.AccessibilityEvent
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import android.view.KeyEvent as KeyEventAndroid
import android.graphics.Rect import android.graphics.Rect
import android.media.AudioManager
import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.AccessibilityServiceInfo
import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR
import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
@ -75,6 +77,8 @@ class InputService : AccessibilityService() {
private var fakeEditTextForTextStateCalculation: EditText? = null private var fakeEditTextForTextStateCalculation: EditText? = null
private val volumeController: VolumeController by lazy { VolumeController(applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager) }
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
fun onMouseInput(mask: Int, _x: Int, _y: Int) { fun onMouseInput(mask: Int, _x: Int, _y: Int) {
val x = max(0, _x) val x = max(0, _x)
@ -294,6 +298,18 @@ class InputService : AccessibilityService() {
Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit") 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) { if (Build.VERSION.SDK_INT >= 33) {
getInputMethod()?.let { inputMethod -> getInputMethod()?.let { inputMethod ->
inputMethod.getCurrentInputConnection()?.let { inputConnection -> inputMethod.getCurrentInputConnection()?.let { inputConnection ->
@ -302,7 +318,7 @@ class InputService : AccessibilityService() {
inputConnection.commitText(text, 1, null) inputConnection.commitText(text, 1, null)
} }
} else { } else {
KeyEventConverter.toAndroidKeyEvent(keyEvent).let { event -> ke?.let { event ->
inputConnection.sendKeyEvent(event) inputConnection.sendKeyEvent(event)
} }
} }
@ -311,7 +327,7 @@ class InputService : AccessibilityService() {
} else { } else {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
handler.post { handler.post {
KeyEventConverter.toAndroidKeyEvent(keyEvent)?.let { event -> ke?.let { event ->
val possibleNodes = possibleAccessibiltyNodes() val possibleNodes = possibleAccessibiltyNodes()
Log.d(logTag, "possibleNodes:$possibleNodes") Log.d(logTag, "possibleNodes:$possibleNodes")
for (item in 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) { private fun insertAccessibilityNode(list: LinkedList<AccessibilityNodeInfo>, node: AccessibilityNodeInfo) {
if (node == null) { if (node == null) {
return return
@ -422,7 +475,7 @@ class InputService : AccessibilityService() {
return linkedList 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() node.refresh()
this.fakeEditTextForTextStateCalculation?.setSelection(0,0) this.fakeEditTextForTextStateCalculation?.setSelection(0,0)
this.fakeEditTextForTextStateCalculation?.setText(null) this.fakeEditTextForTextStateCalculation?.setText(null)
@ -487,10 +540,10 @@ class InputService : AccessibilityService() {
it.layout(rect.left, rect.top, rect.right, rect.bottom) it.layout(rect.left, rect.top, rect.right, rect.bottom)
it.onPreDraw() it.onPreDraw()
if (event.action == android.view.KeyEvent.ACTION_DOWN) { if (event.action == KeyEventAndroid.ACTION_DOWN) {
val succ = it.onKeyDown(event.getKeyCode(), event) val succ = it.onKeyDown(event.getKeyCode(), event)
Log.d(logTag, "onKeyDown $succ") 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) val success = it.onKeyUp(event.getKeyCode(), event)
Log.d(logTag, "keyup $success") Log.d(logTag, "keyup $success")
} else {} } else {}

View File

@ -37,6 +37,8 @@ object KeyEventConverter {
action = KeyEvent.ACTION_UP 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) return KeyEvent(0, 0, action, chrValue, 0, modifiers)
} }
@ -112,6 +114,10 @@ object KeyEventConverter {
ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL
ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR
ControlKey.Pause -> KeyEvent.KEYCODE_BREAK 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. 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, position: draggablePositions.mobileActions,
width: overlayW, width: overlayW,
height: overlayH, height: overlayH,
onBackPressed: () => session.inputModel.tap(MouseButtons.right), onBackPressed: session.inputModel.onMobileBack,
onHomePressed: () => session.inputModel.tap(MouseButtons.wheel), onHomePressed: session.inputModel.onMobileHome,
onRecentPressed: () async { onRecentPressed: session.inputModel.onMobileApps,
session.inputModel.sendMouse('down', MouseButtons.wheel);
await Future.delayed(const Duration(milliseconds: 500));
session.inputModel.sendMouse('up', MouseButtons.wheel);
},
onHidePressed: onHide, 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/shared_state.dart';
import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart';
import 'package:flutter_hbb/consts.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/model.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -76,6 +77,36 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
final sessionId = ffi.sessionId; final sessionId = ffi.sessionId;
List<TTextMenu> v = []; 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 // elevation
if (perms['keyboard'] != false && ffi.elevationModel.showRequestMenu) { if (perms['keyboard'] != false && ffi.elevationModel.showRequestMenu) {
v.add( v.add(

View File

@ -155,6 +155,8 @@ const int kWindowMainId = 0;
const String kPointerEventKindTouch = "touch"; const String kPointerEventKindTouch = "touch";
const String kPointerEventKindMouse = "mouse"; const String kPointerEventKindMouse = "mouse";
const String kKeyFlutterKey = "flutter_key";
const String kKeyShowDisplaysAsIndividualWindows = const String kKeyShowDisplaysAsIndividualWindows =
'displays_as_individual_windows'; 'displays_as_individual_windows';
const String kKeyUseAllMyDisplaysForTheRemoteSession = 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/audio_input.dart';
import 'package:flutter_hbb/common/widgets/toolbar.dart'; import 'package:flutter_hbb/common/widgets/toolbar.dart';
import 'package:flutter_hbb/models/chat_model.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/models/state_model.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
@ -1741,6 +1742,7 @@ class _KeyboardMenu extends StatelessWidget {
viewMode(), viewMode(),
Divider(), Divider(),
...toolbarToggles(), ...toolbarToggles(),
...mobileActions(),
]); ]);
} }
@ -1877,6 +1879,39 @@ class _KeyboardMenu extends StatelessWidget {
ffi: ffi, ffi: ffi,
child: Text(translate('View Mode'))); 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 { class _ChatMenu extends StatefulWidget {

View File

@ -1152,4 +1152,27 @@ class InputModel {
platformFFI.stopDesktopWebListener(); 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) { if (desktopType == DesktopType.remote) {
KeyboardEnabledState.find(id).value = _permissions['keyboard'] != false; KeyboardEnabledState.find(id).value = _permissions['keyboard'] != false;
} }
debugPrint('$_permissions'); debugPrint('updatePermission: $_permissions');
notifyListeners(); notifyListeners();
} }

View File

@ -271,6 +271,10 @@ enum ControlKey {
RShift = 73; RShift = 73;
RControl = 74; RControl = 74;
RAlt = 75; 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; CtrlAltDel = 100;
LockScreen = 101; LockScreen = 101;
} }

View File

@ -44,7 +44,7 @@ pub(crate) const APP_TYPE_CM: &str = "main";
pub type FlutterSession = Arc<Session<FlutterHandler>>; pub type FlutterSession = Arc<Session<FlutterHandler>>;
lazy_static::lazy_static! { 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 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 controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", "Пры кіраванні"), ("During controlled", "Пры кіраванні"),
("During service is on", "Пры запушчанай службе"), ("During service is on", "Пры запушчанай службе"),
("Capture screen using DirectX", "Захоп экрана з выкарыстаннем DirectX"), ("Capture screen using DirectX", "Захоп экрана з выкарыстаннем DirectX"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", "被控期间"), ("During controlled", "被控期间"),
("During service is on", "服务开启期间"), ("During service is on", "服务开启期间"),
("Capture screen using DirectX", "使用 DirectX 捕获屏幕"), ("Capture screen using DirectX", "使用 DirectX 捕获屏幕"),
("Back", "回退"),
("Apps", "应用"),
("Volume up", "提升音量"),
("Volume down", "降低音量"),
("Power", "电源"),
].iter().cloned().collect(); ].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 controlled", "Během řízeného"),
("During service is on", "Během služby je v provozu"), ("During service is on", "Během služby je v provozu"),
("Capture screen using DirectX", "Snímání obrazovky pomocí DirectX"), ("Capture screen using DirectX", "Snímání obrazovky pomocí DirectX"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].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 controlled", "Wenn kontrolliert"),
("During service is on", "Wenn der Dienst läuft"), ("During service is on", "Wenn der Dienst läuft"),
("Capture screen using DirectX", "Bildschirm mit DirectX aufnehmen"), ("Capture screen using DirectX", "Bildschirm mit DirectX aufnehmen"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].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 controlled", "Mientras está siendo controlado"),
("During service is on", "Mientras el servicio está activo"), ("During service is on", "Mientras el servicio está activo"),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].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 controlled", "Durante il controllo"),
("During service is on", "Quando il servizio è attivo"), ("During service is on", "Quando il servizio è attivo"),
("Capture screen using DirectX", "Cattura schermo usando DirectX"), ("Capture screen using DirectX", "Cattura schermo usando DirectX"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].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 controlled", "Lietošanas laikā"),
("During service is on", "Kamēr pakalpojums ir ieslēgts"), ("During service is on", "Kamēr pakalpojums ir ieslēgts"),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", "При управлении"), ("During controlled", "При управлении"),
("During service is on", "При запущенной службе"), ("During service is on", "При запущенной службе"),
("Capture screen using DirectX", "Захват экрана с помощью DirectX"), ("Capture screen using DirectX", "Захват экрана с помощью DirectX"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].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 controlled", "Počas kontrolovaného"),
("During service is on", "Počas služby je v prevádzke"), ("During service is on", "Počas služby je v prevádzke"),
("Capture screen using DirectX", "Snímanie obrazovky pomocou DirectX"), ("Capture screen using DirectX", "Snímanie obrazovky pomocou DirectX"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", "被控期間"), ("During controlled", "被控期間"),
("During service is on", "服務開啟期間"), ("During service is on", "服務開啟期間"),
("Capture screen using DirectX", "使用 DirectX 擷取螢幕"), ("Capture screen using DirectX", "使用 DirectX 擷取螢幕"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", "Коли керується"), ("During controlled", "Коли керується"),
("During service is on", "Коли запущена служба"), ("During service is on", "Коли запущена служба"),
("Capture screen using DirectX", "Захоплення екрана з використанням DirectX"), ("Capture screen using DirectX", "Захоплення екрана з використанням DirectX"),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -622,5 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("During controlled", ""), ("During controlled", ""),
("During service is on", ""), ("During service is on", ""),
("Capture screen using DirectX", ""), ("Capture screen using DirectX", ""),
("Back", ""),
("Apps", ""),
("Volume up", ""),
("Volume down", ""),
("Power", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -804,7 +804,54 @@ impl<T: InvokeUiSession> Session<T> {
pub fn handle_flutter_key_event( pub fn handle_flutter_key_event(
&self, &self,
keyboard_mode: &str, 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, platform_code: i32,
position_code: i32, position_code: i32,
lock_modes: i32, lock_modes: i32,