0

[ozone/wayland] Text input refactors part 3: Single instance

Make ZwpTextInputV1 and ZwpTextInputV3 single instances per
WaylandConnection, shared by multiple per-window
WaylandInputMethodContext instances.

Introduce new text-input focus derived from text-input-v3 enter/leave
for:
- Ensuring the correct WaylandInputMethodContext is set as the v3
  client.
- Determining window focus if keyboard focus isn't available.

Remove `WaylandWindowObserver::OnKeyboardFocusedWindowChanged()`
and add `WaylandWindow::FocusClient` instead for notifying keyboard
and text input focus changes specifically for that window.

This makes it possible for the WaylandWindow to keep a reference to the
associated WaylandInputMethodContext and directly notify focus changes
to it.

Bug: 414284073
Change-Id: Id8ced1a690c759107983a5c1cb71195f9556a1d7
Fixed: 368299543, 369574355, 372650991
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6496390
Reviewed-by: Kramer Ge <fangzhoug@chromium.org>
Reviewed-by: Thomas Anderson <thomasanderson@chromium.org>
Commit-Queue: Orko Garai <orko@igalia.com>
Cr-Commit-Position: refs/heads/main@{#1455466}
This commit is contained in:
Orko Garai
2025-05-04 09:18:50 -07:00
committed by Chromium LUCI CQ
parent 169446238b
commit d6678747e4
20 changed files with 281 additions and 112 deletions

@ -43,8 +43,11 @@ bool IsSameKeyEvent(const ui::KeyEvent& lhs, const ui::KeyEvent& rhs) {
namespace ui {
InputMethodAuraLinux::InputMethodAuraLinux(
ImeKeyEventDispatcher* ime_key_event_dispatcher)
ImeKeyEventDispatcher* ime_key_event_dispatcher,
gfx::AcceleratedWidget widget)
: InputMethodBase(ime_key_event_dispatcher),
widget_(widget),
text_input_type_(TEXT_INPUT_TYPE_NONE),
is_sync_mode_(false),
composition_changed_(false) {
@ -457,6 +460,10 @@ InputMethodAuraLinux::GetVirtualKeyboardController() {
// Overriden from ui::LinuxInputMethodContextDelegate
gfx::AcceleratedWidget InputMethodAuraLinux::GetClientWindowKey() const {
return widget_;
}
void InputMethodAuraLinux::OnCommit(const std::u16string& text) {
if (IgnoringNonKeyInput() || !GetTextInputClient())
return;

@ -12,6 +12,7 @@
#include "ui/base/ime/composition_text.h"
#include "ui/base/ime/input_method_base.h"
#include "ui/base/ime/linux/linux_input_method_context.h"
#include "ui/gfx/native_widget_types.h"
namespace ui {
@ -22,15 +23,15 @@ class COMPONENT_EXPORT(UI_BASE_IME_LINUX) InputMethodAuraLinux
: public InputMethodBase,
public LinuxInputMethodContextDelegate {
public:
explicit InputMethodAuraLinux(
ImeKeyEventDispatcher* ime_key_event_dispatcher);
explicit InputMethodAuraLinux(ImeKeyEventDispatcher* ime_key_event_dispatcher,
gfx::AcceleratedWidget widget);
InputMethodAuraLinux(const InputMethodAuraLinux&) = delete;
InputMethodAuraLinux& operator=(const InputMethodAuraLinux&) = delete;
~InputMethodAuraLinux() override;
LinuxInputMethodContext* GetContextForTesting();
// Overriden from InputMethod.
// Overridden from InputMethod.
ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override;
void OnTextInputTypeChanged(TextInputClient* client) override;
void OnCaretBoundsChanged(const TextInputClient* client) override;
@ -38,7 +39,8 @@ class COMPONENT_EXPORT(UI_BASE_IME_LINUX) InputMethodAuraLinux
bool IsCandidatePopupOpen() const override;
VirtualKeyboardController* GetVirtualKeyboardController() override;
// Overriden from ui::LinuxInputMethodContextDelegate
// Overridden from ui::LinuxInputMethodContextDelegate
gfx::AcceleratedWidget GetClientWindowKey() const override;
void OnCommit(const std::u16string& text) override;
void OnConfirmCompositionText(bool keep_selection) override;
void OnDeleteSurroundingText(size_t before, size_t after) override;
@ -87,6 +89,8 @@ class COMPONENT_EXPORT(UI_BASE_IME_LINUX) InputMethodAuraLinux
void ResetContext();
bool IgnoringNonKeyInput() const;
const gfx::AcceleratedWidget widget_;
std::unique_ptr<LinuxInputMethodContext> context_;
// The last key event that IME is probably in process in

@ -350,8 +350,8 @@ class InputMethodAuraLinuxTest : public testing::Test {
void SetUp() override {
delegate_ = std::make_unique<InputMethodDelegateForTesting>();
input_method_auralinux_ =
std::make_unique<InputMethodAuraLinux>(delegate_.get());
input_method_auralinux_ = std::make_unique<InputMethodAuraLinux>(
delegate_.get(), gfx::kNullAcceleratedWidget);
input_method_auralinux_->OnFocus();
context_ = static_cast<LinuxInputMethodContextForTesting*>(
input_method_auralinux_->GetContextForTesting());

@ -16,6 +16,7 @@
#include "ui/base/ime/text_input_mode.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/range/range.h"
namespace gfx {
@ -86,6 +87,9 @@ class COMPONENT_EXPORT(UI_BASE_IME_LINUX) LinuxInputMethodContextDelegate {
public:
virtual ~LinuxInputMethodContextDelegate() {}
// Returns the key for the window containing the input method.
virtual gfx::AcceleratedWidget GetClientWindowKey() const = 0;
// Commits the |text| to the text input client.
virtual void OnCommit(const std::u16string& text) = 0;

@ -64,6 +64,8 @@
#include "ui/ozone/platform/wayland/host/xdg_session_manager.h"
#include "ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h"
#include "ui/ozone/platform/wayland/host/zwp_primary_selection_device_manager.h"
#include "ui/ozone/platform/wayland/host/zwp_text_input_v1.h"
#include "ui/ozone/platform/wayland/host/zwp_text_input_v3.h"
#include "ui/platform_window/common/platform_window_defaults.h"
namespace ui {
@ -366,6 +368,32 @@ std::vector<KeyboardDevice> WaylandConnection::CreateKeyboardDevices() const {
return devices;
}
ZwpTextInputV1* WaylandConnection::EnsureTextInputV1() {
if (text_input_v1_) {
return text_input_v1_.get();
}
if (text_input_manager_v1_) {
text_input_v1_ = std::make_unique<ZwpTextInputV1Impl>(
this, text_input_manager_v1_.get());
} else {
LOG(WARNING) << "text-input-v1 not available.";
}
return text_input_v1_.get();
}
ZwpTextInputV3* WaylandConnection::EnsureTextInputV3() {
if (text_input_v3_) {
return text_input_v3_.get();
}
if (text_input_manager_v3_) {
text_input_v3_ = std::make_unique<ZwpTextInputV3Impl>(
this, text_input_manager_v3_.get());
} else {
LOG(WARNING) << "text-input-v3 not available.";
}
return text_input_v3_.get();
}
std::vector<TouchscreenDevice> WaylandConnection::CreateTouchscreenDevices()
const {
std::vector<TouchscreenDevice> devices;

@ -67,6 +67,8 @@ class XdgForeignWrapper;
class XdgSessionManager;
class ZwpIdleInhibitManager;
class ZwpPrimarySelectionDeviceManager;
class ZwpTextInputV1;
class ZwpTextInputV3;
class WaylandConnection {
public:
@ -112,9 +114,6 @@ class WaylandConnection {
zwp_text_input_manager_v1* text_input_manager_v1() const {
return text_input_manager_v1_.get();
}
zwp_text_input_manager_v3* text_input_manager_v3() const {
return text_input_manager_v3_.get();
}
zwp_linux_explicit_synchronization_v1* linux_explicit_synchronization_v1()
const {
return linux_explicit_synchronization_.get();
@ -207,6 +206,10 @@ class WaylandConnection {
return zwp_primary_selection_device_manager_.get();
}
ZwpTextInputV1* EnsureTextInputV1();
ZwpTextInputV3* EnsureTextInputV3();
bool SupportsTextInputFocus() const { return !!text_input_v3_; }
WaylandDataDragController* data_drag_controller() const {
return data_drag_controller_.get();
}
@ -463,6 +466,8 @@ class WaylandConnection {
gtk_primary_selection_device_manager_;
std::unique_ptr<ZwpPrimarySelectionDeviceManager>
zwp_primary_selection_device_manager_;
std::unique_ptr<ZwpTextInputV1> text_input_v1_;
std::unique_ptr<ZwpTextInputV3> text_input_v3_;
std::unique_ptr<WaylandClipboard> clipboard_;
// Objects specific to KDE Plasma desktop environment.

@ -246,9 +246,12 @@ WaylandInputMethodContext::WaylandInputMethodContext(
: connection_(connection),
key_delegate_(key_delegate),
ime_delegate_(ime_delegate),
window_(connection_->window_manager()
->GetWindow(ime_delegate_->GetClientWindowKey())
->AsWeakPtr()),
text_input_v1_(nullptr),
character_composer_(kPreeditStringMode) {
connection_->window_manager()->AddObserver(this);
window_->set_focus_client(this);
Init();
}
@ -259,7 +262,9 @@ WaylandInputMethodContext::~WaylandInputMethodContext() {
DismissVirtualKeyboard();
text_input_v1_->OnClientDestroyed(text_input_v1_client_.get());
}
connection_->window_manager()->RemoveObserver(this);
if (window_) {
window_->set_focus_client(nullptr);
}
}
void WaylandInputMethodContext::CreateTextInput() {
@ -277,12 +282,7 @@ void WaylandInputMethodContext::CreateTextInput() {
if (enable_using_cmd_line_version &&
version_from_cmd_line == kWaylandTextInputVersion1) {
if (!connection_->text_input_manager_v1()) {
LOG(WARNING) << "text-input-v1 is not supported by the compositor.";
return;
}
text_input_v1_ = std::make_unique<ZwpTextInputV1Impl>(
connection_, connection_->text_input_manager_v1());
text_input_v1_ = connection_->EnsureTextInputV1();
text_input_v1_client_ =
std::make_unique<WaylandInputMethodContextV1Client>(this);
} else if (base::FeatureList::IsEnabled(features::kWaylandTextInputV3) ||
@ -294,12 +294,7 @@ void WaylandInputMethodContext::CreateTextInput() {
"--enable-wayland-ime should be present. Defaulting to "
"text-input-v3.";
}
if (!connection_->text_input_manager_v3()) {
LOG(WARNING) << "text-input-v3 is not supported by the compositor.";
return;
}
text_input_v3_ = std::make_unique<ZwpTextInputV3Impl>(
connection_, connection_->text_input_manager_v3());
text_input_v3_ = connection_->EnsureTextInputV3();
text_input_v3_client_ =
std::make_unique<WaylandInputmethodContextV3Client>(this);
}
@ -320,7 +315,7 @@ void WaylandInputMethodContext::Init() {
}
void WaylandInputMethodContext::SetTextInputV1ForTesting(
std::unique_ptr<ZwpTextInputV1> text_input_v1) {
ZwpTextInputV1* text_input_v1) {
text_input_v1_ = std::move(text_input_v1);
if (!text_input_v1_client_) {
text_input_v1_client_ =
@ -331,7 +326,7 @@ void WaylandInputMethodContext::SetTextInputV1ForTesting(
}
void WaylandInputMethodContext::SetTextInputV3ForTesting(
std::unique_ptr<ZwpTextInputV3> text_input_v3) {
ZwpTextInputV3* text_input_v3) {
text_input_v3_ = std::move(text_input_v3);
if (!text_input_v3_client_) {
text_input_v3_client_ =
@ -438,7 +433,9 @@ void WaylandInputMethodContext::SetCursorLocation(const gfx::Rect& rect) {
return;
}
WaylandWindow* focused_window =
connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
text_input_v3_
? connection_->window_manager()->GetCurrentTextInputFocusedWindow()
: connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
if (!focused_window) {
return;
}
@ -818,32 +815,46 @@ void WaylandInputMethodContext::OnModifiersMap(
modifiers_map_ = std::move(modifiers_map);
}
void WaylandInputMethodContext::OnKeyboardFocusedWindowChanged() {
void WaylandInputMethodContext::OnTextInputFocusChanged(bool focused) {
CHECK(text_input_v3_);
window_focused_ = focused;
MaybeUpdateActivated(false);
}
void WaylandInputMethodContext::OnKeyboardFocusChanged(bool focused) {
if (text_input_v3_) {
// For text-input-v3, zwp_text_input_l3::{enter,leave} is used instead.
return;
}
window_focused_ = focused;
MaybeUpdateActivated(false);
}
bool WaylandInputMethodContext::WindowIsActiveForTextInputV1() const {
if (!text_input_v1_ || !window_) {
return false;
}
return
// The associated window has keyboard focus
window_focused_ ||
// If no keyboard is connected, the toplevel window active state is used
// to deduce if this window is active.
(!connection_->seat()->keyboard() &&
window_->GetRootParentWindow()->IsActive());
}
void WaylandInputMethodContext::MaybeUpdateActivated(
bool skip_virtual_keyboard_update) {
if (!text_input_v3_ && !text_input_v1_) {
return;
}
WaylandWindow* focused_window =
connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
if (!focused_window && !connection_->seat()->keyboard()) {
// If no keyboard is connected, the current active window is used.
focused_window = connection_->window_manager()->GetCurrentActiveWindow();
}
// Activate Wayland IME only if the following conditions are met:
// 1) InputMethod has some TextInputClient connected.
// 2) There is a focused Chromium window.
// 3) The focused window matches the window containing this
// WaylandInputMethodContext.
bool activated = focused_ && focused_window;
// 2) The associated window for this context is focused, or there is an
// active window for text-input-v1.
bool activated =
focused_ && (window_focused_ || WindowIsActiveForTextInputV1());
if (activated_ == activated)
return;
@ -856,7 +867,7 @@ void WaylandInputMethodContext::MaybeUpdateActivated(
attributes_.should_do_learning);
} else {
text_input_v1_->SetClient(text_input_v1_client_.get());
text_input_v1_->Activate(focused_window);
text_input_v1_->Activate(window_.get());
text_input_v1_->SetContentType(attributes_.input_type, attributes_.flags,
attributes_.should_do_learning);
}

@ -20,7 +20,7 @@
#include "ui/base/ime/virtual_keyboard_controller.h"
#include "ui/gfx/range/range.h"
#include "ui/ozone/platform/wayland/host/wayland_keyboard.h"
#include "ui/ozone/platform/wayland/host/wayland_window_observer.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/ozone/platform/wayland/host/zwp_text_input_v1.h"
#include "ui/ozone/platform/wayland/host/zwp_text_input_v3.h"
@ -30,7 +30,7 @@ class WaylandConnection;
class WaylandInputMethodContext : public LinuxInputMethodContext,
public VirtualKeyboardController,
public WaylandWindowObserver {
public WaylandWindow::FocusClient {
public:
class Delegate;
@ -70,8 +70,9 @@ class WaylandInputMethodContext : public LinuxInputMethodContext,
void RemoveObserver(VirtualKeyboardControllerObserver* observer) override;
bool IsKeyboardVisible() override;
// WaylandWindowObserver overrides:
void OnKeyboardFocusedWindowChanged() override;
// WaylandWindow::FocusClient overrides:
void OnKeyboardFocusChanged(bool focused) override;
void OnTextInputFocusChanged(bool focused) override;
// Callbacks from the v1 and v3 clients.
void OnPreeditString(std::string_view text,
@ -92,9 +93,9 @@ class WaylandInputMethodContext : public LinuxInputMethodContext,
return surrounding_text_tracker_.predicted_state();
}
void SetTextInputV1ForTesting(std::unique_ptr<ZwpTextInputV1> text_input_v1);
void SetTextInputV1ForTesting(ZwpTextInputV1* text_input_v1);
void SetTextInputV3ForTesting(std::unique_ptr<ZwpTextInputV3> text_input_v3);
void SetTextInputV3ForTesting(ZwpTextInputV3* text_input_v3);
void SetDesktopEnvironmentForTesting(
base::nix::DesktopEnvironment desktop_environment) {
@ -106,6 +107,7 @@ class WaylandInputMethodContext : public LinuxInputMethodContext,
void Focus(bool skip_virtual_keyboard_update);
void Blur(bool skip_virtual_keyboard_update);
void UpdatePreeditText(const std::u16string& preedit_text);
bool WindowIsActiveForTextInputV1() const;
// If |skip_virtual_keyboard_update| is true, no virtual keyboard show/hide
// requests will be sent. This is used to prevent flickering the virtual
// keyboard when it would be immediately reshown anyway, e.g. when changing
@ -121,14 +123,21 @@ class WaylandInputMethodContext : public LinuxInputMethodContext,
// Delegate IME-specific events to be handled by //ui code.
const raw_ptr<LinuxInputMethodContextDelegate> ime_delegate_;
// Window obtained from IME delegate's widget. This WaylandInputMethodContext
// will be associated exclusively with this window and so this object will not
// change for the lifetime of the window.
base::WeakPtr<WaylandWindow> window_;
std::unique_ptr<ZwpTextInputV1Client> text_input_v1_client_;
std::unique_ptr<ZwpTextInputV3Client> text_input_v3_client_;
std::unique_ptr<ZwpTextInputV1> text_input_v1_;
std::unique_ptr<ZwpTextInputV3> text_input_v3_;
raw_ptr<ZwpTextInputV1> text_input_v1_;
raw_ptr<ZwpTextInputV3> text_input_v3_;
// Tracks whether InputMethod in Chrome has some focus.
bool focused_ = false;
// Tracks whether the window associated with the input method is focused.
bool window_focused_ = false;
// Tracks whether a request to activate InputMethod is sent to wayland
// compositor.
bool activated_ = false;

@ -183,7 +183,8 @@ class MockZwpTextInputV3 : public ZwpTextInputV3 {
class TestInputMethodContextDelegate : public LinuxInputMethodContextDelegate {
public:
TestInputMethodContextDelegate() = default;
explicit TestInputMethodContextDelegate(gfx::AcceleratedWidget window_key)
: client_window_key_(window_key) {}
TestInputMethodContextDelegate(const TestInputMethodContextDelegate&) =
delete;
TestInputMethodContextDelegate& operator=(
@ -220,6 +221,10 @@ class TestInputMethodContextDelegate : public LinuxInputMethodContextDelegate {
virtual_keyboard_bounds_ = screen_bounds;
}
gfx::AcceleratedWidget GetClientWindowKey() const override {
return client_window_key_;
}
bool was_on_commit_called() const { return was_on_commit_called_; }
const std::optional<ui::CompositionText>& last_preedit() {
@ -256,6 +261,7 @@ class TestInputMethodContextDelegate : public LinuxInputMethodContextDelegate {
}
private:
gfx::AcceleratedWidget client_window_key_;
bool was_on_commit_called_ = false;
std::optional<std::u16string> last_commit_text_;
std::optional<bool> last_on_confirm_composition_arg_;
@ -323,15 +329,13 @@ class WaylandInputMethodContextTest : public WaylandTest {
protected:
void SetUpInternal() {
input_method_context_delegate_ =
std::make_unique<TestInputMethodContextDelegate>();
std::make_unique<TestInputMethodContextDelegate>(window_->GetWidget());
keyboard_delegate_ = std::make_unique<TestKeyboardDelegate>();
input_method_context_ = std::make_unique<WaylandInputMethodContext>(
connection_.get(), keyboard_delegate_.get(),
input_method_context_delegate_.get());
auto text_input_v1 = std::make_unique<ZwpTextInputV1Impl>(
connection_.get(), connection_->text_input_manager_v1());
text_input_v1_ = text_input_v1.get();
input_method_context_->SetTextInputV1ForTesting(std::move(text_input_v1));
text_input_v1_ = connection_->EnsureTextInputV1();
input_method_context_->SetTextInputV1ForTesting(text_input_v1_.get());
input_method_context_->SetDesktopEnvironmentForTesting(
// Ensure by default it doesn't pick the current desktop from the system
// the tests are running on.
@ -1266,14 +1270,13 @@ class WaylandInputMethodContextWithMockV3Test : public WaylandTestSimple {
void SetUp() override {
WaylandTestSimple::SetUp();
input_method_context_delegate_ =
std::make_unique<TestInputMethodContextDelegate>();
std::make_unique<TestInputMethodContextDelegate>(window_->GetWidget());
keyboard_delegate_ = std::make_unique<TestKeyboardDelegate>();
mock_wrapper_ = std::make_unique<MockZwpTextInputV3>();
input_method_context_ = std::make_unique<WaylandInputMethodContext>(
connection_.get(), keyboard_delegate_.get(),
input_method_context_delegate_.get());
auto mock_wrapper = std::make_unique<MockZwpTextInputV3>();
mock_wrapper_ = mock_wrapper.get();
input_method_context_->SetTextInputV3ForTesting(std::move(mock_wrapper));
input_method_context_->SetTextInputV3ForTesting(mock_wrapper_.get());
input_method_context_->SetDesktopEnvironmentForTesting(
// Ensure by default it doesn't pick the current desktop from the system
// the tests are running on.
@ -1284,8 +1287,8 @@ class WaylandInputMethodContextWithMockV3Test : public WaylandTestSimple {
std::unique_ptr<TestInputMethodContextDelegate>
input_method_context_delegate_;
std::unique_ptr<TestKeyboardDelegate> keyboard_delegate_;
std::unique_ptr<MockZwpTextInputV3> mock_wrapper_;
std::unique_ptr<WaylandInputMethodContext> input_method_context_;
raw_ptr<MockZwpTextInputV3> mock_wrapper_;
};
TEST_F(WaylandInputMethodContextWithMockV3Test,

@ -420,15 +420,27 @@ void WaylandToplevelWindow::UpdateActivationState() {
bool prev_is_active = is_active_;
// Determine active state from keyboard focus. If keyboard is unavailable,
// determine it from xdg-shell "activated" state as that's the only hint the
// compositor provides us on whether our window is considered active.
// TODO(crbug.com/369574355): utilize zwp_text_input_v3::{enter,leave}
// eventually
// determine it from zwp_text_input_v3::{enter,leave}.
// If neither of those are available, use xdg-shell "activated" state as
// that's the only other hint the compositor provides us on whether our window
// is considered active.
if (connection()->IsKeyboardAvailable()) {
auto* keyboard_focused_window =
connection()->window_manager()->GetCurrentKeyboardFocusedWindow();
is_active_ = keyboard_focused_window &&
keyboard_focused_window->GetRootParentWindow() == this;
} else if (connection()->SupportsTextInputFocus()) {
// Note: Some compositors (sway, niri, cosmic etc.) may not send
// text-input-v3 enter/leave events if an IM framework is not
// installed/running. So text input focus cannot be used always instead of
// keyboard focus above. However, if there is no physical keyboard, there
// should be an IM framework to facilitate inputting text in some way, e.g.
// using a virtual keyboard, and so it should be okay to expect focus to be
// received from text-input in that case if text-input-v3 is available.
auto* text_input_focused_window =
connection()->window_manager()->GetCurrentTextInputFocusedWindow();
is_active_ = text_input_focused_window &&
text_input_focused_window->GetRootParentWindow() == this;
} else {
is_active_ = is_xdg_active_;
}

@ -135,6 +135,13 @@ WaylandWindow::~WaylandWindow() {
for (auto bubble : child_bubbles_) {
bubble->set_parent_window(nullptr);
}
if (focus_client_) {
focus_client_->OnKeyboardFocusChanged(false);
if (connection_->SupportsTextInputFocus()) {
focus_client_->OnTextInputFocusChanged(false);
}
}
}
void WaylandWindow::OnWindowLostCapture() {
@ -319,6 +326,18 @@ void WaylandWindow::OnPointerFocusChanged(bool focused) {
}
}
void WaylandWindow::OnKeyboardFocusChanged(bool focused) {
if (focus_client_) {
focus_client_->OnKeyboardFocusChanged(focused);
}
}
void WaylandWindow::OnTextInputFocusChanged(bool focused) {
if (focus_client_) {
focus_client_->OnTextInputFocusChanged(focused);
}
}
bool WaylandWindow::HasPointerFocus() const {
return this ==
connection_->window_manager()->GetCurrentPointerFocusedWindow();

@ -68,6 +68,13 @@ class WaylandWindow : public PlatformWindow,
public WaylandExtension,
public EventTarget {
public:
// An interface to receive window focus change events.
class FocusClient {
public:
virtual void OnKeyboardFocusChanged(bool focused) = 0;
virtual void OnTextInputFocusChanged(bool focused) = 0;
};
WaylandWindow(const WaylandWindow&) = delete;
WaylandWindow& operator=(const WaylandWindow&) = delete;
@ -127,9 +134,19 @@ class WaylandWindow : public PlatformWindow,
const gfx::FrameData& data,
std::vector<wl::WaylandOverlayConfig>& overlays);
// Called when the focus changed on this window.
void set_focus_client(FocusClient* focus_client) {
focus_client_ = focus_client;
}
// Called when the pointer focus changed on this window.
void OnPointerFocusChanged(bool focused);
// Called when the keyboard focus changed on this window.
void OnKeyboardFocusChanged(bool focused);
// Called when the text input focus changed on this window.
void OnTextInputFocusChanged(bool focused);
// Returns the focus status of this window.
bool HasPointerFocus() const;
bool HasKeyboardFocus() const;
@ -619,6 +636,10 @@ class WaylandWindow : public PlatformWindow,
// dip.
gfx::Rect restored_bounds_dip_;
// A focus client that, once set, is expected to live at least as long as this
// window.
raw_ptr<FocusClient> focus_client_ = nullptr;
// This holds the currently applied state. When in doubt, use this as the
// source of truth for this window's state. Whenever applied_state_ is
// changed, that change should be applied and a new in-flight request and

@ -17,6 +17,38 @@
namespace ui {
namespace {
void UpdateToplevelActivation(WaylandWindow* old_focused_window,
WaylandWindow* new_focused_window) {
auto* old_focused_toplevel_window =
old_focused_window
? old_focused_window->GetRootParentWindow()->AsWaylandToplevelWindow()
: nullptr;
auto* focused_toplevel_window =
new_focused_window
? new_focused_window->GetRootParentWindow()->AsWaylandToplevelWindow()
: nullptr;
if (focused_toplevel_window != old_focused_toplevel_window) {
if (old_focused_toplevel_window) {
old_focused_toplevel_window->UpdateActivationState();
}
if (focused_toplevel_window) {
focused_toplevel_window->UpdateActivationState();
}
}
}
void UpdateParentToplevelAcivationOnRemoval(WaylandWindow* window) {
auto* toplevel_window =
window->GetRootParentWindow()->AsWaylandToplevelWindow();
if (toplevel_window && toplevel_window != window) {
toplevel_window->UpdateActivationState();
}
}
} // namespace
WaylandWindowManager::WaylandWindowManager(WaylandConnection* connection)
: connection_(connection) {}
@ -166,6 +198,10 @@ WaylandWindow* WaylandWindowManager::GetCurrentKeyboardFocusedWindow() const {
return keyboard_focused_window_;
}
WaylandWindow* WaylandWindowManager::GetCurrentTextInputFocusedWindow() const {
return text_input_focused_window_;
}
void WaylandWindowManager::SetPointerFocusedWindow(WaylandWindow* window) {
auto* old_focused_window = GetCurrentPointerFocusedWindow();
if (window == old_focused_window)
@ -192,22 +228,28 @@ void WaylandWindowManager::SetKeyboardFocusedWindow(WaylandWindow* window) {
if (window == old_focused_window)
return;
keyboard_focused_window_ = window;
observers_.Notify(&WaylandWindowObserver::OnKeyboardFocusedWindowChanged);
auto* old_focused_toplevel_window =
old_focused_window
? old_focused_window->GetRootParentWindow()->AsWaylandToplevelWindow()
: nullptr;
auto* focused_toplevel_window =
window ? window->GetRootParentWindow()->AsWaylandToplevelWindow()
: nullptr;
if (focused_toplevel_window != old_focused_toplevel_window) {
if (old_focused_toplevel_window) {
old_focused_toplevel_window->UpdateActivationState();
}
if (focused_toplevel_window) {
focused_toplevel_window->UpdateActivationState();
}
if (old_focused_window) {
old_focused_window->OnKeyboardFocusChanged(false);
}
if (window) {
window->OnKeyboardFocusChanged(true);
}
UpdateToplevelActivation(old_focused_window, window);
}
void WaylandWindowManager::SetTextInputFocusedWindow(WaylandWindow* window) {
auto* old_focused_window = GetCurrentTextInputFocusedWindow();
if (window == old_focused_window) {
return;
}
text_input_focused_window_ = window;
if (old_focused_window) {
old_focused_window->OnTextInputFocusChanged(false);
}
if (window) {
window->OnTextInputFocusChanged(true);
}
UpdateToplevelActivation(old_focused_window, window);
}
void WaylandWindowManager::AddWindow(gfx::AcceleratedWidget widget,
@ -223,24 +265,23 @@ void WaylandWindowManager::RemoveWindow(gfx::AcceleratedWidget widget) {
window_map_.erase(widget);
// Reset `pointer_focused_window_` and `keyboard_focused_window_` before
// notifying any observers to make sure GetCurrentPointerFocusedWindow() and
// GetCurrentKeyboardFocusedWindow() behave correctly. Especially the former
// can be problematic if notifying WaylandWindowDragController that a window
// has been removed before resetting `pointer_focused_window_`, because that
// leads to WaylandEventSource::OnPointerButtonEvent() being called, which
// then calls GetCurrentPointerFocusedWindow().
// Reset `*_focused_window_` before notifying any observers to make sure
// GetCurrent*FocusedWindow() behave correctly.
// The pointer case in particular can be problematic if notifying
// WaylandWindowDragController that a window has been removed before resetting
// `pointer_focused_window_`, because that leads to
// WaylandEventSource::OnPointerButtonEvent() being called, which then calls
// GetCurrentPointerFocusedWindow().
if (window == pointer_focused_window_) {
pointer_focused_window_ = nullptr;
}
if (window == keyboard_focused_window_) {
keyboard_focused_window_ = nullptr;
observers_.Notify(&WaylandWindowObserver::OnKeyboardFocusedWindowChanged);
auto* toplevel_window =
window->GetRootParentWindow()->AsWaylandToplevelWindow();
if (toplevel_window && toplevel_window != window) {
toplevel_window->UpdateActivationState();
}
UpdateParentToplevelAcivationOnRemoval(window);
}
if (window == text_input_focused_window_) {
text_input_focused_window_ = nullptr;
UpdateParentToplevelAcivationOnRemoval(window);
}
observers_.Notify(&WaylandWindowObserver::OnWindowRemoved, window);

@ -80,6 +80,9 @@ class WaylandWindowManager {
// Returns a current focused window by keyboard.
WaylandWindow* GetCurrentKeyboardFocusedWindow() const;
// Returns a current focused window by text input.
WaylandWindow* GetCurrentTextInputFocusedWindow() const;
// Sets the given window as the pointer focused window.
// If there already is another, the old one will be unset.
// If nullptr is passed to |window|, it means pointer focus is unset from
@ -101,6 +104,14 @@ class WaylandWindowManager {
// The given |window| must be managed by this manager.
void SetKeyboardFocusedWindow(WaylandWindow* window);
// Sets the given window as the text input focused window.
// If there already is another, the old one will be unset.
// If nullptr is passed to |window|, it means text-input focus is unset from
// any window.
// The given |window| must be managed by this manager.
// Text input focus usually follows keyboard focus.
void SetTextInputFocusedWindow(WaylandWindow* window);
// Returns all stored windows.
std::vector<WaylandWindow*> GetAllWindows() const;
@ -135,6 +146,7 @@ class WaylandWindowManager {
private:
raw_ptr<WaylandWindow> pointer_focused_window_ = nullptr;
raw_ptr<WaylandWindow> keyboard_focused_window_ = nullptr;
raw_ptr<WaylandWindow> text_input_focused_window_ = nullptr;
const raw_ptr<WaylandConnection> connection_;

@ -23,8 +23,6 @@ void WaylandWindowObserver::OnSubsurfaceRemoved(WaylandWindow* window,
WaylandSubsurface* subsurface) {
}
void WaylandWindowObserver::OnKeyboardFocusedWindowChanged() {}
void WaylandWindowObserver::OnWindowRemovedFromSession(WaylandWindow* window) {}
} // namespace ui

@ -35,11 +35,6 @@ class WaylandWindowObserver : public base::CheckedObserver {
virtual void OnSubsurfaceRemoved(WaylandWindow* window,
WaylandSubsurface* subsurface);
// Called when the keyboard focused window is changed.
// The latest keyboard focused window can be obtain via
// WaylandWindowManager::GetCurrentKeyboardFocusedWindow().
virtual void OnKeyboardFocusedWindowChanged();
// Called when a window is being permanently removed from the
// session it did belong to.
virtual void OnWindowRemovedFromSession(WaylandWindow* window);

@ -13,6 +13,7 @@
#include "base/numerics/safe_conversions.h"
#include "ui/base/wayland/wayland_client_input_types.h"
#include "ui/gfx/range/range.h"
#include "ui/ozone/platform/wayland/common/wayland_util.h"
#include "ui/ozone/platform/wayland/host/span_style.h"
#include "ui/ozone/platform/wayland/host/wayland_connection.h"
#include "ui/ozone/platform/wayland/host/wayland_seat.h"
@ -333,19 +334,17 @@ void ZwpTextInputV3Impl::Commit() {
void ZwpTextInputV3Impl::OnEnter(void* data,
struct zwp_text_input_v3* text_input,
struct wl_surface* surface) {
// Same as text-input-v1, we don't use this for text-input focus changes and
// instead use wayland keyboard enter/leave events to activate or deactivate
// text-input.
NOTIMPLEMENTED_LOG_ONCE();
auto* self = static_cast<ZwpTextInputV3Impl*>(data);
if (auto* window = wl::RootWindowFromWlSurface(surface)) {
self->connection_->window_manager()->SetTextInputFocusedWindow(window);
}
}
void ZwpTextInputV3Impl::OnLeave(void* data,
struct zwp_text_input_v3* text_input,
struct wl_surface* surface) {
// Same as text-input-v1, we don't use this for text-input focus changes and
// instead use wayland keyboard enter/leave events to activate or deactivate
// text-input.
NOTIMPLEMENTED_LOG_ONCE();
auto* self = static_cast<ZwpTextInputV3Impl*>(data);
self->connection_->window_manager()->SetTextInputFocusedWindow(nullptr);
}
void ZwpTextInputV3Impl::OnPreeditString(void* data,

@ -35,8 +35,7 @@ class ZwpTextInputV3Test : public WaylandTestSimple {
void SetUp() override {
WaylandTestSimple::SetUp();
text_input_v3_ = std::make_unique<ZwpTextInputV3Impl>(
connection_.get(), connection_->text_input_manager_v3());
text_input_v3_ = connection_->EnsureTextInputV3();
text_input_v3_->SetClient(&test_client_);
}
@ -46,7 +45,7 @@ class ZwpTextInputV3Test : public WaylandTestSimple {
}
MockZwpTextInputV3Client test_client_;
std::unique_ptr<ZwpTextInputV3> text_input_v3_;
raw_ptr<ZwpTextInputV3> text_input_v3_;
};
TEST_F(ZwpTextInputV3Test, Enable) {

@ -176,7 +176,8 @@ class OzonePlatformWayland : public OzonePlatform,
std::unique_ptr<InputMethod> CreateInputMethod(
ImeKeyEventDispatcher* ime_key_event_dispatcher,
gfx::AcceleratedWidget widget) override {
return std::make_unique<InputMethodAuraLinux>(ime_key_event_dispatcher);
return std::make_unique<InputMethodAuraLinux>(ime_key_event_dispatcher,
widget);
}
PlatformMenuUtils* GetPlatformMenuUtils() override {

@ -133,11 +133,12 @@ class OzonePlatformX11 : public OzonePlatform,
std::unique_ptr<InputMethod> CreateInputMethod(
ImeKeyEventDispatcher* ime_key_event_dispatcher,
gfx::AcceleratedWidget) override {
gfx::AcceleratedWidget widget) override {
#if BUILDFLAG(IS_CHROMEOS)
return std::make_unique<ash::InputMethodAsh>(ime_key_event_dispatcher);
#else
return std::make_unique<InputMethodAuraLinux>(ime_key_event_dispatcher);
return std::make_unique<InputMethodAuraLinux>(ime_key_event_dispatcher,
widget);
#endif
}