diff --git a/CMakeLists.txt b/CMakeLists.txt index 15d9645..5003f58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fcitx5-skey VERSION 0.8.10) +project(fcitx5-skey VERSION 0.8.11) # Version compiled into the settings GUI / updater. Dev builds append a # suffix (e.g. 0.7.5~dev.123) — project() VERSION must stay purely numeric, diff --git a/src/engine.cpp b/src/engine.cpp index 8926440..02ee062 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -4915,11 +4915,18 @@ void SKeyState::keyEvent(KeyEvent &keyEvent) { unsigned int selEnd = std::max(surrounding.anchor(), surrounding.cursor()); unsigned int deleteSize = selEnd - selStart; - const bool chromiumWaylandSelection = isWayland() && isChromiumCached(); + // GTK's GNOME Text Editor has the same Wayland behaviour as + // Chromium here: a forwarded Backspace first collapses Ctrl+A's + // selection, requiring a second physical Backspace to delete it. + // Send the exact selected range through the surrounding-text API so + // one user Backspace performs one atomic deletion. + const bool nativeWaylandSelection = + isWayland() && + (isChromiumCached() || appProgram() == "gnome-text-editor"); SKEY_DEBUG() << "SurrBS: delete selection size=" << deleteSize - << (chromiumWaylandSelection ? " via surrounding text" - : " via forwardKey"); - if (chromiumWaylandSelection) { + << (nativeWaylandSelection ? " via surrounding text" + : " via forwardKey"); + if (nativeWaylandSelection) { // Chromium Wayland may consume a forwarded Backspace without // applying it to a DOM selection. The surrounding-text request // carries the exact selected range and is handled by the same text @@ -4952,8 +4959,18 @@ void SKeyState::keyEvent(KeyEvent &keyEvent) { // character and cannot clear a multi-character selection. if ((!surrounding.isValid() || surrounding.cursor() == 0) && committedLen_ <= 0) { - SKEY_DEBUG() << "SurrBS: forwardKey (valid=" << surrounding.isValid() - << " cursor=" << surrounding.cursor() << ")"; + // Native Wayland clients can expose the SurroundingText capability + // while publishing no usable snapshot (GTK does this after Ctrl+A). + // forwardKey() is a synthetic fcitx event and is ignored by this + // path, so accepting the event would swallow the user's Backspace. + // Leave it unaccepted and let the compositor deliver the original + // key, preserving application selection deletion. + SKEY_DEBUG() << "SurrBS: pass-through (valid=" << surrounding.isValid() + << " cursor=" << surrounding.cursor() + << (isWayland() ? " wayland" : "") << ")"; + if (isWayland()) { + return; + } ic_->forwardKey(Key(FcitxKey_BackSpace)); keyEvent.filterAndAccept(); return; diff --git a/src/native_injector.cpp b/src/native_injector.cpp index f397b52..13b70dd 100644 --- a/src/native_injector.cpp +++ b/src/native_injector.cpp @@ -59,11 +59,16 @@ struct NativeInjector::Impl { const auto path = tokenPath(); std::error_code ec; std::filesystem::create_directories(path.parent_path(), ec); - std::ofstream out(path, std::ios::trunc); + // Write replacement token atomically; a killed Fcitx must not leave a + // zero-length token that looks like a valid restore state. + const auto tmp = path.string() + ".tmp-" + std::to_string(getpid()); + std::ofstream out(tmp, std::ios::trunc); if (out) { out << token << '\n'; out.close(); - chmod(path.c_str(), 0600); + chmod(tmp.c_str(), 0600); + std::filesystem::rename(tmp, path, ec); + if (ec) std::filesystem::remove(tmp, ec); } g_free(token); } @@ -206,7 +211,20 @@ struct NativeInjector::Impl { if (wakeSource) { g_source_destroy(wakeSource); g_source_unref(wakeSource); } if (keyboard) keyboard = ei_device_unref(keyboard); if (connection) connection = ei_unref(connection); - if (session) { xdp_session_close(session); g_object_unref(session); } + if (session) { + // The portal consumes a restore token before starting a restored + // session and writes it back only when Close is delivered. The + // close call is asynchronous; give GLib time to flush it before + // tearing down the portal connection during Fcitx shutdown. + xdp_session_close(session); + const gint64 deadline = g_get_monotonic_time() + 250000; + while (g_get_monotonic_time() < deadline) { + while (g_main_context_pending(context)) + g_main_context_iteration(context, FALSE); + g_usleep(10000); + } + g_object_unref(session); + } if (portal) g_object_unref(portal); g_object_unref(cancellable); g_main_context_pop_thread_default(context);