Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions flutter/lib/desktop/widgets/remote_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1861,8 +1861,18 @@ class _KeyboardMenu extends StatelessWidget {
continue;
}

if (pi.isWayland && mode.key != kKeyMapMode) {
continue;
if (pi.isWayland) {
// Legacy mode is hidden on desktop control side because dead keys
// don't work properly on Wayland. When the control side is mobile,
// Legacy mode is used automatically (mobile always sends Legacy events).
if (mode.key == kKeyLegacyMode) {
continue;
}
// Translate mode requires server >= 1.4.6.
if (mode.key == kKeyTranslateMode &&
versionCmp(pi.version, '1.4.6') < 0) {
continue;
}
}

var text = translate(mode.menu);
Expand Down
20 changes: 17 additions & 3 deletions libs/enigo/src/linux/nix_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ impl KeyboardControllable for Enigo {
} else {
if let Some(keyboard) = &mut self.custom_keyboard {
keyboard.key_sequence(sequence)
} else {
log::warn!("Enigo::key_sequence: no custom_keyboard set for Wayland!");
}
}
}
Expand All @@ -277,6 +279,7 @@ impl KeyboardControllable for Enigo {
if let Some(keyboard) = &mut self.custom_keyboard {
keyboard.key_down(key)
} else {
log::warn!("Enigo::key_down: no custom_keyboard set for Wayland!");
Ok(())
}
}
Expand All @@ -290,13 +293,24 @@ impl KeyboardControllable for Enigo {
} else {
if let Some(keyboard) = &mut self.custom_keyboard {
keyboard.key_up(key)
} else {
log::warn!("Enigo::key_up: no custom_keyboard set for Wayland!");
}
}
}
fn key_click(&mut self, key: Key) {
if self.tfc_key_click(key).is_err() {
self.key_down(key).ok();
self.key_up(key);
if self.is_x11 {
// X11: try tfc first, then fallback to key_down/key_up
if self.tfc_key_click(key).is_err() {
self.key_down(key).ok();
self.key_up(key);
}
} else {
if let Some(keyboard) = &mut self.custom_keyboard {
keyboard.key_click(key);
} else {
log::warn!("Enigo::key_click: no custom_keyboard set for Wayland!");
}
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/rendezvous_mediator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ lazy_static::lazy_static! {
}
static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false);
static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false);

#[derive(Clone)]
pub struct RendezvousMediator {
Expand Down Expand Up @@ -689,6 +690,7 @@ impl RendezvousMediator {
..Default::default()
});
socket.send(&msg_out).await?;
SENT_REGISTER_PK.store(true, Ordering::SeqCst);
Ok(())
}

Expand Down Expand Up @@ -904,3 +906,28 @@ async fn udp_nat_listen(
})?;
Ok(())
}

// When config is not yet synced from root, register_pk may have already been sent with a new generated pk.
// After config sync completes, the pk may change. This struct detects pk changes and triggers
// a re-registration by setting key_confirmed to false.
// NOTE:
// This only corrects PK registration for the current ID. If root uses a non-default mac-generated ID,
// this does not resolve the multi-ID issue by itself.
pub struct CheckIfResendPk {
pk: Option<Vec<u8>>,
}
impl CheckIfResendPk {
pub fn new() -> Self {
Self {
pk: Config::get_cached_pk(),
}
}
}
impl Drop for CheckIfResendPk {
fn drop(&mut self) {
if SENT_REGISTER_PK.load(Ordering::SeqCst) && Config::get_cached_pk() != self.pk {
Config::set_key_confirmed(false);
log::info!("Set key_confirmed to false due to pk changed, will resend register_pk");
}
}
}
67 changes: 63 additions & 4 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ type ConnMap = HashMap<i32, ConnInner>;

#[cfg(any(target_os = "macos", target_os = "linux"))]
const CONFIG_SYNC_INTERVAL_SECS: f32 = 0.3;
#[cfg(any(target_os = "macos", target_os = "linux"))]
// 3s is enough for at least one initial sync attempt:
// 0.3s backoff + up to 1s connect timeout + up to 1s response timeout.
const CONFIG_SYNC_INITIAL_WAIT_SECS: u64 = 3;

lazy_static::lazy_static! {
pub static ref CHILD_PROCESS: Childs = Default::default();
Expand Down Expand Up @@ -600,7 +604,7 @@ pub async fn start_server(is_server: bool, no_server: bool) {
allow_err!(input_service::setup_uinput(0, 1920, 0, 1080).await);
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
tokio::spawn(async { sync_and_watch_config_dir().await });
wait_initial_config_sync().await;
#[cfg(target_os = "windows")]
crate::platform::try_kill_broker();
#[cfg(feature = "hwcodec")]
Expand Down Expand Up @@ -685,13 +689,43 @@ pub async fn start_ipc_url_server() {
}

#[cfg(any(target_os = "macos", target_os = "linux"))]
async fn sync_and_watch_config_dir() {
async fn wait_initial_config_sync() {
if crate::platform::is_root() {
return;
}

// Non-server process should not block startup, but still keeps background sync/watch alive.
if !crate::is_server() {
tokio::spawn(async move {
sync_and_watch_config_dir(None).await;
});
return;
}

let (sync_done_tx, mut sync_done_rx) = tokio::sync::oneshot::channel::<()>();
tokio::spawn(async move {
sync_and_watch_config_dir(Some(sync_done_tx)).await;
});

// Server process waits up to N seconds for initial root->local sync to reduce stale-start window.
tokio::select! {
_ = &mut sync_done_rx => {
}
_ = tokio::time::sleep(Duration::from_secs(CONFIG_SYNC_INITIAL_WAIT_SECS)) => {
log::warn!(
"timed out waiting {}s for initial config sync, continue startup and keep syncing in background",
CONFIG_SYNC_INITIAL_WAIT_SECS
);
}
}
}

#[cfg(any(target_os = "macos", target_os = "linux"))]
async fn sync_and_watch_config_dir(sync_done_tx: Option<tokio::sync::oneshot::Sender<()>>) {
let mut cfg0 = (Config::get(), Config2::get());
let mut synced = false;
let mut is_root_config_empty = false;
let mut sync_done_tx = sync_done_tx;
let tries = if crate::is_server() { 30 } else { 3 };
log::debug!("#tries of ipc service connection: {}", tries);
use hbb_common::sleep;
Expand All @@ -706,6 +740,8 @@ async fn sync_and_watch_config_dir() {
Data::SyncConfig(Some(configs)) => {
let (config, config2) = *configs;
let _chk = crate::ipc::CheckIfRestart::new();
#[cfg(target_os = "macos")]
let _chk_pk = crate::CheckIfResendPk::new();
if !config.is_empty() {
if cfg0.0 != config {
cfg0.0 = config.clone();
Expand All @@ -717,8 +753,20 @@ async fn sync_and_watch_config_dir() {
Config2::set(config2);
log::info!("sync config2 from root");
}
} else {
// only on macos, because this issue was only reproduced on macos
#[cfg(target_os = "macos")]
{
// root config is empty, mark for sync in watch loop
// to prevent root from generating a new config on login screen
is_root_config_empty = true;
}
}
synced = true;
// Notify startup waiter once initial sync phase finishes successfully.
if let Some(tx) = sync_done_tx.take() {
let _ = tx.send(());
}
}
_ => {}
};
Expand All @@ -729,8 +777,14 @@ async fn sync_and_watch_config_dir() {
loop {
sleep(CONFIG_SYNC_INTERVAL_SECS).await;
let cfg = (Config::get(), Config2::get());
if cfg != cfg0 {
log::info!("config updated, sync to root");
let should_sync =
cfg != cfg0 || (is_root_config_empty && !cfg.0.is_empty());
if should_sync {
if is_root_config_empty {
log::info!("root config is empty, sync our config to root");
} else {
log::info!("config updated, sync to root");
}
match conn.send(&Data::SyncConfig(Some(cfg.clone().into()))).await {
Err(e) => {
log::error!("sync config to root failed: {}", e);
Expand All @@ -745,6 +799,7 @@ async fn sync_and_watch_config_dir() {
_ => {
cfg0 = cfg;
conn.next_timeout(1000).await.ok();
is_root_config_empty = false;
}
}
}
Expand All @@ -755,6 +810,10 @@ async fn sync_and_watch_config_dir() {
}
}
}
// Notify startup waiter even when initial sync is skipped/failed, to avoid unnecessary waiting.
if let Some(tx) = sync_done_tx.take() {
let _ = tx.send(());
}
log::warn!("skipped config sync");
}

Expand Down
Loading
Loading