| // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <gflags/gflags.h> |
| #include <gtest/gtest.h> |
| |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "cros/chromeos_wm_ipc_enums.h" |
| #include "window_manager/compositor/compositor.h" |
| #include "window_manager/event_loop.h" |
| #include "window_manager/geometry.h" |
| #include "window_manager/shadow.h" |
| #include "window_manager/test_lib.h" |
| #include "window_manager/util.h" |
| #include "window_manager/window.h" |
| #include "window_manager/window_manager.h" |
| #include "window_manager/x11/mock_x_connection.h" |
| |
| DEFINE_bool(logtostderr, false, |
| "Print debugging messages to stderr (suppressed otherwise)"); |
| |
| DECLARE_bool(load_window_shapes); // from window.cc |
| |
| using std::map; |
| using std::string; |
| using std::vector; |
| |
| namespace window_manager { |
| |
| class WindowTest : public BasicWindowManagerTest {}; |
| |
| // Test that we load a window's title when it's first created (instead of |
| // waiting until we get a PropertyNotify event to load it). |
| TEST_F(WindowTest, Title) { |
| XWindow xid = CreateSimpleWindow(); |
| const string kTitle = "foo"; |
| const XAtom kAtom = xconn_->GetAtomOrDie("_NET_WM_NAME"); |
| xconn_->SetStringProperty(xid, kAtom, kTitle); |
| |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_EQ(kTitle, win.title()); |
| |
| const string kNewTitle = "bar"; |
| xconn_->SetStringProperty(xid, kAtom, kNewTitle); |
| win.FetchAndApplyTitle(); |
| EXPECT_EQ(kNewTitle, win.title()); |
| |
| xconn_->DeletePropertyIfExists(xid, kAtom); |
| win.FetchAndApplyTitle(); |
| EXPECT_EQ("", win.title()); |
| } |
| |
| TEST_F(WindowTest, WindowType) { |
| XWindow xid = CreateSimpleWindow(); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_EQ(chromeos::WM_IPC_WINDOW_UNKNOWN, win.type()); |
| |
| ASSERT_TRUE(wm_->wm_ipc()->SetWindowType( |
| xid, chromeos::WM_IPC_WINDOW_CHROME_TOPLEVEL, NULL)); |
| EXPECT_TRUE(win.FetchAndApplyWindowType()); |
| EXPECT_EQ(chromeos::WM_IPC_WINDOW_CHROME_TOPLEVEL, win.type()); |
| |
| ASSERT_TRUE(wm_->wm_ipc()->SetWindowType( |
| xid, chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE, NULL)); |
| EXPECT_TRUE(win.FetchAndApplyWindowType()); |
| EXPECT_EQ(chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE, win.type()); |
| } |
| |
| TEST_F(WindowTest, ChangeClient) { |
| XWindow xid = CreateBasicWindow(Rect(10, 20, 30, 40)); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window window(wm_.get(), xid, false, geometry); |
| |
| // Make sure that the window's initial attributes are loaded correctly. |
| EXPECT_EQ(xid, window.xid()); |
| EXPECT_EQ(10, window.client_x()); |
| EXPECT_EQ(20, window.client_y()); |
| EXPECT_EQ(30, window.client_width()); |
| EXPECT_EQ(40, window.client_height()); |
| EXPECT_FALSE(window.mapped()); |
| |
| EXPECT_TRUE(window.MapClient()); |
| EXPECT_TRUE(info->mapped); |
| |
| // Move the window. |
| EXPECT_TRUE(window.MoveClient(100, 200)); |
| EXPECT_EQ(100, info->bounds.x); |
| EXPECT_EQ(200, info->bounds.y); |
| EXPECT_EQ(100, window.client_x()); |
| EXPECT_EQ(200, window.client_y()); |
| |
| // Resize the window. |
| EXPECT_TRUE(window.Resize(Size(300, 400), GRAVITY_NORTHWEST)); |
| EXPECT_EQ(300, info->bounds.width); |
| EXPECT_EQ(400, info->bounds.height); |
| EXPECT_EQ(300, window.client_width()); |
| EXPECT_EQ(400, window.client_height()); |
| } |
| |
| TEST_F(WindowTest, ChangeComposited) { |
| XWindow xid = CreateBasicWindow(Rect(10, 20, 30, 40)); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window window(wm_.get(), xid, false, geometry); |
| xconn_->MapWindow(xid); |
| window.HandleMapNotify(); |
| |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&window); |
| |
| // Initially, we should place the composited window at the same location |
| // as the client window. |
| EXPECT_EQ(10, actor->x()); |
| EXPECT_EQ(20, actor->y()); |
| EXPECT_EQ(10, window.composited_x()); |
| EXPECT_EQ(20, window.composited_y()); |
| EXPECT_EQ(30, window.actor()->GetWidth()); |
| EXPECT_EQ(40, window.actor()->GetHeight()); |
| EXPECT_DOUBLE_EQ(1.0, actor->scale_x()); |
| EXPECT_DOUBLE_EQ(1.0, actor->scale_y()); |
| EXPECT_DOUBLE_EQ(1.0, window.composited_scale_x()); |
| EXPECT_DOUBLE_EQ(1.0, window.composited_scale_y()); |
| |
| // Move the composited window to a new spot. |
| window.MoveComposited(40, 50, 0); |
| EXPECT_EQ(40, actor->x()); |
| EXPECT_EQ(50, actor->y()); |
| EXPECT_EQ(40, window.composited_x()); |
| EXPECT_EQ(50, window.composited_y()); |
| |
| window.ScaleComposited(0.75, 0.25, 0); |
| EXPECT_EQ(0.75, actor->scale_x()); |
| EXPECT_EQ(0.25, actor->scale_y()); |
| EXPECT_EQ(0.75, window.composited_scale_x()); |
| EXPECT_EQ(0.25, window.composited_scale_y()); |
| } |
| |
| TEST_F(WindowTest, TransientFor) { |
| XWindow xid = CreateSimpleWindow(); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| |
| XWindow owner_xid = 1234; // arbitrary ID |
| info->transient_for = owner_xid; |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_EQ(owner_xid, win.transient_for_xid()); |
| |
| XWindow new_owner_xid = 5678; |
| info->transient_for = new_owner_xid; |
| EXPECT_TRUE(win.FetchAndApplyTransientHint()); |
| EXPECT_EQ(new_owner_xid, win.transient_for_xid()); |
| } |
| |
| TEST_F(WindowTest, GetMaxSize) { |
| XWindow xid = CreateBasicWindow(Rect(10, 20, 30, 40)); |
| |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| info->size_hints.min_size.reset(400, 300); |
| info->size_hints.max_size.reset(800, 600); |
| info->size_hints.size_increment.reset(10, 5); |
| info->size_hints.base_size.reset(40, 30); |
| |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| ASSERT_TRUE(win.FetchAndApplySizeHints()); |
| Size size; |
| |
| // We should get the minimum size if we request a size smaller than it. |
| win.GetMaxSize(Size(300, 200), &size); |
| EXPECT_EQ(Size(400, 300), size); |
| |
| // And the maximum size if we request one larger than it. |
| win.GetMaxSize(Size(1000, 800), &size); |
| EXPECT_EQ(Size(800, 600), size); |
| |
| // Test that the size increment hints are honored when we request a size |
| // that's not equal to the base size plus some multiple of the size |
| // increments. |
| win.GetMaxSize(Size(609, 409), &size); |
| EXPECT_EQ(Size(600, 405), size); |
| } |
| |
| // Test WM_DELETE_WINDOW and WM_TAKE_FOCUS from ICCCM's WM_PROTOCOLS. |
| TEST_F(WindowTest, WmProtocols) { |
| // Create a window. |
| XWindow xid = CreateSimpleWindow(); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| |
| // Set its WM_PROTOCOLS property to indicate that it supports both |
| // message types. |
| vector<int> values; |
| values.push_back(static_cast<int>(xconn_->GetAtomOrDie("WM_DELETE_WINDOW"))); |
| values.push_back(static_cast<int>(xconn_->GetAtomOrDie("WM_TAKE_FOCUS"))); |
| xconn_->SetIntArrayProperty(xid, |
| xconn_->GetAtomOrDie("WM_PROTOCOLS"), // atom |
| xconn_->GetAtomOrDie("ATOM"), // type |
| values); |
| |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| |
| // Send a WM_DELETE_WINDOW message to the window and check that its |
| // contents are correct. |
| XTime timestamp = 43; // arbitrary |
| EXPECT_TRUE(win.SendDeleteRequest(timestamp)); |
| ASSERT_EQ(1, info->client_messages.size()); |
| const XClientMessageEvent& delete_msg = info->client_messages[0]; |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_PROTOCOLS"), delete_msg.message_type); |
| EXPECT_EQ(XConnection::kLongFormat, delete_msg.format); |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_DELETE_WINDOW"), delete_msg.data.l[0]); |
| EXPECT_EQ(timestamp, delete_msg.data.l[1]); |
| |
| // Now do the same thing with WM_TAKE_FOCUS. |
| timestamp = 98; // arbitrary |
| info->client_messages.clear(); |
| EXPECT_TRUE(win.TakeFocus(timestamp)); |
| ASSERT_EQ(1, info->client_messages.size()); |
| const XClientMessageEvent& focus_msg = info->client_messages[0]; |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_PROTOCOLS"), focus_msg.message_type); |
| EXPECT_EQ(XConnection::kLongFormat, focus_msg.format); |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_TAKE_FOCUS"), focus_msg.data.l[0]); |
| EXPECT_EQ(timestamp, focus_msg.data.l[1]); |
| |
| // Get rid of the window's WM_PROTOCOLS support. |
| xconn_->DeletePropertyIfExists(xid, xconn_->GetAtomOrDie("WM_PROTOCOLS")); |
| win.FetchAndApplyWmProtocols(); |
| info->client_messages.clear(); |
| |
| // SendDeleteRequest() should fail outright if the window doesn't support |
| // WM_DELETE_WINDOW. |
| EXPECT_FALSE(win.SendDeleteRequest(1)); |
| EXPECT_EQ(0, info->client_messages.size()); |
| |
| // TakeFocus() should manually assign the focus with a SetInputFocus |
| // request instead of sending a message. |
| EXPECT_EQ(None, xconn_->focused_xid()); |
| EXPECT_TRUE(win.TakeFocus(timestamp)); |
| EXPECT_EQ(0, info->client_messages.size()); |
| EXPECT_EQ(xid, xconn_->focused_xid()); |
| } |
| |
| TEST_F(WindowTest, WmHints) { |
| // Set the urgency flag on a window and check that it gets loaded correctly. |
| const XAtom wm_hints_atom = xconn_->GetAtomOrDie("WM_HINTS"); |
| XWindow xid = CreateSimpleWindow(); |
| xconn_->SetIntProperty(xid, |
| wm_hints_atom, // atom |
| wm_hints_atom, // type |
| 256); // UrgencyHint flag from ICCCM 4.1.2.4 |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_TRUE(win.wm_hint_urgent()); |
| |
| // Now clear the UrgencyHint flag and set another flag that we don't care |
| // about and check that the window loads the change. |
| vector<int> values; |
| values.push_back(2); // StateHint flag |
| values.push_back(1); // NormalState |
| xconn_->SetIntArrayProperty(xid, wm_hints_atom, wm_hints_atom, values); |
| win.FetchAndApplyWmHints(); |
| EXPECT_FALSE(win.wm_hint_urgent()); |
| |
| // Set it one more time. |
| xconn_->SetIntProperty(xid, wm_hints_atom, wm_hints_atom, 256); |
| win.FetchAndApplyWmHints(); |
| EXPECT_TRUE(win.wm_hint_urgent()); |
| } |
| |
| TEST_F(WindowTest, WmState) { |
| XAtom wm_state_atom = xconn_->GetAtomOrDie("_NET_WM_STATE"); |
| XAtom fullscreen_atom = xconn_->GetAtomOrDie("_NET_WM_STATE_FULLSCREEN"); |
| XAtom max_horz_atom = xconn_->GetAtomOrDie("_NET_WM_STATE_MAXIMIZED_HORZ"); |
| XAtom max_vert_atom = xconn_->GetAtomOrDie("_NET_WM_STATE_MAXIMIZED_VERT"); |
| XAtom modal_atom = xconn_->GetAtomOrDie("_NET_WM_STATE_MODAL"); |
| |
| // Create a window with its _NET_WM_STATE property set to only |
| // _NET_WM_STATE_MODAL and make sure that it's correctly loaded in the |
| // constructor. |
| XWindow xid = CreateSimpleWindow(); |
| xconn_->SetIntProperty(xid, |
| wm_state_atom, // atom |
| xconn_->GetAtomOrDie("ATOM"), // type |
| modal_atom); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_FALSE(win.wm_state_fullscreen()); |
| EXPECT_TRUE(win.wm_state_modal()); |
| |
| // Now make the Window object handle a message removing the modal |
| // state... |
| long data[5]; |
| memset(data, 0, sizeof(data)); |
| data[0] = 0; // remove |
| data[1] = modal_atom; |
| map<XAtom, bool> states; |
| win.ParseWmStateMessage(data, &states); |
| EXPECT_TRUE(win.ChangeWmState(states)); |
| EXPECT_FALSE(win.wm_state_fullscreen()); |
| EXPECT_FALSE(win.wm_state_modal()); |
| |
| // ... and one adding the fullscreen state. |
| data[0] = 1; // add |
| data[1] = fullscreen_atom; |
| win.ParseWmStateMessage(data, &states); |
| EXPECT_TRUE(win.ChangeWmState(states)); |
| EXPECT_TRUE(win.wm_state_fullscreen()); |
| EXPECT_FALSE(win.wm_state_modal()); |
| |
| // Check that the window's _NET_WM_STATE property was updated in response |
| // to the messages. |
| vector<int> values; |
| ASSERT_TRUE(xconn_->GetIntArrayProperty(xid, wm_state_atom, &values)); |
| ASSERT_EQ(1, values.size()); |
| EXPECT_EQ(fullscreen_atom, values[0]); |
| |
| // Test that we can toggle states (and that we process messages listing |
| // multiple states correctly). |
| data[0] = 2; // toggle |
| data[1] = fullscreen_atom; |
| data[2] = modal_atom; |
| win.ParseWmStateMessage(data, &states); |
| EXPECT_TRUE(win.ChangeWmState(states)); |
| EXPECT_FALSE(win.wm_state_fullscreen()); |
| EXPECT_TRUE(win.wm_state_modal()); |
| |
| values.clear(); |
| ASSERT_TRUE(xconn_->GetIntArrayProperty(xid, wm_state_atom, &values)); |
| ASSERT_EQ(1, values.size()); |
| EXPECT_EQ(modal_atom, values[0]); |
| |
| // Test that ChangeWmState() works for clearing the modal state and |
| // setting both maximized states. |
| map<XAtom, bool> changed_states; |
| changed_states[modal_atom] = false; |
| changed_states[max_horz_atom] = true; |
| changed_states[max_vert_atom] = true; |
| EXPECT_TRUE(win.ChangeWmState(changed_states)); |
| values.clear(); |
| ASSERT_TRUE(xconn_->GetIntArrayProperty(xid, wm_state_atom, &values)); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_EQ(max_horz_atom, values[0]); |
| EXPECT_EQ(max_vert_atom, values[1]); |
| } |
| |
| TEST_F(WindowTest, ChromeState) { |
| const XAtom state_atom = xconn_->GetAtomOrDie("_CHROME_STATE"); |
| const XAtom collapsed_atom = |
| xconn_->GetAtomOrDie("_CHROME_STATE_COLLAPSED_PANEL"); |
| // This isn't an atom that we'd actually set in the _CHROME_STATE |
| // property, but we need another atom besides the collapsed one for |
| // testing. |
| const XAtom other_atom = xconn_->GetAtomOrDie("_NET_WM_STATE_MODAL"); |
| |
| // Set the "collapsed" atom on a window. The Window class should load |
| // the initial property in its constructor. |
| XWindow xid = CreateSimpleWindow(); |
| xconn_->SetIntProperty(xid, |
| state_atom, // atom |
| xconn_->GetAtomOrDie("ATOM"), // type |
| collapsed_atom); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| |
| // Tell the window to set the other atom. |
| map<XAtom, bool> states; |
| states[other_atom] = true; |
| EXPECT_TRUE(win.ChangeChromeState(states)); |
| |
| // Check that both atoms are included in the property. |
| vector<int> values; |
| ASSERT_TRUE(xconn_->GetIntArrayProperty(xid, state_atom, &values)); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_EQ(collapsed_atom, values[0]); |
| EXPECT_EQ(other_atom, values[1]); |
| |
| // Now tell the window to unset the "collapsed" atom, and make sure that |
| // only the other atom is present. |
| states.clear(); |
| states[collapsed_atom] = false; |
| EXPECT_TRUE(win.ChangeChromeState(states)); |
| values.clear(); |
| ASSERT_TRUE(xconn_->GetIntArrayProperty(xid, state_atom, &values)); |
| ASSERT_EQ(1, values.size()); |
| EXPECT_EQ(other_atom, values[0]); |
| |
| // If we also unset the other atom, the property should be removed. |
| states.clear(); |
| states[other_atom] = false; |
| EXPECT_TRUE(win.ChangeChromeState(states)); |
| EXPECT_FALSE(xconn_->GetIntArrayProperty(xid, state_atom, &values)); |
| } |
| |
| TEST_F(WindowTest, Shape) { |
| // Loading windows' regions is turned off by default, since it can cause |
| // a pretty big memory allocation for new windows and the compositor |
| // doesn't currently even support using these regions as masks, but we |
| // need to enable it to test this code. |
| AutoReset<bool> flag_resetter(&FLAGS_load_window_shapes, true); |
| |
| // Create a shaped window. |
| int width = 10; |
| int height = 5; |
| XWindow xid = CreateBasicWindow(Rect(10, 20, width, height)); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| info->shape.reset(new ByteMap(Size(width, height))); |
| info->shape->Clear(0xff); |
| info->shape->SetRectangle(Rect(0, 0, 3, 3), 0x0); |
| |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| win.SetShadowType(Shadow::TYPE_RECTANGULAR); |
| EXPECT_TRUE(info->shape_events_selected); |
| EXPECT_TRUE(win.shaped()); |
| win.HandleMapNotify(); |
| win.ShowComposited(); |
| |
| // We should have created a shadow (since SetShadowType() was called), |
| // but we shouldn't be showing it (since the window is shaped). |
| ASSERT_TRUE(win.shadow() != NULL); |
| EXPECT_FALSE(win.shadow()->is_shown()); |
| |
| // Set the opacity for the window's shadow (even though it's not using a |
| // shadow right now). |
| double shadow_opacity = 0.5; |
| win.SetShadowOpacity(shadow_opacity, 0); // anim_ms |
| |
| // Check that the shape mask got applied to the compositing actor. |
| MockCompositor::TexturePixmapActor* mock_actor = GetMockActorForWindow(&win); |
| ASSERT_TRUE(mock_actor->alpha_mask_bytes() != NULL); |
| EXPECT_PRED_FORMAT3(BytesAreEqual, |
| info->shape->bytes(), |
| mock_actor->alpha_mask_bytes(), |
| width * height); |
| |
| // Change the shape and check that the window updates its actor. |
| info->shape->Clear(0xff); |
| info->shape->SetRectangle(Rect(width - 3, height - 3, 3, 3), 0x0); |
| win.FetchAndApplyShape(); |
| EXPECT_TRUE(win.shaped()); |
| EXPECT_FALSE(win.shadow()->is_shown()); |
| ASSERT_TRUE(mock_actor->alpha_mask_bytes() != NULL); |
| EXPECT_PRED_FORMAT3(BytesAreEqual, |
| info->shape->bytes(), |
| mock_actor->alpha_mask_bytes(), |
| width * height); |
| |
| // Now clear the shape and make sure that the mask is removed from the |
| // actor. |
| info->shape.reset(); |
| win.FetchAndApplyShape(); |
| EXPECT_FALSE(win.shaped()); |
| EXPECT_TRUE(mock_actor->alpha_mask_bytes() == NULL); |
| |
| // Since the shape is gone, the shadow should now be shown using the |
| // opacity that was specified earlier. |
| EXPECT_TRUE(win.shadow()->is_shown()); |
| EXPECT_EQ(shadow_opacity, win.shadow()->opacity()); |
| } |
| |
| TEST_F(WindowTest, OverrideRedirectForDestroyedWindow) { |
| // Check that Window's c'tor uses the passed-in override-redirect value |
| // instead of querying the server. If an override-redirect window has |
| // already been destroyed, we don't want to mistakenly think that it's |
| // non-override-redirect. |
| // TODO: Remove this once we're able to grab the server while |
| // constructing Window objects (see comments in window_manager.cc). |
| XConnection::WindowGeometry geometry; |
| Window win(wm_.get(), 43241, true, geometry); |
| EXPECT_TRUE(win.override_redirect()); |
| } |
| |
| // Test that we remove windows' borders. |
| TEST_F(WindowTest, RemoveBorder) { |
| XWindow xid = CreateSimpleWindow(); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| info->border_width = 1; |
| |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_EQ(0, info->border_width); |
| } |
| |
| // Test that we don't resize the composited window until we receive |
| // notification that the client window has been resized. Otherwise, we can |
| // end up with the previous contents being scaled to fit the new size -- |
| // see http://crosbug.com/1279. |
| TEST_F(WindowTest, DeferResizingActor) { |
| const Rect kOrigBounds(0, 0, 300, 200); |
| XWindow xid = CreateToplevelWindow(2, 0, kOrigBounds); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| xconn_->MapWindow(xid); |
| win.HandleMapNotify(); |
| |
| // Check that the actor's initial dimensions match that of the client window. |
| EXPECT_EQ(kOrigBounds.size(), win.actor()->GetBounds().size()); |
| |
| // After resizing the client window, the actor should still still be |
| // using the original dimensions. |
| const Rect kNewBounds(0, 0, 600, 400); |
| win.Resize(kNewBounds.size(), GRAVITY_NORTHWEST); |
| EXPECT_EQ(kOrigBounds.size(), win.actor()->GetBounds().size()); |
| |
| // Now let the window know that we've seen a ConfigureNotify event with |
| // the new dimensions and check that the actor is resized. |
| win.HandleConfigureNotify(kNewBounds, 0); |
| EXPECT_EQ(kNewBounds.size(), win.actor()->GetBounds().size()); |
| } |
| |
| // Test that pixmap actor and shadow sizes get updated correctly in |
| // response to ConfigureNotify events. |
| TEST_F(WindowTest, UpdatePixmapAndShadowSizes) { |
| const int orig_width = 300, orig_height = 200; |
| XWindow xid = CreateToplevelWindow(2, 0, Rect(0, 0, orig_width, orig_height)); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| win.SetShadowType(Shadow::TYPE_RECTANGULAR); |
| |
| // Resize the window once before it gets mapped, to make sure that we get |
| // the updated size later after the window is mapped. |
| const int second_width = orig_width + 10, second_height = orig_height + 10; |
| ASSERT_TRUE(xconn_->ResizeWindow(xid, Size(second_width, second_height))); |
| win.HandleConfigureNotify(info->bounds, 0); |
| |
| // Now map the window and check that everything starts out at the right size. |
| ASSERT_TRUE(xconn_->MapWindow(xid)); |
| win.HandleMapNotify(); |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&win); |
| EXPECT_EQ(second_width, actor->GetWidth()); |
| EXPECT_EQ(second_height, actor->GetHeight()); |
| EXPECT_EQ(second_width, win.shadow()->width()); |
| EXPECT_EQ(second_height, win.shadow()->height()); |
| |
| // We shouldn't reload the pixmap in response to a non-resize |
| // ConfigureNotify event (like what we'll receive whenever the window |
| // gets moved). |
| XID prev_pixmap = actor->pixmap(); |
| win.HandleConfigureNotify(info->bounds, 0); |
| EXPECT_EQ(prev_pixmap, actor->pixmap()); |
| |
| // Now act as if the window gets resized two more times, but the second |
| // resize has already happened in the X server by the time that the |
| // window manager receives the ConfigureNotify for the first resize. |
| const int third_width = second_width + 10, third_height = second_height + 10; |
| const int fourth_width = third_width + 10, fourth_height = third_height + 10; |
| xconn_->ResizeWindow(xid, Size(fourth_width, fourth_height)); |
| win.HandleConfigureNotify( |
| Rect(info->bounds.x, info->bounds.y, third_width, third_height), 0); |
| |
| // We should load the pixmap now and resize the shadow to the dimensions |
| // from the final pixmap instead of the ones supplied in the event. |
| EXPECT_EQ(fourth_width, actor->GetWidth()); |
| EXPECT_EQ(fourth_height, actor->GetHeight()); |
| EXPECT_EQ(fourth_width, win.shadow()->width()); |
| EXPECT_EQ(fourth_height, win.shadow()->height()); |
| |
| // Nothing should change after we get the second ConfigureNotify. |
| win.HandleConfigureNotify(info->bounds, 0); |
| EXPECT_EQ(fourth_width, actor->GetWidth()); |
| EXPECT_EQ(fourth_height, actor->GetHeight()); |
| EXPECT_EQ(fourth_width, win.shadow()->width()); |
| EXPECT_EQ(fourth_height, win.shadow()->height()); |
| } |
| |
| // Test that we show and hide shadows under the proper conditions (note |
| // that a portion of this is covered by the Shape test). |
| TEST_F(WindowTest, ShadowVisibility) { |
| XWindow xid = CreateSimpleWindow(); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| |
| // First, turn on the window's shadow before it's been mapped. Since we |
| // can't draw the window yet, we shouldn't draw its shadow either. |
| win.SetShadowType(Shadow::TYPE_RECTANGULAR); |
| win.ShowComposited(); |
| ASSERT_TRUE(win.shadow() != NULL); |
| EXPECT_FALSE(win.shadow()->is_shown()); |
| |
| // After the window gets mapped, we should show the shadow. |
| win.HandleMapNotify(); |
| EXPECT_TRUE(win.shadow()->is_shown()); |
| |
| // If we hide the window, the shadow should also be hidden. |
| win.HideComposited(); |
| EXPECT_FALSE(win.shadow()->is_shown()); |
| |
| // We should show the shadow again after the window is shown. |
| win.ShowComposited(); |
| EXPECT_TRUE(win.shadow()->is_shown()); |
| |
| // We should destroy the Shadow object when requested. |
| win.DisableShadow(); |
| EXPECT_TRUE(win.shadow() == NULL); |
| } |
| |
| // Check our implementation of the _NET_WM_SYNC_REQUEST protocol defined in |
| // EWMH, used for synchronizing redraws by the client when the window |
| // manager resizes a window. |
| TEST_F(WindowTest, SyncRequest) { |
| XWindow xid = CreateSimpleWindow(); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| xconn_->MapWindow(xid); |
| win.HandleMapRequested(); |
| win.HandleMapNotify(); |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&win); |
| |
| EXPECT_TRUE(win.client_has_redrawn_after_last_resize_); |
| EXPECT_EQ(geometry.bounds.size(), actor->GetBounds().size()); |
| |
| // If the client doesn't support the sync request protocol, we should |
| // just pretend like it's always redrawn the window immediately after a |
| // resize. |
| Size kFirstSize(500, 500); |
| win.Resize(kFirstSize, GRAVITY_NORTHWEST); |
| EXPECT_TRUE(win.client_has_redrawn_after_last_resize_); |
| win.HandleConfigureNotify(Rect(geometry.bounds.position(), kFirstSize), 0); |
| EXPECT_EQ(kFirstSize, actor->GetBounds().size()); |
| |
| // Add the hint saying that the window supports the sync request |
| // protocol, but don't actually set the property saying which counter |
| // it's using. The hint should be ignored. |
| ASSERT_TRUE( |
| xconn_->SetIntProperty( |
| xid, |
| xconn_->GetAtomOrDie("WM_PROTOCOLS"), // atom |
| xconn_->GetAtomOrDie("ATOM"), // type |
| xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST"))); |
| win.FetchAndApplyWmProtocols(); |
| EXPECT_EQ(0, win.wm_sync_request_alarm_); |
| |
| // Now set the property and check that an alarm gets created to watch it. |
| const XID counter_xid = 45; // arbitrary |
| ASSERT_TRUE( |
| xconn_->SetIntProperty( |
| xid, |
| xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST_COUNTER"), // atom |
| xconn_->GetAtomOrDie("CARDINAL"), // type |
| counter_xid)); |
| win.FetchAndApplyWmProtocols(); |
| EXPECT_NE(0, win.wm_sync_request_alarm_); |
| const MockXConnection::SyncCounterAlarmInfo* alarm_info = |
| xconn_->GetSyncCounterAlarmInfoOrDie(win.wm_sync_request_alarm_); |
| EXPECT_EQ(counter_xid, alarm_info->counter_id); |
| |
| // We should initialize the counter to a nonzero value and set the |
| // alarm's trigger at the next-greatest value. |
| int64_t initial_counter_value = xconn_->GetSyncCounterValueOrDie(counter_xid); |
| EXPECT_NE(static_cast<int64_t>(0), initial_counter_value); |
| int64_t next_counter_value = initial_counter_value + 1; |
| EXPECT_EQ(next_counter_value, alarm_info->initial_trigger_value); |
| |
| // When we resize the window, we should consider the window as needing to |
| // be redrawn. |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| info->client_messages.clear(); |
| Size kSecondSize(600, 600); |
| win.Resize(kSecondSize, GRAVITY_NORTHWEST); |
| EXPECT_FALSE(win.client_has_redrawn_after_last_resize_); |
| |
| // We should also abstain from getting a new pixmap in response to |
| // ConfigureNotify events... |
| win.HandleConfigureNotify(Rect(info->bounds.position(), kSecondSize), 0); |
| EXPECT_EQ(kFirstSize, actor->GetBounds().size()); |
| |
| // ... and we should send the client a message telling it to increment the |
| // counter when it's done redrawing. |
| ASSERT_EQ(1, info->client_messages.size()); |
| const XClientMessageEvent& msg = info->client_messages[0]; |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_PROTOCOLS"), msg.message_type); |
| EXPECT_EQ(XConnection::kLongFormat, msg.format); |
| EXPECT_EQ(xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST"), msg.data.l[0]); |
| // TODO: Check timestamp in l[1]? |
| EXPECT_EQ(static_cast<uint32_t>(next_counter_value & 0xffffffff), |
| msg.data.l[2]); |
| EXPECT_EQ(static_cast<uint32_t>((next_counter_value >> 32) & 0xffffffff), |
| msg.data.l[3]); |
| |
| // If we get notified that the counter is at the previous value, we |
| // should ignore it. |
| win.HandleSyncAlarmNotify(win.wm_sync_request_alarm_, initial_counter_value); |
| EXPECT_FALSE(win.client_has_redrawn_after_last_resize_); |
| |
| // Ditto if we get notified about some alarm that we don't know about |
| // (this shouldn't happen in practice). |
| win.HandleSyncAlarmNotify(0, next_counter_value); |
| EXPECT_FALSE(win.client_has_redrawn_after_last_resize_); |
| |
| // When we get notified that the counter has increased to the next value, |
| // we should consider the window to be redrawn and fetch an updated pixmap. |
| win.HandleSyncAlarmNotify(win.wm_sync_request_alarm_, next_counter_value); |
| EXPECT_TRUE(win.client_has_redrawn_after_last_resize_); |
| EXPECT_EQ(kSecondSize, actor->GetBounds().size()); |
| |
| // If we somehow get notified that the window has been redrawn before we |
| // get the ConfigureNotify, reset the pixmap immediately. |
| Size kThirdSize(700, 700); |
| win.Resize(kThirdSize, GRAVITY_NORTHWEST); |
| win.HandleSyncAlarmNotify(win.wm_sync_request_alarm_, |
| win.current_wm_sync_num_); |
| EXPECT_EQ(kThirdSize, actor->GetBounds().size()); |
| } |
| |
| // Test that we wait to fetch pixmaps for newly-created windows until the |
| // client tells us that they've been painted. |
| TEST_F(WindowTest, DeferFetchingPixmapUntilPainted) { |
| // Create a window and configure it to use _NET_WM_SYNC_REQUEST. |
| XWindow xid = CreateSimpleWindow(); |
| ConfigureWindowForSyncRequestProtocol(xid); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| xconn_->MapWindow(xid); |
| win.HandleMapRequested(); |
| |
| // Window::HandleMapRequested() should send a message to the client |
| // asking it to sync after painting the window, along with a synthetic |
| // ConfigureNotify event. |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| |
| ASSERT_EQ(1, info->client_messages.size()); |
| const XClientMessageEvent& msg = info->client_messages[0]; |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_PROTOCOLS"), msg.message_type); |
| EXPECT_EQ(XConnection::kLongFormat, msg.format); |
| EXPECT_EQ(xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST"), msg.data.l[0]); |
| |
| ASSERT_EQ(1, info->configure_notify_events.size()); |
| const XConfigureEvent& conf_notify = info->configure_notify_events[0]; |
| EXPECT_EQ(info->bounds.x, conf_notify.x); |
| EXPECT_EQ(info->bounds.y, conf_notify.y); |
| EXPECT_EQ(info->bounds.width, conf_notify.width); |
| EXPECT_EQ(info->bounds.height, conf_notify.height); |
| EXPECT_EQ(info->border_width, conf_notify.border_width); |
| // Don't bother checking the stacking here. We never registered this |
| // window with WindowManager (we don't want event consumers messing |
| // around with it), so the Window class won't be able to query the |
| // correct stacking position from WindowManager when it sends the |
| // synthetic event. |
| EXPECT_EQ(0, conf_notify.override_redirect); |
| |
| // We should hold off on fetching the pixmap in response to a MapNotify |
| // event if we haven't received notice that the window has been painted. |
| win.HandleMapNotify(); |
| EXPECT_EQ(0, win.pixmap_); |
| EXPECT_FALSE(win.has_initial_pixmap()); |
| |
| // After getting notice, we should fetch the pixmap. |
| win.HandleSyncAlarmNotify(win.wm_sync_request_alarm_, |
| win.current_wm_sync_num_); |
| EXPECT_NE(0, win.pixmap_); |
| EXPECT_TRUE(win.has_initial_pixmap()); |
| } |
| |
| // Test that we load the WM_CLIENT_MACHINE property, containing the |
| // hostname of the machine where the client is running. |
| TEST_F(WindowTest, ClientHostname) { |
| const XAtom client_machine_atom = xconn_->GetAtomOrDie("WM_CLIENT_MACHINE"); |
| |
| string hostname = "a.example.com"; |
| XWindow xid = CreateSimpleWindow(); |
| xconn_->SetStringProperty(xid, client_machine_atom, hostname); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_EQ(hostname, win.client_hostname()); |
| |
| hostname = "b.example.com"; |
| xconn_->SetStringProperty(xid, client_machine_atom, hostname); |
| win.FetchAndApplyWmClientMachine(); |
| EXPECT_EQ(hostname, win.client_hostname()); |
| |
| xconn_->DeletePropertyIfExists(xid, client_machine_atom); |
| win.FetchAndApplyWmClientMachine(); |
| EXPECT_EQ("", win.client_hostname()); |
| } |
| |
| // Test that we load the _NET_WM_PID property, containing the client's PID. |
| TEST_F(WindowTest, ClientPid) { |
| const XAtom pid_atom = xconn_->GetAtomOrDie("_NET_WM_PID"); |
| const XAtom cardinal_atom = xconn_->GetAtomOrDie("CARDINAL"); |
| |
| int pid = 123; |
| XWindow xid = CreateSimpleWindow(); |
| xconn_->SetIntProperty(xid, pid_atom, cardinal_atom, pid); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| EXPECT_EQ(pid, win.client_pid()); |
| |
| pid = 5436; |
| xconn_->SetIntProperty(xid, pid_atom, cardinal_atom, pid); |
| win.FetchAndApplyWmPid(); |
| EXPECT_EQ(pid, win.client_pid()); |
| |
| xconn_->DeletePropertyIfExists(xid, pid_atom); |
| win.FetchAndApplyWmPid(); |
| EXPECT_EQ(-1, win.client_pid()); |
| } |
| |
| // Test that we're able to send messages per the _NET_WM_PING protocol. |
| TEST_F(WindowTest, SendPingMessage) { |
| XWindow xid = CreateSimpleWindow(); |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| |
| // SendPing() should just fail without doing anything if the window |
| // hasn't told us that it supports the protocol. |
| XTime timestamp = 123; |
| info->client_messages.clear(); |
| EXPECT_FALSE(win.SendPing(timestamp)); |
| EXPECT_TRUE(info->client_messages.empty()); |
| |
| // Otherwise, we should send a client message as described in the spec. |
| AppendAtomToProperty(xid, |
| xconn_->GetAtomOrDie("WM_PROTOCOLS"), |
| xconn_->GetAtomOrDie("_NET_WM_PING")); |
| info->client_messages.clear(); |
| win.FetchAndApplyWmProtocols(); |
| EXPECT_TRUE(win.SendPing(timestamp)); |
| |
| ASSERT_EQ(1, info->client_messages.size()); |
| const XClientMessageEvent& msg = info->client_messages[0]; |
| EXPECT_EQ(xconn_->GetAtomOrDie("WM_PROTOCOLS"), msg.message_type); |
| EXPECT_EQ(XConnection::kLongFormat, msg.format); |
| EXPECT_EQ(xconn_->GetAtomOrDie("_NET_WM_PING"), msg.data.l[0]); |
| EXPECT_EQ(timestamp, msg.data.l[1]); |
| EXPECT_EQ(xid, msg.data.l[2]); |
| EXPECT_EQ(0, msg.data.l[3]); |
| EXPECT_EQ(0, msg.data.l[4]); |
| } |
| |
| // Check that we avoid a race that used to result in us displaying an |
| // incorrectly-sized shadow when an override-redirect window would be |
| // mapped and then immediately resized around the same time that we were |
| // enabling its shadow. See http://crosbug.com/7227. |
| TEST_F(WindowTest, ShadowSizeRace) { |
| // Create a 1x1 override-redirect window. |
| const Size kOrigSize(1, 1); |
| XWindow xid = xconn_->CreateWindow(xconn_->GetRootWindow(), |
| Rect(Point(0, 0), kOrigSize), |
| true, // override_redirect |
| false, // input_only |
| 0, // event_mask |
| 0); // visual |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, true, geometry); |
| |
| // Map the window and then resize it to 200x400. |
| xconn_->MapWindow(xid); |
| const Size kNewSize(200, 400); |
| xconn_->ResizeWindow(xid, kNewSize); |
| |
| // Let the Window object know about the MapNotify. Since the window has |
| // already been resized in the X server at this point, the actor should |
| // get the 200x400 pixmap. |
| win.HandleMapNotify(); |
| EXPECT_EQ(kNewSize, win.actor()->GetBounds().size()); |
| |
| // Turn on the shadow while we're in this brief state where we have a |
| // 200x400 actor but have only heard about the 1x1 size from the X |
| // server. The shadow should take the actor's size. |
| win.SetShadowType(Shadow::TYPE_RECTANGULAR); |
| EXPECT_EQ(kNewSize, win.shadow()->bounds().size()); |
| |
| // Now send the ConfigureNotify and check that nothing changes. |
| win.HandleConfigureNotify(Rect(Point(0, 0), kNewSize), 0); |
| EXPECT_EQ(kNewSize, win.actor()->GetBounds().size()); |
| } |
| |
| // Test that when we ask a window to simultaneously move and resize itself (that |
| // is, we request a resize with non-northwest gravity), the actor's position and |
| // size are updated atomically, rather than its position getting changed |
| // immediately and the resize only happening after we fetch the new pixmap. |
| TEST_F(WindowTest, SimultaneousMoveAndResize) { |
| // Create and map a window. |
| const Rect kOrigBounds(100, 150, 300, 250); |
| XWindow xid = xconn_->CreateWindow(xconn_->GetRootWindow(), |
| kOrigBounds, |
| false, // override_redirect |
| false, // input_only |
| 0, // event_mask |
| 0); // visual |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| ASSERT_TRUE(xconn_->MapWindow(xid)); |
| win.HandleMapNotify(); |
| win.ShowComposited(); |
| |
| // The client window and the actor should both have the requested bounds. |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| EXPECT_EQ(kOrigBounds, info->bounds); |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&win); |
| EXPECT_EQ(kOrigBounds, actor->GetBounds()); |
| |
| // Now make the window 50 pixels wider and taller with southeast gravity. |
| // In other words, its origin should also move 50 pixels up and to the left. |
| const Rect kNewBounds(50, 100, 350, 300); |
| win.Resize(kNewBounds.size(), GRAVITY_SOUTHEAST); |
| |
| // A request should've been sent to the X server asking for the new bounds, so |
| // the client window should be resized. The actor should still be at the old |
| // size (since we can't fetch its pixmap yet) and also at the old position (so |
| // we can make the move and resize happen atomically onscreen later). |
| EXPECT_EQ(kNewBounds, info->bounds); |
| EXPECT_EQ(kOrigBounds, actor->GetBounds()); |
| |
| // After we've received notification that the new pixmap is available, the |
| // actor should be both resized and moved to the requested position. |
| win.HandleConfigureNotify(kNewBounds, 0); |
| EXPECT_EQ(kNewBounds, actor->GetBounds()); |
| |
| // Move the actor to a completely different position. |
| const Point kCompositedPosition(500, 600); |
| win.MoveComposited(kCompositedPosition.x, kCompositedPosition.y, 0); |
| EXPECT_EQ(Rect(kCompositedPosition, kNewBounds.size()), actor->GetBounds()); |
| |
| // Now resize the window back to its old size, again with southeast gravity. |
| // The actor shouldn't move, but we should update the |composited_x| and |
| // |composited_y| fields. |
| win.Resize(kOrigBounds.size(), GRAVITY_SOUTHEAST); |
| EXPECT_EQ(kOrigBounds, info->bounds); |
| EXPECT_EQ(Rect(kCompositedPosition, kNewBounds.size()), actor->GetBounds()); |
| const Point kOffsetCompositedPosition( |
| kCompositedPosition.x + (kNewBounds.width - kOrigBounds.width), |
| kCompositedPosition.y + (kNewBounds.height - kOrigBounds.height)); |
| |
| // After getting notification about the pixmap, the actor should be resized |
| // and moved to the new position. |
| win.HandleConfigureNotify(kOrigBounds, 0); |
| EXPECT_EQ(Rect(kOffsetCompositedPosition, kOrigBounds.size()), |
| actor->GetBounds()); |
| |
| // Move the composited window back to the client window's position and scale |
| // it to 50% of its original size. |
| win.MoveComposited(kOrigBounds.x, kOrigBounds.y, 0); |
| const double kCompositedScale = 0.5; |
| win.ScaleComposited(kCompositedScale, kCompositedScale, 0); |
| |
| // Resize the client again. The amount that the composited window is moved |
| // should be scaled by its scaling factor. |
| win.Resize(kNewBounds.size(), GRAVITY_SOUTHEAST); |
| const Point kScaledCompositedPosition( |
| kOrigBounds.x + kCompositedScale * (kNewBounds.x - kOrigBounds.x), |
| kOrigBounds.y + kCompositedScale * (kNewBounds.y - kOrigBounds.y)); |
| |
| win.HandleConfigureNotify(kNewBounds, 0); |
| EXPECT_EQ(Rect(kScaledCompositedPosition, kNewBounds.size()), |
| actor->GetBounds()); |
| } |
| |
| // Exercises the new interface for managing both X and composited windows |
| // simultaneously (SetVisibility() and Move()). |
| TEST_F(WindowTest, SetVisibility) { |
| // Create and map a window. |
| const Rect kOrigBounds(100, 150, 300, 250); |
| XWindow xid = xconn_->CreateWindow(xconn_->GetRootWindow(), |
| kOrigBounds, |
| false, // override_redirect |
| false, // input_only |
| 0, // event_mask |
| 0); // visual |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| ASSERT_TRUE(xconn_->MapWindow(xid)); |
| win.HandleMapNotify(); |
| |
| // In the default state, we should leave the X window at its original |
| // position and hide the composited window. |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| EXPECT_EQ(kOrigBounds, info->bounds); |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&win); |
| EXPECT_EQ(kOrigBounds, actor->GetBounds()); |
| EXPECT_FALSE(actor->is_shown()); |
| |
| // With VISIBILITY_SHOWN, the X and composited windows should be in the same |
| // place and the composited window should be shown. |
| win.SetVisibility(Window::VISIBILITY_SHOWN); |
| EXPECT_EQ(kOrigBounds, info->bounds); |
| EXPECT_EQ(kOrigBounds, actor->GetBounds()); |
| EXPECT_TRUE(actor->is_shown()); |
| |
| // When we call Move(), both windows should be moved. |
| const Point kNewPosition(200, 300); |
| win.Move(kNewPosition, 0); |
| EXPECT_EQ(kNewPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| EXPECT_TRUE(actor->is_shown()); |
| |
| // With VISIBILITY_SHOWN_NO_INPUT, the X window should be moved offscreen. |
| const Point kOffscreenPosition(Window::kOffscreenX, Window::kOffscreenY); |
| win.SetVisibility(Window::VISIBILITY_SHOWN_NO_INPUT); |
| EXPECT_EQ(kOffscreenPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| EXPECT_TRUE(actor->is_shown()); |
| |
| // The X window should stay offscreen when we call Move(). |
| win.Move(kOrigBounds.position(), 0); |
| EXPECT_EQ(kOffscreenPosition, info->bounds.position()); |
| EXPECT_EQ(kOrigBounds.position(), actor->GetBounds().position()); |
| EXPECT_TRUE(actor->is_shown()); |
| |
| // With VISIBILITY_HIDDEN, the composited window should additionally be |
| // hidden. |
| win.SetVisibility(Window::VISIBILITY_HIDDEN); |
| EXPECT_EQ(kOffscreenPosition, info->bounds.position()); |
| EXPECT_EQ(kOrigBounds.position(), actor->GetBounds().position()); |
| EXPECT_FALSE(actor->is_shown()); |
| |
| // The composited window should get moved but stay hidden when we call Move(). |
| win.Move(kNewPosition, 0); |
| EXPECT_EQ(kOffscreenPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| EXPECT_FALSE(actor->is_shown()); |
| |
| // After setting the visibility to VISIBILITY_SHOWN, the X window should be |
| // moved back to the same position as the composited window. |
| win.SetVisibility(Window::VISIBILITY_SHOWN); |
| EXPECT_EQ(kNewPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| EXPECT_TRUE(actor->is_shown()); |
| |
| // Scaling the composited window should automatically move the X window |
| // offscreen, since mouse events wouldn't get transformed correctly if it |
| // stayed onscreen. |
| win.ScaleComposited(0.5, 1.0, 0); |
| EXPECT_EQ(kOffscreenPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| |
| // Check that the X window gets moved back when we restore the scale. |
| win.ScaleComposited(1.0, 1.0, 0); |
| EXPECT_EQ(kNewPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| |
| // Similarly, setting the opacity to 0 should move the X window offscreen. |
| win.SetCompositedOpacity(0.0, 0); |
| EXPECT_EQ(kOffscreenPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| |
| // The X window should get moved back when we make the window partially |
| // visible. |
| win.SetCompositedOpacity(0.5, 0); |
| EXPECT_EQ(kNewPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| } |
| |
| TEST_F(WindowTest, SetUpdateClientPositionForMoves) { |
| // Create and map a window. |
| const Rect kOrigBounds(100, 150, 300, 250); |
| XWindow xid = xconn_->CreateWindow(xconn_->GetRootWindow(), |
| kOrigBounds, |
| false, // override_redirect |
| false, // input_only |
| 0, // event_mask |
| 0); // visual |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| win.SetVisibility(Window::VISIBILITY_SHOWN); |
| ASSERT_TRUE(xconn_->MapWindow(xid)); |
| win.HandleMapNotify(); |
| |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| EXPECT_EQ(kOrigBounds, info->bounds); |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&win); |
| EXPECT_EQ(kOrigBounds, actor->GetBounds()); |
| |
| const Point kNewPosition(200, 300); |
| win.SetUpdateClientPositionForMoves(false); |
| win.Move(kNewPosition, 0); |
| EXPECT_EQ(kOrigBounds.position(), info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| |
| win.SetUpdateClientPositionForMoves(true); |
| EXPECT_EQ(kNewPosition, info->bounds.position()); |
| EXPECT_EQ(kNewPosition, actor->GetBounds().position()); |
| } |
| |
| TEST_F(WindowTest, FreezeUpdates) { |
| XWindow xid = CreateSimpleWindow(); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| |
| // Set the _CHROME_FREEZE_UPDATES property on the window before mapping it. |
| // We should avoid fetching its pixmap. |
| const XAtom kAtom = xconn_->GetAtomOrDie("_CHROME_FREEZE_UPDATES"); |
| ASSERT_TRUE(xconn_->SetIntProperty(xid, kAtom, kAtom, 1)); |
| win.HandleFreezeUpdatesPropertyChange(true); |
| win.HandleMapRequested(); |
| xconn_->MapWindow(xid); |
| win.HandleMapNotify(); |
| EXPECT_EQ(0, win.pixmap_); |
| EXPECT_FALSE(win.has_initial_pixmap()); |
| |
| // After the property is removed, we should fetch the pixmap. |
| ASSERT_TRUE(xconn_->DeletePropertyIfExists(xid, kAtom)); |
| win.HandleFreezeUpdatesPropertyChange(false); |
| EXPECT_NE(0, win.pixmap_); |
| EXPECT_TRUE(win.has_initial_pixmap()); |
| |
| // Create a second window. Configure it for _NET_WM_SYNC_REQUEST and set the |
| // _CHROME_FREEZE_UPDATES property before the Window class hears about it. |
| XWindow xid2 = CreateSimpleWindow(); |
| ConfigureWindowForSyncRequestProtocol(xid2); |
| ASSERT_TRUE(xconn_->SetIntProperty(xid2, kAtom, kAtom, 1)); |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid2, &geometry)); |
| Window win2(wm_.get(), xid2, false, geometry); |
| |
| // Map the window and check that we don't load its pixmap. |
| win2.HandleMapRequested(); |
| xconn_->MapWindow(xid2); |
| win2.HandleMapNotify(); |
| EXPECT_EQ(0, win2.pixmap_); |
| EXPECT_FALSE(win2.has_initial_pixmap()); |
| |
| // Update the sync counter. We should still avoid loading the pixmap, since |
| // the freeze-updates property is still set. |
| win2.HandleSyncAlarmNotify(win2.wm_sync_request_alarm_, |
| win2.current_wm_sync_num_); |
| EXPECT_EQ(0, win2.pixmap_); |
| EXPECT_FALSE(win2.has_initial_pixmap()); |
| |
| // After the property is removed, the pixmap should finally be loaded. |
| ASSERT_TRUE(xconn_->DeletePropertyIfExists(xid2, kAtom)); |
| win2.HandleFreezeUpdatesPropertyChange(false); |
| EXPECT_NE(0, win2.pixmap_); |
| EXPECT_TRUE(win2.has_initial_pixmap()); |
| } |
| |
| // Test that we avoid moving a window's actor when the actor's size (which is |
| // determined from the pixmap that it's currently displaying) differs from that |
| // of the underlying X window. |
| TEST_F(WindowTest, AvoidMovingActorDuringResize) { |
| const Rect kInitialBounds(20, 30, 40, 50); |
| XWindow xid = CreateBasicWindow(kInitialBounds); |
| ConfigureWindowForSyncRequestProtocol(xid); |
| XConnection::WindowGeometry geometry; |
| ASSERT_TRUE(xconn_->GetWindowGeometry(xid, &geometry)); |
| Window win(wm_.get(), xid, false, geometry); |
| |
| win.SetVisibility(Window::VISIBILITY_SHOWN); |
| win.HandleMapRequested(); |
| ASSERT_TRUE(xconn_->MapWindow(xid)); |
| win.HandleMapNotify(); |
| win.HandleSyncAlarmNotify(win.wm_sync_request_alarm_, |
| win.current_wm_sync_num_); |
| |
| MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid); |
| EXPECT_EQ(kInitialBounds, info->bounds); |
| MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(&win); |
| EXPECT_EQ(kInitialBounds, actor->GetBounds()); |
| |
| // Move the window without resizing it. Both the X window and actor should be |
| // moved immediately. |
| const Rect kBounds2(Point(60, 70), kInitialBounds.size()); |
| info->num_configures = 0; |
| win.SetBounds(kBounds2, 0); |
| EXPECT_EQ(kBounds2, info->bounds); |
| EXPECT_EQ(kBounds2, actor->GetBounds()); |
| EXPECT_EQ(1, info->num_configures); |
| |
| // Simultaneously move and resize the window. The X window should be updated |
| // (by a single ConfigureWindow request -- see http://crosbug.com/16716) but |
| // the actor should stay at the previous size and position. |
| const Rect kBounds3(80, 90, 100, 110); |
| info->num_configures = 0; |
| win.SetBounds(kBounds3, 0); |
| EXPECT_EQ(kBounds3, info->bounds); |
| EXPECT_EQ(kBounds2, actor->GetBounds()); |
| EXPECT_EQ(1, info->num_configures); |
| |
| // Now move the window again and resize it back to the previous size. We |
| // should move the actor now since it's at the (formerly stale, now correct) |
| // current size. |
| const Rect kBounds4(Point(150, 160), kBounds2.size()); |
| info->num_configures = 0; |
| win.SetBounds(kBounds4, 0); |
| EXPECT_EQ(kBounds4, info->bounds); |
| EXPECT_EQ(kBounds4, actor->GetBounds()); |
| EXPECT_EQ(1, info->num_configures); |
| |
| // Move and resize the window yet again. |
| const Rect kBounds5(180, 190, 200, 210); |
| info->num_configures = 0; |
| win.SetBounds(kBounds5, 0); |
| EXPECT_EQ(kBounds5, info->bounds); |
| EXPECT_EQ(kBounds4, actor->GetBounds()); |
| EXPECT_EQ(1, info->num_configures); |
| |
| // After we receive notice that the window has been painted, we should fetch a |
| // new pixmap (resulting in a resized actor) and move the actor. |
| win.HandleSyncAlarmNotify(win.wm_sync_request_alarm_, |
| win.current_wm_sync_num_); |
| EXPECT_EQ(kBounds5, actor->GetBounds()); |
| } |
| |
| } // namespace window_manager |
| |
| int main(int argc, char** argv) { |
| return window_manager::InitAndRunTests(&argc, argv, &FLAGS_logtostderr); |
| } |