diff --git a/Cargo.lock b/Cargo.lock
index 888115066..642828d4a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1806,7 +1806,7 @@ dependencies = [
  "log",
  "objc",
  "pkg-config",
- "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)",
+ "rdev",
  "serde 1.0.190",
  "serde_derive",
  "tfc",
@@ -4894,31 +4894,7 @@ dependencies = [
 [[package]]
 name = "rdev"
 version = "0.5.0-2"
-source = "git+https://github.com/fufesou/rdev?branch=master#339b2a334ba273afebb7e27fb76984e620fc76e5"
-dependencies = [
- "cocoa",
- "core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation-sys 0.8.4",
- "core-graphics 0.22.3",
- "dispatch",
- "enum-map",
- "epoll",
- "inotify",
- "lazy_static",
- "libc",
- "log",
- "mio",
- "strum 0.24.1",
- "strum_macros 0.24.3",
- "widestring",
- "winapi 0.3.9",
- "x11 2.21.0",
-]
-
-[[package]]
-name = "rdev"
-version = "0.5.0-2"
-source = "git+https://github.com/fufesou/rdev#339b2a334ba273afebb7e27fb76984e620fc76e5"
+source = "git+https://github.com/fufesou/rdev#b3434caee84c92412b45a2f655a15ac5dad33488"
 dependencies = [
  "cocoa",
  "core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -5261,7 +5237,7 @@ dependencies = [
  "pam",
  "parity-tokio-ipc",
  "percent-encoding",
- "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev?branch=master)",
+ "rdev",
  "repng",
  "reqwest",
  "ringbuf",
diff --git a/Cargo.toml b/Cargo.toml
index c41d0744b..cb94b7da1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -71,7 +71,7 @@ default-net = "0.14"
 wol-rs = "1.0"
 flutter_rust_bridge = { version = "=1.80", features = ["uuid"], optional = true}
 errno = "0.3"
-rdev = { git = "https://github.com/fufesou/rdev", branch = "master" }
+rdev = { git = "https://github.com/fufesou/rdev" }
 url = { version = "2.3", features = ["serde"] }
 crossbeam-queue = "0.3"
 hex = "0.4"
diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart
index bc5b19c97..53e304596 100644
--- a/flutter/lib/common.dart
+++ b/flutter/lib/common.dart
@@ -959,7 +959,6 @@ class CustomAlertDialog extends StatelessWidget {
 void msgBox(SessionID sessionId, String type, String title, String text,
     String link, OverlayDialogManager dialogManager,
     {bool? hasCancel, ReconnectHandle? reconnect, int? reconnectTimeout}) {
-
   dialogManager.dismissAll();
   List<Widget> buttons = [];
   bool hasOk = false;
@@ -2766,6 +2765,8 @@ parseParamScreenRect(Map<String, dynamic> params) {
   return screenRect;
 }
 
+get isInputSourceFlutter => stateGlobal.getInputSource() == "Input source 2";
+
 class _ReconnectCountDownButton extends StatefulWidget {
   _ReconnectCountDownButton({
     Key? key,
diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart
index a07bffa47..e073bffc9 100644
--- a/flutter/lib/common/widgets/remote_input.dart
+++ b/flutter/lib/common/widgets/remote_input.dart
@@ -34,7 +34,8 @@ class RawKeyFocusScope extends StatelessWidget {
             canRequestFocus: true,
             focusNode: focusNode,
             onFocusChange: onFocusChange,
-            onKey: inputModel.handleRawKeyEvent,
+            onKey: (FocusNode data, RawKeyEvent e) =>
+                inputModel.handleRawKeyEvent(e),
             child: child));
   }
 }
diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart
index 4236e96d4..fe4052d99 100644
--- a/flutter/lib/consts.dart
+++ b/flutter/lib/consts.dart
@@ -45,6 +45,7 @@ const String kAppTypeDesktopPortForward = "port forward";
 const String kWindowMainWindowOnTop = "main_window_on_top";
 const String kWindowGetWindowInfo = "get_window_info";
 const String kWindowGetScreenList = "get_screen_list";
+// This method is not used, maybe it can be removed.
 const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
 const String kWindowActionRebuild = "rebuild";
 const String kWindowEventHide = "hide";
diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart
index c97a0ef3f..dee2d2e29 100644
--- a/flutter/lib/desktop/pages/remote_tab_page.dart
+++ b/flutter/lib/desktop/pages/remote_tab_page.dart
@@ -156,7 +156,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
           ),
         ));
       } else if (call.method == kWindowDisableGrabKeyboard) {
-        stateGlobal.grabKeyboard = false;
+        // ???
       } else if (call.method == "onDestroy") {
         tabController.clear();
       } else if (call.method == kWindowActionRebuild) {
diff --git a/flutter/lib/desktop/screen/desktop_remote_screen.dart b/flutter/lib/desktop/screen/desktop_remote_screen.dart
index 4427cf8a3..255f1a5b2 100644
--- a/flutter/lib/desktop/screen/desktop_remote_screen.dart
+++ b/flutter/lib/desktop/screen/desktop_remote_screen.dart
@@ -12,9 +12,8 @@ class DesktopRemoteScreen extends StatelessWidget {
   final Map<String, dynamic> params;
 
   DesktopRemoteScreen({Key? key, required this.params}) : super(key: key) {
-      if (!bind.mainStartGrabKeyboard()) {
-        stateGlobal.grabKeyboard = true;
-      }
+      bind.mainInitInputSource();
+      stateGlobal.getInputSource(force: true);
   }
 
   @override
diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart
index 271879428..1ae380761 100644
--- a/flutter/lib/desktop/widgets/remote_toolbar.dart
+++ b/flutter/lib/desktop/widgets/remote_toolbar.dart
@@ -1585,7 +1585,7 @@ class _KeyboardMenu extends StatelessWidget {
     // If use flutter to grab keys, we can only use one mode.
     // Map mode and Legacy mode, at least one of them is supported.
     String? modeOnly;
-    if (stateGlobal.grabKeyboard) {
+    if (isInputSourceFlutter) {
       if (bind.sessionIsKeyboardModeSupported(
           sessionId: ffi.sessionId, mode: kKeyMapMode)) {
         modeOnly = kKeyMapMode;
@@ -1603,6 +1603,7 @@ class _KeyboardMenu extends StatelessWidget {
         menuChildren: [
           keyboardMode(modeOnly),
           localKeyboardType(),
+          inputSource(),
           Divider(),
           viewMode(),
           Divider(),
@@ -1627,6 +1628,7 @@ class _KeyboardMenu extends StatelessWidget {
         if (value == null) return;
         await bind.sessionSetKeyboardMode(
             sessionId: ffi.sessionId, value: value);
+        await ffi.inputModel.updateKeyboardMode();
       }
 
       for (InputModeMenu mode in modes) {
@@ -1678,6 +1680,41 @@ class _KeyboardMenu extends StatelessWidget {
     );
   }
 
+  inputSource() {
+    final supportedInputSource = bind.mainSupportedInputSource();
+    if (supportedInputSource.isEmpty) return Offstage();
+    late final List<dynamic> supportedInputSourceList;
+    try {
+      supportedInputSourceList = jsonDecode(supportedInputSource);
+    } catch (e) {
+      debugPrint('Failed to decode $supportedInputSource, $e');
+      return;
+    }
+    if (supportedInputSourceList.length < 2) return Offstage();
+    final inputSource = stateGlobal.getInputSource();
+    final enabled = !ffi.ffiModel.viewOnly;
+    final children = <Widget>[Divider()];
+    children.addAll(supportedInputSourceList.map((e) {
+      final d = e as List<dynamic>;
+      return RdoMenuButton<String>(
+        child: Text(translate(d[1] as String)),
+        value: d[0] as String,
+        groupValue: inputSource,
+        onChanged: enabled
+            ? (v) async {
+                if (v != null) {
+                  await stateGlobal.setInputSource(ffi.sessionId, v);
+                  await ffi.ffiModel.checkDesktopKeyboardMode();
+                  await ffi.inputModel.updateKeyboardMode();
+                }
+              }
+            : null,
+        ffi: ffi,
+      );
+    }));
+    return Column(children: children);
+  }
+
   viewMode() {
     final ffiModel = ffi.ffiModel;
     final enabled = versionCmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart
index 29c13f625..458ccf3ad 100644
--- a/flutter/lib/models/input_model.dart
+++ b/flutter/lib/models/input_model.dart
@@ -13,7 +13,6 @@ import '../../models/model.dart';
 import '../../models/platform_model.dart';
 import '../common.dart';
 import '../consts.dart';
-import './state_model.dart';
 
 /// Mouse button enum.
 enum MouseButtons { left, right, wheel }
@@ -53,9 +52,114 @@ class PointerEventToRust {
   }
 }
 
+class ToReleaseKeys {
+  RawKeyEvent? lastLShiftKeyEvent;
+  RawKeyEvent? lastRShiftKeyEvent;
+  RawKeyEvent? lastLCtrlKeyEvent;
+  RawKeyEvent? lastRCtrlKeyEvent;
+  RawKeyEvent? lastLAltKeyEvent;
+  RawKeyEvent? lastRAltKeyEvent;
+  RawKeyEvent? lastLCommandKeyEvent;
+  RawKeyEvent? lastRCommandKeyEvent;
+  RawKeyEvent? lastSuperKeyEvent;
+
+  reset() {
+    lastLShiftKeyEvent = null;
+    lastRShiftKeyEvent = null;
+    lastLCtrlKeyEvent = null;
+    lastRCtrlKeyEvent = null;
+    lastLAltKeyEvent = null;
+    lastRAltKeyEvent = null;
+    lastLCommandKeyEvent = null;
+    lastRCommandKeyEvent = null;
+    lastSuperKeyEvent = null;
+  }
+
+  updateKeyDown(LogicalKeyboardKey logicKey, RawKeyDownEvent e) {
+    if (e.isAltPressed) {
+      if (logicKey == LogicalKeyboardKey.altLeft) {
+        lastLAltKeyEvent = e;
+      } else if (logicKey == LogicalKeyboardKey.altRight) {
+        lastRAltKeyEvent = e;
+      }
+    } else if (e.isControlPressed) {
+      if (logicKey == LogicalKeyboardKey.controlLeft) {
+        lastLCtrlKeyEvent = e;
+      } else if (logicKey == LogicalKeyboardKey.controlRight) {
+        lastRCtrlKeyEvent = e;
+      }
+    } else if (e.isShiftPressed) {
+      if (logicKey == LogicalKeyboardKey.shiftLeft) {
+        lastLShiftKeyEvent = e;
+      } else if (logicKey == LogicalKeyboardKey.shiftRight) {
+        lastRShiftKeyEvent = e;
+      }
+    } else if (e.isMetaPressed) {
+      if (logicKey == LogicalKeyboardKey.metaLeft) {
+        lastLCommandKeyEvent = e;
+      } else if (logicKey == LogicalKeyboardKey.metaRight) {
+        lastRCommandKeyEvent = e;
+      } else if (logicKey == LogicalKeyboardKey.superKey) {
+        lastSuperKeyEvent = e;
+      }
+    }
+  }
+
+  updateKeyUp(LogicalKeyboardKey logicKey, RawKeyUpEvent e) {
+    if (e.isAltPressed) {
+      if (logicKey == LogicalKeyboardKey.altLeft) {
+        lastLAltKeyEvent = null;
+      } else if (logicKey == LogicalKeyboardKey.altRight) {
+        lastRAltKeyEvent = null;
+      }
+    } else if (e.isControlPressed) {
+      if (logicKey == LogicalKeyboardKey.controlLeft) {
+        lastLCtrlKeyEvent = null;
+      } else if (logicKey == LogicalKeyboardKey.controlRight) {
+        lastRCtrlKeyEvent = null;
+      }
+    } else if (e.isShiftPressed) {
+      if (logicKey == LogicalKeyboardKey.shiftLeft) {
+        lastLShiftKeyEvent = null;
+      } else if (logicKey == LogicalKeyboardKey.shiftRight) {
+        lastRShiftKeyEvent = null;
+      }
+    } else if (e.isMetaPressed) {
+      if (logicKey == LogicalKeyboardKey.metaLeft) {
+        lastLCommandKeyEvent = null;
+      } else if (logicKey == LogicalKeyboardKey.metaRight) {
+        lastRCommandKeyEvent = null;
+      } else if (logicKey == LogicalKeyboardKey.superKey) {
+        lastSuperKeyEvent = null;
+      }
+    }
+  }
+
+  release(KeyEventResult Function(RawKeyEvent e) handleRawKeyEvent) {
+    for (final key in [
+      lastLShiftKeyEvent,
+      lastRShiftKeyEvent,
+      lastLCtrlKeyEvent,
+      lastRCtrlKeyEvent,
+      lastLAltKeyEvent,
+      lastRAltKeyEvent,
+      lastLCommandKeyEvent,
+      lastRCommandKeyEvent,
+      lastSuperKeyEvent,
+    ]) {
+      if (key != null) {
+        handleRawKeyEvent(RawKeyUpEvent(
+          data: key.data,
+          character: key.character,
+        ));
+      }
+    }
+  }
+}
+
 class InputModel {
   final WeakReference<FFI> parent;
-  String keyboardMode = "legacy";
+  String keyboardMode = '';
 
   // keyboard
   var shift = false;
@@ -63,6 +167,8 @@ class InputModel {
   var alt = false;
   var command = false;
 
+  final ToReleaseKeys toReleaseKeys = ToReleaseKeys();
+
   // trackpad
   var _trackpadLastDelta = Offset.zero;
   var _stopFling = true;
@@ -88,18 +194,29 @@ class InputModel {
 
   InputModel(this.parent) {
     sessionId = parent.target!.sessionId;
+
+    // It is ok to call updateKeyboardMode() directly.
+    // Because `bind` is initialized in `PlatformFFI.init()` which is called very early.
+    // But we still wrap it in a Future.delayed() to make it more clear.
+    Future.delayed(Duration(milliseconds: 100), () {
+      updateKeyboardMode();
+    });
   }
 
-  KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
-    if (isDesktop && !stateGlobal.grabKeyboard) {
-      return KeyEventResult.handled;
-    }
-
+  updateKeyboardMode() async {
     // * Currently mobile does not enable map mode
     if (isDesktop) {
-      bind.sessionGetKeyboardMode(sessionId: sessionId).then((result) {
-        keyboardMode = result.toString();
-      });
+      if (keyboardMode.isEmpty) {
+        keyboardMode =
+            await bind.sessionGetKeyboardMode(sessionId: sessionId) ??
+                kKeyLegacyMode;
+      }
+    }
+  }
+
+  KeyEventResult handleRawKeyEvent(RawKeyEvent e) {
+    if (isDesktop && !isInputSourceFlutter) {
+      return KeyEventResult.handled;
     }
 
     final key = e.logicalKey;
@@ -115,6 +232,7 @@ class InputModel {
           command = true;
         }
       }
+      toReleaseKeys.updateKeyDown(key, e);
     }
     if (e is RawKeyUpEvent) {
       if (key == LogicalKeyboardKey.altLeft ||
@@ -131,6 +249,8 @@ class InputModel {
           key == LogicalKeyboardKey.superKey) {
         command = false;
       }
+
+      toReleaseKeys.updateKeyUp(key, e);
     }
 
     // * Currently mobile does not enable map mode
@@ -320,12 +440,16 @@ class InputModel {
   }
 
   void enterOrLeave(bool enter) {
+    toReleaseKeys.release(handleRawKeyEvent);
+
     // Fix status
     if (!enter) {
       resetModifiers();
     }
     _flingTimer?.cancel();
-    bind.sessionEnterOrLeave(sessionId: sessionId, enter: enter);
+    if (!isInputSourceFlutter) {
+      bind.sessionEnterOrLeave(sessionId: sessionId, enter: enter);
+    }
   }
 
   /// Send mouse movement event with distance in [x] and [y].
@@ -396,7 +520,8 @@ class InputModel {
     }
     if (x != 0 || y != 0) {
       if (peerPlatform == kPeerPlatformAndroid) {
-        handlePointerEvent('touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
+        handlePointerEvent(
+            'touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
       } else {
         bind.sessionSendMouse(
             sessionId: sessionId,
diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart
index 2de85aa11..f01a97820 100644
--- a/flutter/lib/models/model.dart
+++ b/flutter/lib/models/model.dart
@@ -735,17 +735,9 @@ class FfiModel with ChangeNotifier {
   }
 
   checkDesktopKeyboardMode() async {
-    final curMode = await bind.sessionGetKeyboardMode(sessionId: sessionId);
-    if (curMode != null) {
-      if (bind.sessionIsKeyboardModeSupported(
-          sessionId: sessionId, mode: curMode)) {
-        return;
-      }
-    }
-
-    // If current keyboard mode is not supported, change to another one.
-
-    if (stateGlobal.grabKeyboard) {
+    if (isInputSourceFlutter) {
+      // Local side, flutter keyboard input source
+      // Currently only map mode is supported, legacy mode is used for compatibility.
       for (final mode in [kKeyMapMode, kKeyLegacyMode]) {
         if (bind.sessionIsKeyboardModeSupported(
             sessionId: sessionId, mode: mode)) {
@@ -754,6 +746,15 @@ class FfiModel with ChangeNotifier {
         }
       }
     } else {
+      final curMode = await bind.sessionGetKeyboardMode(sessionId: sessionId);
+      if (curMode != null) {
+        if (bind.sessionIsKeyboardModeSupported(
+            sessionId: sessionId, mode: curMode)) {
+          return;
+        }
+      }
+
+      // If current keyboard mode is not supported, change to another one.
       for (final mode in [kKeyMapMode, kKeyTranslateMode, kKeyLegacyMode]) {
         if (bind.sessionIsKeyboardModeSupported(
             sessionId: sessionId, mode: mode)) {
diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart
index ef74a17a2..6778cdbb9 100644
--- a/flutter/lib/models/native_model.dart
+++ b/flutter/lib/models/native_model.dart
@@ -153,10 +153,10 @@ class PlatformFFI {
         AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
         name = '${androidInfo.brand}-${androidInfo.model}';
         id = androidInfo.id.hashCode.toString();
-        androidVersion = androidInfo.version.sdkInt ?? 0;
+        androidVersion = androidInfo.version.sdkInt;
       } else if (Platform.isIOS) {
         IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
-        name = iosInfo.utsname.machine ?? '';
+        name = iosInfo.utsname.machine;
         id = iosInfo.identifierForVendor.hashCode.toString();
       } else if (Platform.isLinux) {
         LinuxDeviceInfo linuxInfo = await deviceInfo.linuxInfo;
diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart
index c80c3551e..0d95b560b 100644
--- a/flutter/lib/models/state_model.dart
+++ b/flutter/lib/models/state_model.dart
@@ -2,15 +2,16 @@ import 'dart:io';
 
 import 'package:desktop_multi_window/desktop_multi_window.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_hbb/common.dart';
 import 'package:get/get.dart';
 
 import '../consts.dart';
+import './platform_model.dart';
 
 enum SvcStatus { notReady, connecting, ready }
 
 class StateGlobal {
   int _windowId = -1;
-  bool grabKeyboard = false;
   final RxBool _fullscreen = false.obs;
   bool _isMinimized = false;
   final RxBool isMaximized = false.obs;
@@ -22,6 +23,8 @@ class StateGlobal {
   // Only used for macOS
   bool? closeOnFullscreen;
 
+  String _inputSource = '';
+
   // Use for desktop -> remote toolbar -> resolution
   final Map<String, Map<int, String?>> _lastResolutionGroupValues = {};
 
@@ -94,6 +97,18 @@ class StateGlobal {
     }
   }
 
+  String getInputSource({bool force = false}) {
+    if (force || _inputSource.isEmpty) {
+      _inputSource = bind.mainGetInputSource();
+    }
+    return _inputSource;
+  }
+
+  setInputSource(SessionID sessionId, String v) async {
+    await bind.mainSetInputSource(sessionId: sessionId, value: v);
+    _inputSource = bind.mainGetInputSource();
+  }
+
   StateGlobal._();
 
   static final StateGlobal instance = StateGlobal._();
diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs
index 106bc64e8..d25923de5 100644
--- a/src/flutter_ffi.rs
+++ b/src/flutter_ffi.rs
@@ -1,5 +1,3 @@
-#[cfg(not(any(target_os = "android", target_os = "ios")))]
-use crate::common::get_default_sound_input;
 use crate::{
     client::file_trait::FileManager,
     common::is_keyboard_mode_supported,
@@ -8,6 +6,11 @@ use crate::{
     input::*,
     ui_interface::{self, *},
 };
+#[cfg(not(any(target_os = "android", target_os = "ios")))]
+use crate::{
+    common::get_default_sound_input,
+    keyboard::input_source::{change_input_source, get_cur_session_input_source},
+};
 use flutter_rust_bridge::{StreamSink, SyncReturn};
 #[cfg(feature = "plugin_framework")]
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -857,6 +860,19 @@ pub fn main_set_local_option(key: String, value: String) {
     set_local_option(key, value)
 }
 
+pub fn main_get_input_source() -> SyncReturn<String> {
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    let input_source = get_cur_session_input_source();
+    #[cfg(any(target_os = "android", target_os = "ios"))]
+    let input_source = "".to_owned();
+    SyncReturn(input_source)
+}
+
+pub fn main_set_input_source(session_id: SessionID, value: String) {
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    change_input_source(session_id, value);
+}
+
 pub fn main_get_my_id() -> String {
     get_id()
 }
@@ -1593,16 +1609,10 @@ pub fn main_is_installed() -> SyncReturn<bool> {
     SyncReturn(is_installed())
 }
 
-pub fn main_start_grab_keyboard() -> SyncReturn<bool> {
-    #[cfg(target_os = "linux")]
-    if !crate::platform::linux::is_x11() {
-        return SyncReturn(false);
-    }
-    crate::keyboard::client::start_grab_loop();
-    if !is_can_input_monitoring(false) {
-        return SyncReturn(false);
-    }
-    SyncReturn(true)
+pub fn main_init_input_source() -> SyncReturn<()> {
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    crate::keyboard::input_source::init_input_source();
+    SyncReturn(())
 }
 
 pub fn main_is_installed_lower_version() -> SyncReturn<bool> {
@@ -1979,6 +1989,20 @@ pub fn main_supported_privacy_mode_impls() -> SyncReturn<String> {
     )
 }
 
+pub fn main_supported_input_source() -> SyncReturn<String> {
+    #[cfg(any(target_os = "android", target_os = "ios"))]
+    {
+        SyncReturn("".to_owned())
+    }
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    {
+        SyncReturn(
+            serde_json::to_string(&crate::keyboard::input_source::get_supported_input_source())
+                .unwrap_or_default(),
+        )
+    }
+}
+
 #[cfg(target_os = "android")]
 pub mod server_side {
     use hbb_common::{config, log};
diff --git a/src/keyboard.rs b/src/keyboard.rs
index be2827c3d..6f7001e93 100644
--- a/src/keyboard.rs
+++ b/src/keyboard.rs
@@ -12,7 +12,7 @@ use hbb_common::message_proto::*;
 #[cfg(any(target_os = "windows", target_os = "macos"))]
 use rdev::KeyCode;
 use rdev::{Event, EventType, Key};
-#[cfg(any(target_os = "windows", target_os = "macos"))]
+#[cfg(not(any(target_os = "android", target_os = "ios")))]
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::{
     collections::HashMap,
@@ -34,6 +34,9 @@ const OS_LOWER_ANDROID: &str = "android";
 #[cfg(any(target_os = "windows", target_os = "macos"))]
 static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
 
+#[cfg(not(any(target_os = "android", target_os = "ios")))]
+static IS_RDEV_ENABLED: AtomicBool = AtomicBool::new(false);
+
 lazy_static::lazy_static! {
     static ref TO_RELEASE: Arc<Mutex<HashMap<Key, Event>>> = Arc::new(Mutex::new(HashMap::new()));
     static ref MODIFIERS_STATE: Mutex<HashMap<Key, bool>> = {
@@ -52,6 +55,7 @@ lazy_static::lazy_static! {
 
 pub mod client {
     use super::*;
+
     lazy_static::lazy_static! {
         static ref IS_GRAB_STARTED: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
     }
@@ -67,6 +71,9 @@ pub mod client {
 
     #[cfg(not(any(target_os = "android", target_os = "ios")))]
     pub fn change_grab_status(state: GrabState, keyboard_mode: &str) {
+        if !IS_RDEV_ENABLED.load(Ordering::SeqCst) {
+            return;
+        }
         match state {
             GrabState::Ready => {}
             GrabState::Run => {
@@ -90,10 +97,7 @@ pub mod client {
                 #[cfg(target_os = "linux")]
                 rdev::disable_grab();
             }
-            GrabState::Exit => {
-                #[cfg(target_os = "linux")]
-                rdev::exit_grab_listen();
-            }
+            GrabState::Exit => {}
         }
     }
 
@@ -243,7 +247,7 @@ fn get_keyboard_mode() -> String {
     "legacy".to_string()
 }
 
-pub fn start_grab_loop() {
+fn start_grab_loop() {
     std::env::set_var("KEYBOARD_ONLY", "y");
     #[cfg(any(target_os = "windows", target_os = "macos"))]
     std::thread::spawn(move || {
@@ -327,6 +331,16 @@ pub fn start_grab_loop() {
     };
 }
 
+// #[allow(dead_code)] is ok here. No need to stop grabbing loop.
+#[allow(dead_code)]
+fn stop_grab_loop() -> Result<(), rdev::GrabError> {
+    #[cfg(any(target_os = "windows", target_os = "macos"))]
+    rdev::exit_grab()?;
+    #[cfg(target_os = "linux")]
+    rdev::exit_grab_listen();
+    Ok(())
+}
+
 pub fn is_long_press(event: &Event) -> bool {
     let keys = MODIFIERS_STATE.lock().unwrap();
     match event.event_type {
@@ -1076,3 +1090,104 @@ pub fn keycode_to_rdev_key(keycode: u32) -> Key {
     #[cfg(target_os = "macos")]
     return rdev::macos_key_from_code(keycode.try_into().unwrap_or_default());
 }
+
+#[cfg(feature = "flutter")]
+#[cfg(not(any(target_os = "android", target_os = "ios")))]
+pub mod input_source {
+    #[cfg(target_os = "macos")]
+    use hbb_common::log;
+    use hbb_common::SessionID;
+
+    use crate::ui_interface::{get_local_option, set_local_option};
+
+    pub const CONFIG_OPTION_INPUT_SOURCE: &str = "input-source";
+    // rdev grab mode
+    pub const CONFIG_INPUT_SOURCE_1: &str = "Input source 1";
+    pub const CONFIG_INPUT_SOURCE_1_TIP: &str = "input_source_1_tip";
+    // flutter grab mode
+    pub const CONFIG_INPUT_SOURCE_2: &str = "Input source 2";
+    pub const CONFIG_INPUT_SOURCE_2_TIP: &str = "input_source_2_tip";
+
+    pub const CONFIG_INPUT_SOURCE_DEFAULT: &str = CONFIG_INPUT_SOURCE_1;
+
+    pub fn init_input_source() {
+        #[cfg(target_os = "linux")]
+        if !crate::platform::linux::is_x11() {
+            // If switching from X11 to Wayland, the grab loop will not be started.
+            // Do not change the config here.
+            return;
+        }
+        #[cfg(target_os = "macos")]
+        if !crate::platform::macos::is_can_input_monitoring(false) {
+            log::error!("init_input_source, is_can_input_monitoring() false");
+            set_local_option(
+                CONFIG_OPTION_INPUT_SOURCE.to_string(),
+                CONFIG_INPUT_SOURCE_2.to_string(),
+            );
+            return;
+        }
+        let cur_input_source = get_cur_session_input_source();
+        if cur_input_source == CONFIG_INPUT_SOURCE_1 {
+            super::IS_RDEV_ENABLED.store(true, super::Ordering::SeqCst);
+        }
+        super::client::start_grab_loop();
+    }
+
+    pub fn change_input_source(session_id: SessionID, input_source: String) {
+        let cur_input_source = get_cur_session_input_source();
+        if cur_input_source == input_source {
+            return;
+        }
+        if input_source == CONFIG_INPUT_SOURCE_1 {
+            #[cfg(target_os = "macos")]
+            if !crate::platform::macos::is_can_input_monitoring(false) {
+                log::error!("change_input_source, is_can_input_monitoring() false");
+                return;
+            }
+            // It is ok to start grab loop multiple times.
+            super::client::start_grab_loop();
+            super::IS_RDEV_ENABLED.store(true, super::Ordering::SeqCst);
+            crate::flutter_ffi::session_enter_or_leave(session_id, true);
+        } else if input_source == CONFIG_INPUT_SOURCE_2 {
+            // No need to stop grab loop.
+            crate::flutter_ffi::session_enter_or_leave(session_id, false);
+            super::IS_RDEV_ENABLED.store(false, super::Ordering::SeqCst);
+        }
+        set_local_option(CONFIG_OPTION_INPUT_SOURCE.to_string(), input_source);
+    }
+
+    #[inline]
+    pub fn get_cur_session_input_source() -> String {
+        #[cfg(target_os = "linux")]
+        if !crate::platform::linux::is_x11() {
+            return CONFIG_INPUT_SOURCE_2.to_string();
+        }
+        let input_source = get_local_option(CONFIG_OPTION_INPUT_SOURCE.to_string());
+        if input_source.is_empty() {
+            CONFIG_INPUT_SOURCE_DEFAULT.to_string()
+        } else {
+            input_source
+        }
+    }
+
+    #[inline]
+    pub fn get_supported_input_source() -> Vec<(String, String)> {
+        #[cfg(target_os = "linux")]
+        if !crate::platform::linux::is_x11() {
+            return vec![(
+                CONFIG_INPUT_SOURCE_2.to_string(),
+                CONFIG_INPUT_SOURCE_2_TIP.to_string(),
+            )];
+        }
+        vec![
+            (
+                CONFIG_INPUT_SOURCE_1.to_string(),
+                CONFIG_INPUT_SOURCE_1_TIP.to_string(),
+            ),
+            (
+                CONFIG_INPUT_SOURCE_2.to_string(),
+                CONFIG_INPUT_SOURCE_2_TIP.to_string(),
+            ),
+        ]
+    }
+}
diff --git a/src/lang/ar.rs b/src/lang/ar.rs
index bdab9e466..d281facee 100644
--- a/src/lang/ar.rs
+++ b/src/lang/ar.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ca.rs b/src/lang/ca.rs
index 36aba208c..eafc06864 100644
--- a/src/lang/ca.rs
+++ b/src/lang/ca.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/cn.rs b/src/lang/cn.rs
index 86a01bd6b..66e65db72 100644
--- a/src/lang/cn.rs
+++ b/src/lang/cn.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "进入隐私模式"),
         ("Exit privacy mode", "退出隐私模式"),
         ("idd_not_support_under_win10_2004_tip", "不支持 Indirect display driver 。需要 windows 10, version 2004 及更高的版本。"),
+        ("input_source_1_tip", "输入源 1"),
+        ("input_source_2_tip", "输入源 2"),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/cs.rs b/src/lang/cs.rs
index 616071e88..6e185ce83 100644
--- a/src/lang/cs.rs
+++ b/src/lang/cs.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Vstup do režimu soukromí"),
         ("Exit privacy mode", "Ukončit režim soukromí"),
         ("idd_not_support_under_win10_2004_tip", "Ovladač nepřímého zobrazení není podporován. Je vyžadován systém Windows 10, verze 2004 nebo novější."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/da.rs b/src/lang/da.rs
index ed926f0df..739e12690 100644
--- a/src/lang/da.rs
+++ b/src/lang/da.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/de.rs b/src/lang/de.rs
index 1473119c4..4af92444d 100644
--- a/src/lang/de.rs
+++ b/src/lang/de.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Datenschutzmodus aktivieren"),
         ("Exit privacy mode", "Datenschutzmodus beenden"),
         ("idd_not_support_under_win10_2004_tip", "Indirekter Grafiktreiber wird nicht unterstützt. Windows 10, Version 2004 oder neuer ist erforderlich."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/el.rs b/src/lang/el.rs
index 8bb3a3d79..a18812b98 100644
--- a/src/lang/el.rs
+++ b/src/lang/el.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/en.rs b/src/lang/en.rs
index 101bc63a3..365076018 100644
--- a/src/lang/en.rs
+++ b/src/lang/en.rs
@@ -206,5 +206,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("privacy_mode_impl_mag_tip", "Mode 1"),
         ("privacy_mode_impl_virtual_display_tip", "Mode 2"),
         ("idd_not_support_under_win10_2004_tip", "Indirect display driver is not supported. Windows 10, version 2004 or newer is required."),
+        ("input_source_1_tip", "Input source 1"),
+        ("input_source_2_tip", "Input source 2"),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/eo.rs b/src/lang/eo.rs
index 73eb77aa5..5efda9cf7 100644
--- a/src/lang/eo.rs
+++ b/src/lang/eo.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/es.rs b/src/lang/es.rs
index 40a23f317..3b8b8ab46 100644
--- a/src/lang/es.rs
+++ b/src/lang/es.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Entrar al modo privado"),
         ("Exit privacy mode", "Salir del modo privado"),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/fa.rs b/src/lang/fa.rs
index af0b8e944..4d1ad99d5 100644
--- a/src/lang/fa.rs
+++ b/src/lang/fa.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/fr.rs b/src/lang/fr.rs
index ea90ff654..b0d4782e5 100644
--- a/src/lang/fr.rs
+++ b/src/lang/fr.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/hu.rs b/src/lang/hu.rs
index f179da1df..79f0505bf 100644
--- a/src/lang/hu.rs
+++ b/src/lang/hu.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/id.rs b/src/lang/id.rs
index 780b24bbd..e3bc5d4ba 100644
--- a/src/lang/id.rs
+++ b/src/lang/id.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Masuk mode privasi"),
         ("Exit privacy mode", "Keluar mode privasi"),
         ("idd_not_support_under_win10_2004_tip", "Driver grafis yang Anda gunakan tidak kompatibel dengan versi Windows Anda dan memerlukan Windows 10 versi 2004 atau yang lebih baru"),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/it.rs b/src/lang/it.rs
index e148ece66..d049d79ef 100644
--- a/src/lang/it.rs
+++ b/src/lang/it.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Entra in modalità privacy"),
         ("Exit privacy mode", "Esci dalla modalità privacy"),
         ("idd_not_support_under_win10_2004_tip", "Il driver video indiretto non è supportato. È richiesto Windows 10, versione 2004 o successiva."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ja.rs b/src/lang/ja.rs
index 0ff52484c..2b646c8ac 100644
--- a/src/lang/ja.rs
+++ b/src/lang/ja.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ko.rs b/src/lang/ko.rs
index 36d8338b9..2455b4099 100644
--- a/src/lang/ko.rs
+++ b/src/lang/ko.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "개인정보 보호 모드 사용"),
         ("Exit privacy mode", "개인정보 보호 모드 종료"),
         ("idd_not_support_under_win10_2004_tip", "간접 디스플레이 드라이버는 지원되지 않습니다. Windows 10 버전 2004 이상이 필요합니다."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/kz.rs b/src/lang/kz.rs
index 7a552e96a..a485d78bb 100644
--- a/src/lang/kz.rs
+++ b/src/lang/kz.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/lt.rs b/src/lang/lt.rs
index b4990d9c6..78716e3a2 100644
--- a/src/lang/lt.rs
+++ b/src/lang/lt.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/lv.rs b/src/lang/lv.rs
index 53b714723..a13ad8520 100644
--- a/src/lang/lv.rs
+++ b/src/lang/lv.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Ieiet privātuma režīmā"),
         ("Exit privacy mode", "Iziet no privātuma režīma"),
         ("idd_not_support_under_win10_2004_tip", "Netiešā displeja draiveris netiek atbalstīts. Nepieciešama operētājsistēma Windows 10, versija 2004 vai jaunāka."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/nb.rs b/src/lang/nb.rs
index 4f8945721..b3f1360cf 100644
--- a/src/lang/nb.rs
+++ b/src/lang/nb.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/nl.rs b/src/lang/nl.rs
index 7b66fb3f3..9267d9be0 100644
--- a/src/lang/nl.rs
+++ b/src/lang/nl.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/pl.rs b/src/lang/pl.rs
index 3aa80786e..763a7ed19 100644
--- a/src/lang/pl.rs
+++ b/src/lang/pl.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Wejdź w tryb prywatności"),
         ("Exit privacy mode", "Wyjdź z trybu prywatności"),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs
index 6b89eaa8c..79b3374b6 100644
--- a/src/lang/pt_PT.rs
+++ b/src/lang/pt_PT.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs
index c5c4f2768..5f477f853 100644
--- a/src/lang/ptbr.rs
+++ b/src/lang/ptbr.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ro.rs b/src/lang/ro.rs
index 5e806e426..30434268b 100644
--- a/src/lang/ro.rs
+++ b/src/lang/ro.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ru.rs b/src/lang/ru.rs
index 98994795d..b4e12f93d 100644
--- a/src/lang/ru.rs
+++ b/src/lang/ru.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Включить режим конфиденциальности"),
         ("Exit privacy mode", "Отключить режим конфиденциальности"),
         ("idd_not_support_under_win10_2004_tip", "Драйвер непрямого отображения не поддерживается. Требуется Windows 10 версии 2004 или новее."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/sk.rs b/src/lang/sk.rs
index 46848da73..9173f7467 100644
--- a/src/lang/sk.rs
+++ b/src/lang/sk.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Vstup do režimu súkromia"),
         ("Exit privacy mode", "Ukončiť režim súkromia"),
         ("idd_not_support_under_win10_2004_tip", "Ovládač nepriameho zobrazenia nie je podporovaný. Vyžaduje sa systém Windows 10, verzia 2004 alebo novšia."),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/sl.rs b/src/lang/sl.rs
index be9774be8..9a2b39366 100755
--- a/src/lang/sl.rs
+++ b/src/lang/sl.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/sq.rs b/src/lang/sq.rs
index ff8851b94..499c2fd09 100644
--- a/src/lang/sq.rs
+++ b/src/lang/sq.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/sr.rs b/src/lang/sr.rs
index ae4c4e364..8ded31ae3 100644
--- a/src/lang/sr.rs
+++ b/src/lang/sr.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/sv.rs b/src/lang/sv.rs
index cfefe48a1..56e64e8fb 100644
--- a/src/lang/sv.rs
+++ b/src/lang/sv.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/template.rs b/src/lang/template.rs
index e3f8f6f38..2431d0130 100644
--- a/src/lang/template.rs
+++ b/src/lang/template.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/th.rs b/src/lang/th.rs
index ea3a72af2..971745e56 100644
--- a/src/lang/th.rs
+++ b/src/lang/th.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/tr.rs b/src/lang/tr.rs
index e14be47db..c5b2f4816 100644
--- a/src/lang/tr.rs
+++ b/src/lang/tr.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/tw.rs b/src/lang/tw.rs
index 57b7d7b9f..c1072f373 100644
--- a/src/lang/tw.rs
+++ b/src/lang/tw.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/ua.rs b/src/lang/ua.rs
index 6fa172311..ffe887436 100644
--- a/src/lang/ua.rs
+++ b/src/lang/ua.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", "Увійти в режим конфіденційності"),
         ("Exit privacy mode", "Вийти з режиму конфіденційності"),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/lang/vn.rs b/src/lang/vn.rs
index 84b0bf752..4ef8825fa 100644
--- a/src/lang/vn.rs
+++ b/src/lang/vn.rs
@@ -573,5 +573,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Enter privacy mode", ""),
         ("Exit privacy mode", ""),
         ("idd_not_support_under_win10_2004_tip", ""),
+        ("input_source_1_tip", ""),
+        ("input_source_2_tip", ""),
     ].iter().cloned().collect();
 }
diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs
index bc44161ab..fd9802986 100644
--- a/src/ui_session_interface.rs
+++ b/src/ui_session_interface.rs
@@ -5,8 +5,6 @@ use crate::{
 use async_trait::async_trait;
 use bytes::Bytes;
 use rdev::{Event, EventType::*, KeyCode};
-#[cfg(not(any(target_os = "android", target_os = "ios")))]
-use std::sync::atomic::{AtomicBool, Ordering};
 use std::{
     collections::HashMap,
     ops::{Deref, DerefMut},
@@ -43,9 +41,6 @@ use crate::common::GrabState;
 use crate::keyboard;
 use crate::{client::Data, client::Interface};
 
-#[cfg(not(any(target_os = "android", target_os = "ios")))]
-pub static IS_IN: AtomicBool = AtomicBool::new(false);
-
 const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
 
 #[derive(Clone, Default)]
@@ -725,13 +720,11 @@ impl<T: InvokeUiSession> Session<T> {
 
     #[cfg(not(any(target_os = "android", target_os = "ios")))]
     pub fn enter(&self, keyboard_mode: String) {
-        IS_IN.store(true, Ordering::SeqCst);
         keyboard::client::change_grab_status(GrabState::Run, &keyboard_mode);
     }
 
     #[cfg(not(any(target_os = "android", target_os = "ios")))]
     pub fn leave(&self, keyboard_mode: String) {
-        IS_IN.store(false, Ordering::SeqCst);
         keyboard::client::change_grab_status(GrabState::Wait, &keyboard_mode);
     }