zcr_remote_shell_impl_unittest.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/exo/wayland/zcr_remote_shell_impl.h"
  5. #include <wayland-server-core.h>
  6. #include <wayland-server.h>
  7. #include <memory>
  8. #include <vector>
  9. #include "ash/shell.h"
  10. #include "ash/test/ash_test_base.h"
  11. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  12. #include "base/bind.h"
  13. #include "base/posix/unix_domain_socket.h"
  14. #include "components/exo/display.h"
  15. #include "components/exo/shell_surface.h"
  16. #include "components/exo/test/exo_test_base.h"
  17. #include "components/exo/test/shell_surface_builder.h"
  18. #include "components/exo/wayland/server_util.h"
  19. #include "ui/display/display.h"
  20. #include "ui/display/screen.h"
  21. #include "ui/views/widget/widget.h"
  22. namespace exo {
  23. namespace wayland {
  24. namespace {
  25. const int kDefaultWindowLength = 100;
  26. enum class RemoteShellEventType { kSendBoundsChanged, kSendWorkspaceInfo };
  27. struct WlDisplayDeleter {
  28. void operator()(wl_display* ptr) const { wl_display_destroy(ptr); }
  29. };
  30. using ScopedWlDisplay = std::unique_ptr<wl_display, WlDisplayDeleter>;
  31. struct WlClientDeleter {
  32. void operator()(wl_client* ptr) const { wl_client_destroy(ptr); }
  33. };
  34. using ScopedWlClient = std::unique_ptr<wl_client, WlClientDeleter>;
  35. struct WlResourceDeleter {
  36. void operator()(wl_resource* ptr) const { wl_resource_destroy(ptr); }
  37. };
  38. using ScopedWlResource = std::unique_ptr<wl_resource, WlResourceDeleter>;
  39. } // namespace
  40. class WaylandRemoteShellTest : public test::ExoTestBase {
  41. public:
  42. WaylandRemoteShellTest()
  43. : test::ExoTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  44. WaylandRemoteShellTest(const WaylandRemoteShellTest&) = delete;
  45. WaylandRemoteShellTest& operator=(const WaylandRemoteShellTest&) = delete;
  46. // test::ExoTestBase:
  47. void SetUp() override {
  48. test::ExoTestBase::SetUp();
  49. ResetEventRecords();
  50. UpdateDisplay("800x600");
  51. base::CreateSocketPair(&reader_, &writer_);
  52. wl_display_.reset(wl_display_create());
  53. wl_client_.reset(wl_client_create(wl_display_.get(), reader_.release()));
  54. // Use 0 as the id here to avoid the id conflict (i.e. let wayland library
  55. // choose the id from available ids.) Otherwise that will cause memory leak.
  56. wl_shell_resource_.reset(wl_resource_create(wl_client_.get(),
  57. &zcr_remote_shell_v2_interface,
  58. /*version=*/1, /*id=*/0));
  59. wl_remote_surface_resource_.reset(
  60. wl_resource_create(wl_client(), &zcr_remote_surface_v2_interface,
  61. /*version=*/1, /*id=*/0));
  62. display_ = std::make_unique<Display>();
  63. shell_ = std::make_unique<WaylandRemoteShell>(
  64. display_.get(), wl_shell_resource_.get(),
  65. base::BindRepeating(
  66. [](int64_t) { return static_cast<wl_resource*>(nullptr); }),
  67. test_event_mapping_,
  68. /*use_default_scale_cancellation_default=*/true);
  69. }
  70. void TearDown() override {
  71. shell_.reset();
  72. display_.reset();
  73. test::ExoTestBase::TearDown();
  74. }
  75. void EnableTabletMode(bool enable) {
  76. ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(enable);
  77. }
  78. std::unique_ptr<ClientControlledShellSurface::Delegate> CreateDelegate() {
  79. return shell()->CreateShellSurfaceDelegate(
  80. wl_remote_surface_resource_.get());
  81. }
  82. void ResetEventRecords() {
  83. remote_shell_event_sequence_.clear();
  84. remote_shell_requested_bounds_changes_.clear();
  85. }
  86. WaylandRemoteShell* shell() { return shell_.get(); }
  87. wl_client* wl_client() { return wl_client_.get(); }
  88. wl_resource* wl_remote_surface() { return wl_remote_surface_resource_.get(); }
  89. static std::vector<RemoteShellEventType> remote_shell_event_sequence() {
  90. return remote_shell_event_sequence_;
  91. }
  92. static std::vector<WaylandRemoteShell::BoundsChangeData>
  93. remote_shell_requested_bounds_changes() {
  94. return remote_shell_requested_bounds_changes_;
  95. }
  96. static int last_desktop_focus_state() { return last_desktop_focus_state_; }
  97. private:
  98. std::unique_ptr<Display> display_;
  99. base::ScopedFD reader_, writer_;
  100. ScopedWlDisplay wl_display_;
  101. ScopedWlClient wl_client_;
  102. ScopedWlResource wl_shell_resource_;
  103. ScopedWlResource wl_remote_surface_resource_;
  104. std::unique_ptr<WaylandRemoteShell> shell_;
  105. static std::vector<RemoteShellEventType> remote_shell_event_sequence_;
  106. static std::vector<WaylandRemoteShell::BoundsChangeData>
  107. remote_shell_requested_bounds_changes_;
  108. static uint32_t last_desktop_focus_state_;
  109. const WaylandRemoteShellEventMapping test_event_mapping_ = {
  110. /*send_window_geometry_changed=*/+[](struct wl_resource*,
  111. int32_t,
  112. int32_t,
  113. int32_t,
  114. int32_t) {},
  115. /*send_change_zoom_level=*/+[](struct wl_resource*, int32_t) {},
  116. /*send_state_type_changed=*/+[](struct wl_resource*, uint32_t) {},
  117. /*send_bounds_changed_in_output=*/
  118. +[](struct wl_resource*,
  119. struct wl_resource*,
  120. int32_t,
  121. int32_t,
  122. int32_t,
  123. int32_t,
  124. uint32_t) {},
  125. /*send_bounds_changed=*/
  126. +[](struct wl_resource*,
  127. uint32_t display_id_hi,
  128. uint32_t display_id_lo,
  129. int32_t x,
  130. int32_t y,
  131. int32_t width,
  132. int32_t height,
  133. uint32_t reason) {
  134. remote_shell_event_sequence_.push_back(
  135. RemoteShellEventType::kSendBoundsChanged);
  136. remote_shell_requested_bounds_changes_.emplace_back(
  137. (((int64_t)display_id_hi << 32) | display_id_lo),
  138. gfx::Rect(x, y, width, height),
  139. static_cast<zcr_remote_surface_v1_bounds_change_reason>(reason));
  140. },
  141. /*send_activated=*/
  142. +[](struct wl_resource*, struct wl_resource*, struct wl_resource*) {},
  143. /*send_desktop_focus_state_changed=*/
  144. +[](struct wl_resource*, uint32_t state) {
  145. last_desktop_focus_state_ = state;
  146. },
  147. /*send_workspace_info=*/
  148. +[](struct wl_resource*,
  149. uint32_t,
  150. uint32_t,
  151. int32_t,
  152. int32_t,
  153. int32_t,
  154. int32_t,
  155. int32_t,
  156. int32_t,
  157. int32_t,
  158. int32_t,
  159. int32_t,
  160. int32_t,
  161. int32_t,
  162. int32_t,
  163. int32_t,
  164. int32_t,
  165. uint32_t,
  166. struct wl_array*) {
  167. remote_shell_event_sequence_.push_back(
  168. RemoteShellEventType::kSendWorkspaceInfo);
  169. },
  170. /*send_drag_finished=*/
  171. +[](struct wl_resource*, int32_t, int32_t, int32_t) {},
  172. /*send_drag_started=*/+[](struct wl_resource*, uint32_t) {},
  173. /*send_layout_mode=*/+[](struct wl_resource*, uint32_t) {},
  174. /*send_default_device_scale_factor=*/+[](struct wl_resource*, int32_t) {},
  175. /*send_configure=*/+[](struct wl_resource*, uint32_t) {},
  176. /*bounds_changed_in_output_since_version=*/0,
  177. /*desktop_focus_state_changed_since_version=*/0,
  178. /*layout_mode_since_version=*/0,
  179. /*default_device_scale_factor_since_version=*/0,
  180. /*change_zoom_level_since_version=*/0,
  181. /*send_workspace_info_since_version=*/0,
  182. /*set_use_default_scale_cancellation_since_version=*/0,
  183. };
  184. };
  185. std::vector<RemoteShellEventType>
  186. WaylandRemoteShellTest::remote_shell_event_sequence_;
  187. std::vector<WaylandRemoteShell::BoundsChangeData>
  188. WaylandRemoteShellTest::remote_shell_requested_bounds_changes_;
  189. uint32_t WaylandRemoteShellTest::last_desktop_focus_state_ = 0;
  190. // Test that all bounds change requests are deferred while the tablet transition
  191. // is happening until it's finished.
  192. TEST_F(WaylandRemoteShellTest, TabletTransition) {
  193. auto shell_surface = exo::test::ShellSurfaceBuilder({256, 256})
  194. .SetDelegate(CreateDelegate())
  195. .BuildClientControlledShellSurface();
  196. auto* surface = shell_surface->root_surface();
  197. auto* const widget = shell_surface->GetWidget();
  198. auto* const window = widget->GetNativeWindow();
  199. // Snap window.
  200. ash::WindowSnapWMEvent event(ash::WM_EVENT_SNAP_PRIMARY);
  201. ash::WindowState::Get(window)->OnWMEvent(&event);
  202. shell_surface->SetSnappedToPrimary();
  203. shell_surface->SetGeometry(gfx::Rect(0, 0, 400, 520));
  204. surface->Commit();
  205. // Enable tablet mode.
  206. ResetEventRecords();
  207. EnableTabletMode(true);
  208. task_environment()->FastForwardBy(base::Seconds(1));
  209. task_environment()->RunUntilIdle();
  210. const auto expected_sequence = std::vector<RemoteShellEventType>{
  211. RemoteShellEventType::kSendWorkspaceInfo,
  212. RemoteShellEventType::kSendBoundsChanged};
  213. EXPECT_EQ(expected_sequence, remote_shell_event_sequence());
  214. // TODO(b/236432849): Add a reasonable bounds check.
  215. }
  216. // Verifies bounds change events and workspace info events are triggered with
  217. // proper values and in proper order when display zoom happens. A bounds change
  218. // event must be triggered only for PIP.
  219. TEST_F(WaylandRemoteShellTest, DisplayZoom) {
  220. // Test a restored window first.
  221. auto shell_surface =
  222. exo::test::ShellSurfaceBuilder({256, 256})
  223. .SetDelegate(CreateDelegate())
  224. .SetWindowState(chromeos::WindowStateType::kNormal)
  225. .SetGeometry({100, 100, kDefaultWindowLength, kDefaultWindowLength})
  226. .BuildClientControlledShellSurface();
  227. auto* surface = shell_surface->root_surface();
  228. auto* window = shell_surface->GetWidget()->GetNativeWindow();
  229. const display::Display& display =
  230. display::Screen::GetScreen()->GetDisplayNearestWindow(window);
  231. ResetEventRecords();
  232. ash::Shell::Get()->display_manager()->ZoomDisplay(display.id(), /*up=*/true);
  233. task_environment()->RunUntilIdle();
  234. const auto expected_sequence_for_restored = std::vector<RemoteShellEventType>{
  235. RemoteShellEventType::kSendWorkspaceInfo};
  236. EXPECT_EQ(expected_sequence_for_restored, remote_shell_event_sequence());
  237. // Test a maximized window.
  238. shell_surface->SetMaximized();
  239. surface->Commit();
  240. ResetEventRecords();
  241. ash::Shell::Get()->display_manager()->ZoomDisplay(display.id(), /*up=*/true);
  242. task_environment()->RunUntilIdle();
  243. const auto expected_sequence_for_maximized =
  244. std::vector<RemoteShellEventType>{
  245. RemoteShellEventType::kSendWorkspaceInfo};
  246. EXPECT_EQ(expected_sequence_for_maximized, remote_shell_event_sequence());
  247. // Test a PIP window.
  248. shell_surface->SetPip();
  249. // Place PIP at the bottom-right corner so the position will be adjusted with
  250. // display size change. This means no bounds change event is triggered if PIP
  251. // is at the top-left corner, but this is fine as the position doesn't need
  252. // to be adjusted on the client side.
  253. shell_surface->SetGeometry(
  254. gfx::Rect(display.bounds().right() - kDefaultWindowLength,
  255. display.bounds().bottom() - kDefaultWindowLength,
  256. kDefaultWindowLength, kDefaultWindowLength));
  257. surface->Commit();
  258. ResetEventRecords();
  259. ash::Shell::Get()->display_manager()->ZoomDisplay(display.id(), /*up=*/true);
  260. task_environment()->RunUntilIdle();
  261. const auto expected_sequence_for_pip = std::vector<RemoteShellEventType>{
  262. RemoteShellEventType::kSendWorkspaceInfo,
  263. RemoteShellEventType::kSendBoundsChanged};
  264. EXPECT_EQ(remote_shell_event_sequence(), expected_sequence_for_pip);
  265. ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
  266. const auto bounds_change = remote_shell_requested_bounds_changes()[0];
  267. EXPECT_EQ(display.id(), bounds_change.display_id);
  268. // Verify that the new bounds is scaled larger in pixels.
  269. EXPECT_GT(kDefaultWindowLength, bounds_change.bounds_in_display.width());
  270. EXPECT_GT(kDefaultWindowLength, bounds_change.bounds_in_display.height());
  271. EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_PIP,
  272. bounds_change.reason);
  273. }
  274. // Verifies bounds change events and workspace info events are triggered with
  275. // proper values and in proper order when display rotation happens. A bounds
  276. // change event must be triggered only for PIP.
  277. TEST_F(WaylandRemoteShellTest, DisplayRotation) {
  278. // Test a restored window first.
  279. auto shell_surface =
  280. exo::test::ShellSurfaceBuilder({256, 256})
  281. .SetDelegate(CreateDelegate())
  282. .SetWindowState(chromeos::WindowStateType::kNormal)
  283. .SetGeometry({100, 100, kDefaultWindowLength, kDefaultWindowLength})
  284. .BuildClientControlledShellSurface();
  285. auto* surface = shell_surface->root_surface();
  286. auto* window = shell_surface->GetWidget()->GetNativeWindow();
  287. const display::Display& display =
  288. display::Screen::GetScreen()->GetDisplayNearestWindow(window);
  289. ResetEventRecords();
  290. ash::Shell::Get()->display_manager()->SetDisplayRotation(
  291. display.id(), display::Display::ROTATE_90,
  292. display::Display::RotationSource::ACCELEROMETER);
  293. task_environment()->RunUntilIdle();
  294. const auto expected_sequence_for_restored = std::vector<RemoteShellEventType>{
  295. RemoteShellEventType::kSendWorkspaceInfo};
  296. EXPECT_EQ(expected_sequence_for_restored, remote_shell_event_sequence());
  297. // Test a maximized window.
  298. shell_surface->SetMaximized();
  299. surface->Commit();
  300. ResetEventRecords();
  301. ash::Shell::Get()->display_manager()->SetDisplayRotation(
  302. display.id(), display::Display::ROTATE_180,
  303. display::Display::RotationSource::ACCELEROMETER);
  304. task_environment()->RunUntilIdle();
  305. const auto expected_sequence_for_maximized =
  306. std::vector<RemoteShellEventType>{
  307. RemoteShellEventType::kSendWorkspaceInfo,
  308. RemoteShellEventType::kSendBoundsChanged};
  309. EXPECT_EQ(expected_sequence_for_maximized, remote_shell_event_sequence());
  310. // Test a PIP window.
  311. shell_surface->SetPip();
  312. // Place PIP at the bottom-right corner so the position will be adjusted with
  313. // display rotation.
  314. shell_surface->SetGeometry(
  315. gfx::Rect(display.bounds().right(), display.bounds().bottom(),
  316. kDefaultWindowLength, kDefaultWindowLength));
  317. surface->Commit();
  318. const gfx::Rect bounds = window->GetBoundsInScreen();
  319. const int right_inset = display.bounds().right() - bounds.right();
  320. const int bottom_inset = display.bounds().bottom() - bounds.bottom();
  321. ResetEventRecords();
  322. ash::Shell::Get()->display_manager()->SetDisplayRotation(
  323. display.id(), display::Display::ROTATE_270,
  324. display::Display::RotationSource::ACCELEROMETER);
  325. task_environment()->RunUntilIdle();
  326. const auto expected_sequence_for_pip = std::vector<RemoteShellEventType>{
  327. RemoteShellEventType::kSendWorkspaceInfo,
  328. RemoteShellEventType::kSendBoundsChanged};
  329. EXPECT_EQ(expected_sequence_for_pip, remote_shell_event_sequence());
  330. ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
  331. const auto bounds_change = remote_shell_requested_bounds_changes()[0];
  332. EXPECT_EQ(display.id(), bounds_change.display_id);
  333. const display::Display& rotated_display =
  334. display::Screen::GetScreen()->GetDisplayNearestWindow(window);
  335. const int expected_x =
  336. rotated_display.bounds().right() - right_inset - kDefaultWindowLength;
  337. const int expected_y =
  338. rotated_display.bounds().bottom() - bottom_inset - kDefaultWindowLength;
  339. const gfx::Rect expected_bounds = gfx::Rect(
  340. expected_x, expected_y, kDefaultWindowLength, kDefaultWindowLength);
  341. EXPECT_EQ(expected_bounds, bounds_change.bounds_in_display);
  342. EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_PIP,
  343. bounds_change.reason);
  344. }
  345. // Removing secandary display and re-reconnect it restores the bounds of
  346. // windows on secandary display. This test verifies bounds change events
  347. // and workspace info events are triggered with proper values and in
  348. // proper order.
  349. TEST_F(WaylandRemoteShellTest, DisplayRemovalAddition) {
  350. auto shell_surface = exo::test::ShellSurfaceBuilder(
  351. {kDefaultWindowLength, kDefaultWindowLength})
  352. .SetDelegate(CreateDelegate())
  353. .BuildClientControlledShellSurface();
  354. auto* surface = shell_surface->root_surface();
  355. // Add secondary display with a different scale factor.
  356. UpdateDisplay("800x600,800x600*2");
  357. auto* display_manager = ash::Shell::Get()->display_manager();
  358. const int64_t primary_display_id = display_manager->GetDisplayAt(0).id();
  359. const int64_t secondary_display_id = display_manager->GetDisplayAt(1).id();
  360. display::ManagedDisplayInfo primary_display_info =
  361. display_manager->GetDisplayInfo(primary_display_id);
  362. display::ManagedDisplayInfo secondary_display_info =
  363. display_manager->GetDisplayInfo(secondary_display_id);
  364. // Move the window to the secandary display.
  365. const int initial_x = 100;
  366. const int initial_y = 100;
  367. shell_surface->SetBounds(secondary_display_id,
  368. gfx::Rect(initial_x, initial_y, kDefaultWindowLength,
  369. kDefaultWindowLength));
  370. surface->Commit();
  371. // Disconnect secondary display.
  372. ResetEventRecords();
  373. std::vector<display::ManagedDisplayInfo> display_info_list;
  374. display_info_list.push_back(primary_display_info);
  375. display_manager->OnNativeDisplaysChanged(display_info_list);
  376. task_environment()->RunUntilIdle();
  377. const auto event_sequence_disconnect = std::vector<RemoteShellEventType>{
  378. RemoteShellEventType::kSendWorkspaceInfo,
  379. RemoteShellEventType::kSendBoundsChanged};
  380. EXPECT_EQ(remote_shell_event_sequence(), event_sequence_disconnect);
  381. ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
  382. const auto bounds_change = remote_shell_requested_bounds_changes()[0];
  383. EXPECT_EQ(bounds_change.display_id, primary_display_id);
  384. // Verify the new bounds is scaled in pixles with the scale factor of the
  385. // primary display.
  386. const gfx::Rect expected_bounds_after_disconnection = gfx::Rect(
  387. initial_x, initial_y, kDefaultWindowLength / 2, kDefaultWindowLength / 2);
  388. EXPECT_EQ(expected_bounds_after_disconnection,
  389. bounds_change.bounds_in_display);
  390. EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_MOVE,
  391. bounds_change.reason);
  392. // Reconnects the previously connected secondary display.
  393. ResetEventRecords();
  394. display_info_list.push_back(secondary_display_info);
  395. display_manager->OnNativeDisplaysChanged(display_info_list);
  396. task_environment()->RunUntilIdle();
  397. // Reconnecting the secondary display seems to cause two workspace info
  398. // events: One for a display metrics change for the primary display, and the
  399. // other for a display addition event of the secondary display.
  400. const auto event_sequence_reconnect = std::vector<RemoteShellEventType>{
  401. RemoteShellEventType::kSendWorkspaceInfo,
  402. RemoteShellEventType::kSendWorkspaceInfo,
  403. RemoteShellEventType::kSendBoundsChanged};
  404. EXPECT_EQ(event_sequence_reconnect, remote_shell_event_sequence());
  405. ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
  406. const auto bounds_change_to_secondary =
  407. remote_shell_requested_bounds_changes()[0];
  408. EXPECT_EQ(secondary_display_id, bounds_change_to_secondary.display_id);
  409. const gfx::Rect expected_bounds_after_reconnection = gfx::Rect(
  410. initial_x, initial_y, kDefaultWindowLength, kDefaultWindowLength);
  411. EXPECT_EQ(expected_bounds_after_reconnection,
  412. bounds_change_to_secondary.bounds_in_display);
  413. EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_MOVE,
  414. bounds_change_to_secondary.reason);
  415. }
  416. // Test that the desktop focus state event is called with the proper value in
  417. // response to window focus change.
  418. TEST_F(WaylandRemoteShellTest, DesktopFocusState) {
  419. auto client_controlled_shell_surface =
  420. exo::test::ShellSurfaceBuilder(
  421. {kDefaultWindowLength, kDefaultWindowLength})
  422. .SetDelegate(CreateDelegate())
  423. .SetNoCommit()
  424. .BuildClientControlledShellSurface();
  425. auto* surface = client_controlled_shell_surface->root_surface();
  426. SetSurfaceResource(surface, wl_remote_surface());
  427. surface->Commit();
  428. EXPECT_EQ(last_desktop_focus_state(), 2);
  429. client_controlled_shell_surface->SetMinimized();
  430. surface->Commit();
  431. EXPECT_EQ(last_desktop_focus_state(), 1);
  432. auto shell_surface = exo::test::ShellSurfaceBuilder(
  433. {kDefaultWindowLength, kDefaultWindowLength})
  434. .BuildShellSurface();
  435. auto* other_client_window = shell_surface->GetWidget()->GetNativeWindow();
  436. other_client_window->Show();
  437. other_client_window->Focus();
  438. EXPECT_EQ(last_desktop_focus_state(), 3);
  439. }
  440. } // namespace wayland
  441. } // namespace exo