| // Copyright (c) 2011 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 "window_manager/layout2/layout_manager2.h" |
| |
| #include <gflags/gflags.h> |
| extern "C" { |
| #include <X11/cursorfont.h> |
| } |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstdio> |
| #include <string> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/string_util.h" |
| #include "window_manager/atom_cache.h" |
| #include "window_manager/callback.h" |
| #include "window_manager/event_consumer_registrar.h" |
| #include "window_manager/focus_manager.h" |
| #include "window_manager/geometry.h" |
| #include "window_manager/key_bindings.h" |
| #include "window_manager/layout2/browser_window.h" |
| #include "window_manager/resize_box.h" |
| #include "window_manager/stacking_manager.h" |
| #include "window_manager/util.h" |
| #include "window_manager/window.h" |
| #include "window_manager/window_manager.h" |
| #include "window_manager/x11/x_connection.h" |
| |
| DEFINE_string(initial_chrome_window_mapped_file, |
| "", "When we first see a toplevel Chrome window get mapped, " |
| "we write its ID as an ASCII decimal number to this file. " |
| "Tests can watch for the file to know when the user is fully " |
| "logged in. Leave empty to disable."); |
| |
| DEFINE_bool(overlapping_windows, true, |
| "Permit displaying multiple, overlapping browser windows onscreen"); |
| |
| DEFINE_bool(overlap_windows_by_default, true, |
| "Automatically switch to the overlapping layout mode when a " |
| "second browser window is opened"); |
| |
| DEFINE_string(xterm_command, "xterm", "Command to launch a terminal"); |
| |
| using base::TimeTicks; |
| using chromeos::WM_IPC_LAYOUT_MAXIMIZED; |
| using chromeos::WM_IPC_LAYOUT_OVERLAPPING; |
| using chromeos::WmIpcLayoutMode; |
| using std::make_pair; |
| using std::map; |
| using std::max; |
| using std::min; |
| using std::set; |
| using std::string; |
| using std::tr1::shared_ptr; |
| using std::vector; |
| using window_manager::util::GetMonotonicTime; |
| using window_manager::util::RunCommandInBackgroundCallback; |
| using window_manager::util::ToggleBool; |
| using window_manager::util::XidStr; |
| |
| namespace window_manager { |
| |
| namespace { |
| |
| // Duration of browser window movement animations. |
| const int kWindowAnimMs = 150; |
| |
| // Maximum amount of time we'll take to cycle through windows. |
| const int kMaxWindowCycleAnimMs = 400; |
| |
| // Number of steps between the minimum and maximum browser window size for the |
| // incremental resize keyboard shortcuts. |
| const int kNumIncrementalResizeSteps = 2; |
| |
| // We'll try to change the window's width by at least this fraction of the |
| // incremental resize amount. |
| const double kIncrementalResizeMinChangePercent = 0.25; |
| |
| // How many pixels should we reserve for the status area? This affects the |
| // maximum size that a browser window can be in its non-maximized state. |
| const int kStatusWidthPixels = 180; |
| |
| // Size of the invisible resize handle that can be dragged to resize the active |
| // browser window in non-maximized mode. |
| const int kResizeHandleWidthPixels = 12; |
| |
| // Size of the box that we use to sorta-extend the window's shadow when the |
| // pointer is in the resize handle. |
| const int kResizeHandleShadowExtendPixels = 8; |
| |
| // Opacity of the box that we display when the pointer is in the resize handle. |
| const double kResizeHandleShadowOpacity = 0.125; |
| |
| // Duration for resize handle animations, in milliseconds. |
| const int kResizeHandleShadowAnimMs = 100; |
| |
| // Opacity of the box that we display while the user is resizing a window. |
| const double kResizeBoxOpacity = 0.4; |
| |
| // When a browser window is being interactively resized, how much wider than the |
| // maximum width for an unmaximized window does it need to get before we snap it |
| // to cover the whole screen and enter maximized mode? |
| const int kResizeSnapThresholdPixels = 50; |
| |
| // Duration of the snapping-to-cover-the-whole-screen animation, in |
| // milliseconds. |
| const int kResizeBoxSnapAnimMs = 100; |
| |
| // Duration over which we dim the secondary browser window, in milliseconds. |
| const double kSecondaryBrowserWindowDimAnimMs = 10000; |
| |
| // Key binding action names. |
| const char kActivateBrowserByIndexActionFormat[] = "activate-browser-%d"; |
| const char kActivateLastBrowserAction[] = "activate-last-browser"; |
| const char kCloseFocusedNonChromeWindowAction[] = "close-non-chrome-window"; |
| const char kCycleBackwardAction[] = "cycle-browser-backward"; |
| const char kCycleForwardAction[] = "cycle-browser-forward"; |
| const char kLaunchTerminalAction[] = "launch-terminal"; |
| const char kResizeLeftAction[] = "resize-browser-left"; |
| const char kResizeRightAction[] = "resize-browser-right"; |
| const char kToggleMaximizedAction[] = "toggle-browser-maximized"; |
| |
| } // anonymous namespace |
| |
| // Static members (non-anonymous so they can be used by tests). |
| const int LayoutManager2::kWindowInitialWidthPixels = 1024; |
| const int LayoutManager2::kEdgeGapPixels = 32; |
| const double LayoutManager2::kEdgeBrowserWindowBrightness = 0.6; |
| const double LayoutManager2::kSecondaryBrowserWindowBrightness = 0.9; |
| |
| LayoutManager2::LayoutManager2(WindowManager* wm) |
| : wm_(wm), |
| panel_area_notifier_(NULL), |
| workarea_(wm_->root_bounds()), |
| event_consumer_registrar_(new EventConsumerRegistrar(wm, this)), |
| key_bindings_actions_(new KeyBindingsActionRegistrar(wm->key_bindings())), |
| key_bindings_group_(new KeyBindingsGroup(wm->key_bindings())), |
| non_chrome_key_bindings_group_(new KeyBindingsGroup(wm->key_bindings())), |
| active_browser_index_(-1), |
| // Initialized to unwanted value to force SetLayoutMode() update later. |
| layout_mode_(WM_IPC_LAYOUT_OVERLAPPING), |
| fullscreen_browser_(NULL), |
| anim_ms_for_pending_arrange_(0), |
| resize_handle_xid_( |
| wm->CreateInputWindow(Rect(-1, -1, 1, 1), |
| EnterWindowMask | LeaveWindowMask)), |
| resize_handle_actor_( |
| wm_->compositor()->CreateColoredBox(1, 1, Compositor::Color("#000"))), |
| resize_handle_actor_is_visible_(false), |
| left_cursor_(wm_->xconn()->CreateShapedCursor(XC_left_side)), |
| right_cursor_(wm_->xconn()->CreateShapedCursor(XC_right_side)), |
| left_edge_input_xid_( |
| wm_->CreateInputWindow(Rect(-1, -1, 1, 1), ButtonPressMask)), |
| right_edge_input_xid_( |
| wm_->CreateInputWindow(Rect(-1, -1, 1, 1), ButtonPressMask)), |
| in_resize_drag_(false), |
| resize_width_at_start_(0), |
| resize_box_is_snapped_(false), |
| first_browser_window_mapped_(false) { |
| event_consumer_registrar_->RegisterForChromeMessages( |
| chromeos::WM_IPC_MESSAGE_WM_CYCLE_WINDOWS); |
| event_consumer_registrar_->RegisterForChromeMessages( |
| chromeos::WM_IPC_MESSAGE_WM_SET_LAYOUT_MODE); |
| wm_->focus_manager()->RegisterFocusChangeListener(this); |
| wm_->modality_handler()->RegisterModalityChangeListener(this); |
| |
| InitKeyBindings(); |
| SetLayoutMode(WM_IPC_LAYOUT_MAXIMIZED, false); |
| |
| wm_->stacking_manager()->StackXidAtTopOfLayer( |
| resize_handle_xid_, |
| StackingManager::LAYER_ACTIVE_BROWSER_WINDOW); |
| event_consumer_registrar_->RegisterForWindowEvents(resize_handle_xid_); |
| int event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask; |
| wm_->xconn()->AddButtonGrabOnWindow(resize_handle_xid_, 1, event_mask, false); |
| wm_->SetNamePropertiesForXid(resize_handle_xid_, |
| "input window for browser resize"); |
| |
| resize_handle_actor_->SetName("browser resize handle"); |
| resize_handle_actor_->Move(-1, -1, 0); |
| resize_handle_actor_->SetOpacity(0, 0); |
| wm_->stage()->AddActor(resize_handle_actor_.get()); |
| wm_->stacking_manager()->StackActorAtTopOfLayer( |
| resize_handle_actor_.get(), StackingManager::LAYER_ACTIVE_BROWSER_WINDOW); |
| |
| wm_->stacking_manager()->StackXidAtTopOfLayer( |
| left_edge_input_xid_, |
| StackingManager::LAYER_ACTIVE_BROWSER_WINDOW); |
| event_consumer_registrar_->RegisterForWindowEvents(left_edge_input_xid_); |
| wm->SetNamePropertiesForXid(left_edge_input_xid_, "left edge input window"); |
| |
| wm_->stacking_manager()->StackXidAtTopOfLayer( |
| right_edge_input_xid_, |
| StackingManager::LAYER_ACTIVE_BROWSER_WINDOW); |
| event_consumer_registrar_->RegisterForWindowEvents(right_edge_input_xid_); |
| wm->SetNamePropertiesForXid(right_edge_input_xid_, "right edge input window"); |
| } |
| |
| LayoutManager2::~LayoutManager2() { |
| browser_windows_.clear(); |
| wm_->focus_manager()->UnregisterFocusChangeListener(this); |
| wm_->modality_handler()->UnregisterModalityChangeListener(this); |
| SetPanelAreaNotifier(NULL); |
| wm_->xconn()->DestroyWindow(resize_handle_xid_); |
| wm_->xconn()->FreeCursor(left_cursor_); |
| wm_->xconn()->FreeCursor(right_cursor_); |
| wm_->xconn()->DestroyWindow(left_edge_input_xid_); |
| wm_->xconn()->DestroyWindow(right_edge_input_xid_); |
| } |
| |
| void LayoutManager2::HandleScreenResize() { |
| MoveAndResizeForAvailableArea(); |
| } |
| |
| bool LayoutManager2::HandleWindowMapRequest(Window* win) { |
| if (win->transient_for_xid()) { |
| BrowserWindow* owning_browser = |
| FindBrowserWindowByXid(win->transient_for_xid()); |
| if (!owning_browser) { |
| // Handle windows that are transient for other transient windows -- |
| // see http://crosbug.com/3316. |
| Window* owning_win = wm_->GetWindow(win->transient_for_xid()); |
| if (owning_win) |
| owning_browser = FindBrowserWindowOwningTransientWindow(*owning_win); |
| } |
| return (owning_browser != NULL); |
| } |
| |
| if (BrowserWindow::ShouldHandleWindow(*win)) { |
| win->SetVisibility(Window::VISIBILITY_HIDDEN); |
| win->Move(Point(workarea_.right(), workarea_.top()), 0); |
| |
| const bool switch_to_overlapping = |
| FLAGS_overlap_windows_by_default && num_browser_windows() == 1; |
| const bool maximize = |
| !FLAGS_overlapping_windows || (maximized() && !switch_to_overlapping); |
| Size initial_size = workarea_.size(); |
| if (win->wm_state_fullscreen()) |
| initial_size = wm_->root_bounds().size(); |
| else if (!maximize) |
| initial_size.width = GetInitialUnmaximizedWidth(); |
| win->Resize(initial_size, GRAVITY_NORTHWEST); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void LayoutManager2::HandleWindowMap(Window* win) { |
| if (win->override_redirect()) |
| return; |
| |
| if (win->transient_for_xid()) { |
| BrowserWindow* owning_browser = |
| FindBrowserWindowByXid(win->transient_for_xid()); |
| if (!owning_browser) { |
| Window* owning_win = wm_->GetWindow(win->transient_for_xid()); |
| if (owning_win) |
| owning_browser = FindBrowserWindowOwningTransientWindow(*owning_win); |
| } |
| if (owning_browser) |
| HandleTransientWindowMap(win, owning_browser); |
| } else if (BrowserWindow::ShouldHandleWindow(*win)) { |
| HandleBrowserWindowMap(win); |
| } |
| } |
| |
| void LayoutManager2::HandleWindowUnmap(Window* win) { |
| if (win->override_redirect()) |
| return; |
| |
| BrowserWindow* owning_browser = FindBrowserWindowOwningTransientWindow(*win); |
| if (owning_browser) { |
| const bool transient_had_focus = win->IsFocused(); |
| owning_browser->HandleTransientWindowUnmap(win); |
| transient_xids_to_browsers_.erase(win->xid()); |
| if (transient_had_focus) |
| owning_browser->TakeFocus(GetCurrentXTime()); |
| return; |
| } |
| |
| int browser_index = FindBrowserWindowIndexByWindow(*win); |
| if (browser_index < 0) |
| return; |
| |
| BrowserWindow* browser = browser_windows_[browser_index].get(); |
| |
| const bool had_focus = (win == wm_->focus_manager()->focused_win()); |
| HandleWindowNoLongerBlockingArrange(win); |
| if (fullscreen_browser_ && fullscreen_browser_->win() == win) |
| SetFullscreenBrowserWindow(NULL); |
| |
| // Delete any dangling references in the transient-to-browser map caused by |
| // the browser getting unmapped before its transients. |
| vector<XWindow> orphaned_transient_xids; |
| for (map<XWindow, BrowserWindow*>::const_iterator it = |
| transient_xids_to_browsers_.begin(); |
| it != transient_xids_to_browsers_.end(); ++it) { |
| if (it->second == browser) |
| orphaned_transient_xids.push_back(it->first); |
| } |
| for (vector<XWindow>::const_iterator it = orphaned_transient_xids.begin(); |
| it != orphaned_transient_xids.end(); ++it) { |
| CHECK(transient_xids_to_browsers_.erase(*it) == 1); |
| } |
| |
| browser_windows_.erase(browser_windows_.begin() + browser_index); |
| browser = NULL; |
| |
| if (num_browser_windows() <= 1) |
| SetLayoutMode(WM_IPC_LAYOUT_MAXIMIZED, false); |
| |
| if (browser_windows_.empty()) { |
| active_browser_index_ = -1; |
| return; |
| } |
| |
| int new_index = active_browser_index_; |
| if (new_index >= num_browser_windows()) |
| new_index = num_browser_windows() - 1; |
| else if (browser_index <= new_index && new_index > 0) |
| new_index--; |
| |
| SetActiveBrowserWindowIndex(new_index); |
| ArrangeBrowserWindows( |
| ARRANGE_ALL_BROWSERS, |
| had_focus ? ASSIGN_FOCUS_DURING_ARRANGE : PRESERVE_FOCUS_DURING_ARRANGE, |
| had_focus ? GetCurrentXTime() : 0, |
| kWindowAnimMs); |
| } |
| |
| void LayoutManager2::HandleWindowPixmapFetch(Window* win) { |
| HandleWindowNoLongerBlockingArrange(win); |
| } |
| |
| void LayoutManager2::HandleWindowConfigureRequest( |
| Window* win, const Rect& requested_bounds) { |
| // Ignore requests to resize browser windows, but send them fake |
| // ConfigureNotify events to let them know that we saw the requests. |
| BrowserWindow* browser = FindBrowserWindowByXid(win->xid()); |
| if (browser) { |
| win->SendSyntheticConfigureNotify(); |
| return; |
| } |
| |
| BrowserWindow* owning_browser = FindBrowserWindowOwningTransientWindow(*win); |
| if (owning_browser) { |
| owning_browser->HandleTransientWindowConfigureRequest( |
| win, requested_bounds); |
| return; |
| } |
| } |
| |
| void LayoutManager2::HandleButtonPress(XWindow xid, |
| const Point& relative_pos, |
| const Point& absolute_pos, |
| int button, |
| XTime timestamp) { |
| // Bail out early on scrollwheel events, which can come in quickly. |
| if (button > 3) |
| return; |
| |
| if (xid == resize_handle_xid_ && button == 1) { |
| HandleResizeDragStart(absolute_pos); |
| return; |
| } |
| |
| if (xid == left_edge_input_xid_ || xid == right_edge_input_xid_) { |
| vector<Rect> bounds; |
| int left_offscreen_index = -1, right_offscreen_index = -1; |
| ComputeBrowserWindowBounds( |
| &bounds, &left_offscreen_index, &right_offscreen_index); |
| if (xid == left_edge_input_xid_ && left_offscreen_index != -1) |
| SetActiveBrowserWindowIndex(left_offscreen_index); |
| else if (xid == right_edge_input_xid_ && right_offscreen_index != -1) |
| SetActiveBrowserWindowIndex(right_offscreen_index); |
| else |
| LOG(WARNING) << "Unhandled button press in input window " << xid; |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| timestamp, kWindowAnimMs); |
| return; |
| } |
| |
| Window* win = wm_->GetWindow(xid); |
| if (!win) |
| return; |
| |
| int browser_index = -1; |
| if (win->transient_for_xid()) { |
| // If we got a click in a transient window, then give it the focus. |
| BrowserWindow* owning_browser = |
| FindBrowserWindowOwningTransientWindow(*win); |
| if (!owning_browser) |
| return; |
| owning_browser->SetPreferredTransientWindowToFocus(win); |
| browser_index = GetBrowserWindowIndex(*owning_browser); |
| } else { |
| // Otherwise, tell the browser window to steal the focus from its transient |
| // window. |
| browser_index = FindBrowserWindowIndexByWindow(*win); |
| if (browser_index == -1) |
| return; |
| BrowserWindow* browser = browser_windows_[browser_index].get(); |
| browser->SetPreferredTransientWindowToFocus(NULL); |
| } |
| |
| // Avoid focusing a browser in response to clicks within its status area. |
| if (!win->status_bounds().contains_point(relative_pos)) { |
| DCHECK_NE(browser_index, -1); |
| if (browser_index == active_browser_index_) { |
| // If the click happened in the active browser, then we're probably |
| // stealing the focus from a panel or something. |
| TakeFocus(timestamp); |
| } else { |
| SetActiveBrowserWindowIndex(browser_index); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| timestamp, kWindowAnimMs); |
| } |
| } |
| } |
| |
| void LayoutManager2::HandleButtonRelease(XWindow xid, |
| const Point& relative_pos, |
| const Point& absolute_pos, |
| int button, |
| XTime timestamp) { |
| if (xid == resize_handle_xid_) { |
| if (button == 1) |
| HandleResizeDragEnd(absolute_pos); |
| return; |
| } |
| } |
| |
| void LayoutManager2::HandlePointerEnter(XWindow xid, |
| const Point& relative_pos, |
| const Point& absolute_pos, |
| XTime timestamp) { |
| DCHECK_EQ(xid, resize_handle_xid_); |
| |
| BrowserWindow* browser = GetActiveBrowserWindow(); |
| int x = browser->win()->client_x() + |
| (browser->anchored_to_left() ? browser->unmaximized_width() : 0); |
| resize_handle_actor_->Scale(0.0, 1.0, 0); |
| resize_handle_actor_->Move(x, workarea_.y, 0); |
| resize_handle_actor_->SetSize(kResizeHandleShadowExtendPixels, |
| workarea_.height); |
| |
| resize_handle_actor_->Scale(1.0, 1.0, kResizeHandleShadowAnimMs); |
| resize_handle_actor_->SetOpacity(kResizeHandleShadowOpacity, |
| kResizeHandleShadowAnimMs); |
| if (!browser->anchored_to_left()) { |
| resize_handle_actor_->MoveX( |
| x - kResizeHandleShadowExtendPixels, kResizeHandleShadowAnimMs); |
| } |
| |
| resize_handle_actor_is_visible_ = true; |
| } |
| |
| void LayoutManager2::HandlePointerLeave(XWindow xid, |
| const Point& relative_pos, |
| const Point& absolute_pos, |
| XTime timestamp) { |
| DCHECK_EQ(xid, resize_handle_xid_); |
| resize_handle_actor_->SetOpacity(0.0, kResizeHandleShadowAnimMs); |
| resize_handle_actor_is_visible_ = false; |
| } |
| |
| void LayoutManager2::HandlePointerMotion(XWindow xid, |
| const Point& relative_pos, |
| const Point& absolute_pos, |
| XTime timestamp) { |
| if (xid == resize_handle_xid_) { |
| HandleResizeDragMotion(absolute_pos); |
| return; |
| } |
| } |
| |
| void LayoutManager2::HandleChromeMessage(const WmIpc::Message& msg) { |
| switch (msg.type()) { |
| case chromeos::WM_IPC_MESSAGE_WM_CYCLE_WINDOWS: |
| CycleActiveBrowserWindow(msg.param(0) != 0); |
| break; |
| case chromeos::WM_IPC_MESSAGE_WM_SET_LAYOUT_MODE: |
| SetLayoutMode(static_cast<WmIpcLayoutMode>(msg.param(0)), true); |
| break; |
| default: |
| NOTREACHED() << "Unexpected Chrome message of type " << msg.type(); |
| } |
| } |
| |
| void LayoutManager2::HandleClientMessage(XWindow xid, |
| XAtom message_type, |
| const long data[5]) { |
| Window* win = wm_->GetWindow(xid); |
| if (!win) |
| return; |
| |
| if (message_type == wm_->GetXAtom(ATOM_NET_WM_STATE)) { |
| map<XAtom, bool> states; |
| win->ParseWmStateMessage(data, &states); |
| |
| map<XAtom, bool>::const_iterator it = |
| states.find(wm_->GetXAtom(ATOM_NET_WM_STATE_FULLSCREEN)); |
| if (it != states.end()) |
| HandleFullscreenRequest(win, it->second); |
| |
| it = states.find(wm_->GetXAtom(ATOM_NET_WM_STATE_MODAL)); |
| if (it != states.end()) |
| HandleModalityRequest(win, it->second); |
| |
| } else if (message_type == wm_->GetXAtom(ATOM_NET_ACTIVE_WINDOW)) { |
| HandleActiveWindowRequest(win, data[1]); |
| } |
| } |
| |
| void LayoutManager2::HandleFocusChange() { |
| if (fullscreen_browser_ && |
| !fullscreen_browser_->IsWindowOrTransientFocused()) { |
| SetFullscreenBrowserWindow(NULL); |
| ArrangeBrowserWindows( |
| ARRANGE_ALL_BROWSERS, PRESERVE_FOCUS_DURING_ARRANGE, 0, 0); |
| } |
| } |
| |
| void LayoutManager2::HandleModalityChange() { |
| key_bindings_group_->SetEnabled(!wm_->IsModalWindowFocused()); |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| non_chrome_key_bindings_group_->SetEnabled( |
| !wm_->IsModalWindowFocused() && |
| active_browser && |
| active_browser->win()->type() == chromeos::WM_IPC_WINDOW_UNKNOWN); |
| } |
| |
| void LayoutManager2::HandlePanelManagerAreaChange() { |
| MoveAndResizeForAvailableArea(); |
| } |
| |
| bool LayoutManager2::TakeFocus(XTime timestamp) { |
| if (browser_windows_.empty()) |
| return false; |
| |
| if (fullscreen_browser_) |
| fullscreen_browser_->TakeFocus(timestamp); |
| else |
| GetActiveBrowserWindow()->TakeFocus(timestamp); |
| return true; |
| } |
| |
| void LayoutManager2::SetPanelAreaNotifier( |
| PanelManagerAreaChangeNotifier* notifier) { |
| if (panel_area_notifier_) |
| panel_area_notifier_->UnregisterAreaChangeListener(this); |
| |
| panel_area_notifier_ = notifier; |
| if (panel_area_notifier_) |
| panel_area_notifier_->RegisterAreaChangeListener(this); |
| MoveAndResizeForAvailableArea(); |
| } |
| |
| BrowserWindow* LayoutManager2::GetActiveBrowserWindow() const { |
| if (browser_windows_.empty()) |
| return NULL; |
| |
| DCHECK_GE(active_browser_index_, 0); |
| DCHECK_LT(active_browser_index_, num_browser_windows()); |
| return browser_windows_[active_browser_index_].get(); |
| } |
| |
| int LayoutManager2::FindBrowserWindowIndexByWindow(const Window& win) const { |
| for (int i = 0; i < num_browser_windows(); ++i) { |
| if (browser_windows_[i]->win() == &win) |
| return i; |
| } |
| return -1; |
| } |
| |
| int LayoutManager2::FindBrowserWindowIndexByXid(XWindow xid) const { |
| const Window* win = wm_->GetWindow(xid); |
| return win ? FindBrowserWindowIndexByWindow(*win) : -1; |
| } |
| |
| BrowserWindow* LayoutManager2::FindBrowserWindowByXid(XWindow xid) const { |
| const int index = FindBrowserWindowIndexByXid(xid); |
| return index >= 0 ? browser_windows_[index].get() : NULL; |
| } |
| |
| BrowserWindow* LayoutManager2::FindBrowserWindowOwningTransientWindow( |
| const Window& win) const { |
| map<XWindow, BrowserWindow*>::const_iterator it = |
| transient_xids_to_browsers_.find(win.xid()); |
| return (it != transient_xids_to_browsers_.end()) ? it->second : NULL; |
| } |
| |
| int LayoutManager2::GetBrowserWindowIndex(const BrowserWindow& browser) const { |
| return FindBrowserWindowIndexByWindow(*(browser.win())); |
| } |
| |
| int LayoutManager2::GetInitialUnmaximizedWidth() const { |
| return max(min(kWindowInitialWidthPixels, GetMaxUnmaximizedWidth()), |
| GetMinUnmaximizedWidth()); |
| } |
| |
| int LayoutManager2::GetMinUnmaximizedWidth() const { |
| // TODO(derat): Change this based on whether there's a window to the edge or |
| // not? |
| return 0.5 * (workarea_.width + 1); |
| } |
| |
| int LayoutManager2::GetMaxUnmaximizedWidth() const { |
| return workarea_.width - kStatusWidthPixels - 2 * kEdgeGapPixels; |
| } |
| |
| int LayoutManager2::ConstrainUnmaximizedWidth(int requested_width) { |
| return max(min(requested_width, GetMaxUnmaximizedWidth()), |
| GetMinUnmaximizedWidth()); |
| } |
| |
| int LayoutManager2::GetWindowAnimationMs(int new_index) const { |
| const int num_windows_to_traverse = |
| max(new_index, active_browser_index_) - |
| min(new_index, active_browser_index_); |
| return maximized() ? |
| min(num_windows_to_traverse * kWindowAnimMs, kMaxWindowCycleAnimMs) : |
| kWindowAnimMs; |
| } |
| |
| XTime LayoutManager2::GetCurrentXTime() { |
| XTime current_xtime = wm_->key_bindings()->current_event_time(); |
| if (!current_xtime) |
| current_xtime = wm_->GetCurrentTimeFromServer(); |
| return current_xtime; |
| } |
| |
| void LayoutManager2::MoveAndResizeForAvailableArea() { |
| const Size old_size = workarea_.size(); |
| |
| int panel_manager_left_width = 0, panel_manager_right_width = 0; |
| if (panel_area_notifier_) |
| panel_area_notifier_->GetArea(&panel_manager_left_width, |
| &panel_manager_right_width); |
| |
| workarea_ = wm_->root_bounds(); |
| workarea_.x += panel_manager_left_width; |
| workarea_.width -= (panel_manager_left_width + panel_manager_right_width); |
| |
| // Try to preserve windows' old fractions of the screen's width while ensuring |
| // that they stay within the acceptable limits. |
| for (int i = 0; i < num_browser_windows(); ++i) { |
| BrowserWindow* browser = browser_windows_[i].get(); |
| const double ratio = |
| static_cast<double>(browser->unmaximized_width()) / old_size.width; |
| browser->set_unmaximized_width( |
| ConstrainUnmaximizedWidth(round(ratio * workarea_.width))); |
| } |
| // TODO(derat): Recenter transient windows? |
| ArrangeBrowserWindows( |
| ARRANGE_ALL_BROWSERS, PRESERVE_FOCUS_DURING_ARRANGE, 0, 0); |
| } |
| |
| void LayoutManager2::InitKeyBindings() { |
| // Disable the key bindings until we see the first browser window. |
| key_bindings_group_->SetEnabled(false); |
| non_chrome_key_bindings_group_->SetEnabled(false); |
| |
| key_bindings_actions_->AddAction( |
| kCycleForwardAction, |
| NewPermanentCallback( |
| this, &LayoutManager2::CycleActiveBrowserWindow, true), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_Tab, KeyBindings::kAltMask), |
| kCycleForwardAction); |
| if (!FLAGS_overlapping_windows) { |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_F5, 0), |
| kCycleForwardAction); |
| } |
| |
| key_bindings_actions_->AddAction( |
| kCycleBackwardAction, |
| NewPermanentCallback( |
| this, &LayoutManager2::CycleActiveBrowserWindow, false), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo( |
| XK_Tab, KeyBindings::kAltMask | KeyBindings::kShiftMask), |
| kCycleBackwardAction); |
| if (!FLAGS_overlapping_windows) { |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_F5, KeyBindings::kShiftMask), |
| kCycleBackwardAction); |
| } |
| |
| for (int i = 0; i < 8; ++i) { |
| const string action_name = |
| StringPrintf(kActivateBrowserByIndexActionFormat, i); |
| key_bindings_actions_->AddAction( |
| action_name, |
| NewPermanentCallback( |
| this, &LayoutManager2::ActivateBrowserWindowByIndex, i), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_1 + i, KeyBindings::kAltMask), |
| action_name); |
| } |
| |
| key_bindings_actions_->AddAction( |
| kActivateLastBrowserAction, |
| NewPermanentCallback( |
| this, &LayoutManager2::ActivateBrowserWindowByIndex, -1), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_9, KeyBindings::kAltMask), |
| kActivateLastBrowserAction); |
| |
| if (FLAGS_overlapping_windows) { |
| key_bindings_actions_->AddAction( |
| kToggleMaximizedAction, |
| NewPermanentCallback(this, &LayoutManager2::ToggleMaximized), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_F5, 0), |
| kToggleMaximizedAction); |
| |
| key_bindings_actions_->AddAction( |
| kResizeLeftAction, |
| NewPermanentCallback( |
| this, |
| &LayoutManager2::ResizeActiveBrowserWindowIncrementally, false), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_comma, KeyBindings::kAltMask), |
| kResizeLeftAction); |
| |
| key_bindings_actions_->AddAction( |
| kResizeRightAction, |
| NewPermanentCallback( |
| this, |
| &LayoutManager2::ResizeActiveBrowserWindowIncrementally, true), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo(XK_period, KeyBindings::kAltMask), |
| kResizeRightAction); |
| } |
| |
| vector<string> xterm_argv; |
| xterm_argv.push_back(FLAGS_xterm_command); |
| key_bindings_actions_->AddAction( |
| kLaunchTerminalAction, |
| NewPermanentCallback(&RunCommandInBackgroundCallback, xterm_argv), |
| NULL, NULL); |
| key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo( |
| XK_t, KeyBindings::kControlMask | KeyBindings::kAltMask), |
| kLaunchTerminalAction); |
| |
| key_bindings_actions_->AddAction( |
| kCloseFocusedNonChromeWindowAction, |
| NewPermanentCallback( |
| this, &LayoutManager2::CloseFocusedNonChromeWindow), |
| NULL, NULL); |
| non_chrome_key_bindings_group_->AddBinding( |
| KeyBindings::KeyCombo( |
| XK_w, KeyBindings::kControlMask | KeyBindings::kShiftMask), |
| kCloseFocusedNonChromeWindowAction); |
| } |
| |
| void LayoutManager2::HandleBrowserWindowMap(Window* win) { |
| DCHECK(FindBrowserWindowIndexByWindow(*win) < 0) |
| << "Window " << win->xid_str() << " is already being tracked"; |
| |
| shared_ptr<BrowserWindow> browser( |
| new BrowserWindow(win, this, GetInitialUnmaximizedWidth())); |
| |
| int index = 0; |
| if (num_browser_windows()) { |
| index = active_browser_index_ + 1; |
| browser_windows_.insert(browser_windows_.begin() + index, browser); |
| } else { |
| browser_windows_.push_back(browser); |
| } |
| |
| if (FLAGS_overlapping_windows && FLAGS_overlap_windows_by_default && |
| maximized() && num_browser_windows() == 2) |
| SetLayoutMode(WM_IPC_LAYOUT_OVERLAPPING, false); |
| |
| if (win->wm_state_fullscreen()) |
| SetFullscreenBrowserWindow(browser.get()); |
| else if (fullscreen_browser_) |
| SetFullscreenBrowserWindow(NULL); |
| |
| const int anim_ms = num_browser_windows() > 1 ? kWindowAnimMs : 0; |
| if (active_browser_index_ >= 0 && !win->has_initial_pixmap()) { |
| windows_blocking_arrange_.insert(win); |
| anim_ms_for_pending_arrange_ = anim_ms; |
| } |
| |
| SetActiveBrowserWindowIndex(index); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), anim_ms); |
| |
| if (!first_browser_window_mapped_) { |
| HandleFirstBrowserWindowMapped(win); |
| first_browser_window_mapped_ = true; |
| } |
| } |
| |
| void LayoutManager2::HandleTransientWindowMap(Window* transient_win, |
| BrowserWindow* owning_browser) { |
| DCHECK(transient_win); |
| DCHECK(owning_browser); |
| DCHECK(transient_xids_to_browsers_.count(transient_win->xid()) == 0) |
| << "Transient window " << transient_win->xid_str() << " is already " |
| << "registered"; |
| |
| transient_xids_to_browsers_.insert( |
| make_pair(transient_win->xid(), owning_browser)); |
| owning_browser->HandleTransientWindowMap(transient_win); |
| |
| // If the window is modal or if its owner is the active window, make sure that |
| // it gets the focus. |
| if (transient_win->wm_state_modal() || |
| owning_browser == GetActiveBrowserWindow()) |
| FocusBrowserWindow(owning_browser); |
| } |
| |
| void LayoutManager2::HandleFirstBrowserWindowMapped(Window* win) { |
| DCHECK(win); |
| |
| key_bindings_group_->SetEnabled(true); |
| |
| if (!FLAGS_initial_chrome_window_mapped_file.empty()) { |
| DLOG(INFO) << "Writing initial Chrome window's ID to file " |
| << FLAGS_initial_chrome_window_mapped_file; |
| FILE* file = fopen(FLAGS_initial_chrome_window_mapped_file.c_str(), "w+"); |
| if (!file) { |
| PLOG(ERROR) << "Unable to open file " |
| << FLAGS_initial_chrome_window_mapped_file; |
| } else { |
| fprintf(file, "%lu", win->xid()); |
| fclose(file); |
| } |
| } |
| } |
| |
| void LayoutManager2::ComputeBrowserWindowBounds(vector<Rect>* bounds, |
| int* left_offscreen_index, |
| int* right_offscreen_index) { |
| DCHECK(bounds); |
| bounds->clear(); |
| if (left_offscreen_index) |
| *left_offscreen_index = -1; |
| if (right_offscreen_index) |
| *right_offscreen_index = -1; |
| |
| if (browser_windows_.empty()) |
| return; |
| |
| bounds->resize(num_browser_windows(), Rect()); |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| DCHECK(active_browser); |
| |
| switch (layout_mode_) { |
| case WM_IPC_LAYOUT_MAXIMIZED: { |
| // The maximized case is simple: align the active browser window with the |
| // left edge of our bounds and move one workarea-width away from it for |
| // each other window. |
| (*bounds)[active_browser_index_] = workarea_; |
| for (int i = active_browser_index_ - 1; i >= 0; --i) |
| (*bounds)[i].reset( |
| (*bounds)[i + 1].x - workarea_.width, workarea_.y, |
| workarea_.width, workarea_.height); |
| for (int i = active_browser_index_ + 1; i < num_browser_windows(); ++i) |
| (*bounds)[i].reset( |
| (*bounds)[i - 1].x + workarea_.width, workarea_.y, |
| workarea_.width, workarea_.height); |
| break; |
| } |
| case WM_IPC_LAYOUT_OVERLAPPING: { |
| // Find the index of the browser windows displayed on the left and right |
| // portions of the screen. |
| const int left_index = |
| active_browser_index_ - (active_browser->anchored_to_left() ? 0 : 1); |
| const int right_index = |
| active_browser_index_ + (active_browser->anchored_to_left() ? 1 : 0); |
| DCHECK_GE(left_index, 0); |
| DCHECK_LE(right_index, num_browser_windows() - 1); |
| |
| if (left_offscreen_index && left_index > 0) |
| *left_offscreen_index = left_index - 1; |
| if (right_offscreen_index && right_index < num_browser_windows() - 1) |
| *right_offscreen_index = right_index + 1; |
| |
| BrowserWindow* left_browser = browser_windows_[left_index].get(); |
| BrowserWindow* right_browser = browser_windows_[right_index].get(); |
| |
| // Assign positions to the left and right windows, leaving small gaps on |
| // the sides if there are more windows in either direction. |
| (*bounds)[left_index].reset( |
| workarea_.x + (left_index >= 1 ? kEdgeGapPixels : 0), workarea_.y, |
| left_browser->unmaximized_width(), workarea_.height); |
| (*bounds)[right_index].reset( |
| workarea_.right() - |
| right_browser->unmaximized_width() - |
| (right_index < num_browser_windows() - 1 ? kEdgeGapPixels : 0), |
| workarea_.y, |
| right_browser->unmaximized_width(), |
| workarea_.height); |
| |
| // Next, walk to the left, putting each window one bounds-width from the |
| // right edge of the window to its right (plus any gap that we're |
| // reserving for the next window beyond that one). |
| int prev_right_edge = |
| (*bounds)[left_index].right() + |
| (left_index < num_browser_windows() - 1 ? kEdgeGapPixels : 0); |
| for (int i = left_index - 1; i >= 0; --i) { |
| BrowserWindow* browser = browser_windows_[i].get(); |
| (*bounds)[i].reset( |
| prev_right_edge - workarea_.width + (i >= 1 ? kEdgeGapPixels : 0), |
| workarea_.y, |
| browser->unmaximized_width(), |
| workarea_.height); |
| prev_right_edge = (*bounds)[i].right() + |
| (i < num_browser_windows() - 1 ? kEdgeGapPixels : 0); |
| } |
| |
| // Now take the same approach while walking to the right. |
| int prev_left_edge = (*bounds)[right_index].x - |
| (right_index >= 1 ? kEdgeGapPixels : 0); |
| for (int i = right_index + 1; i < num_browser_windows(); ++i) { |
| BrowserWindow* browser = browser_windows_[i].get(); |
| (*bounds)[i].reset( |
| prev_left_edge + workarea_.width - browser->unmaximized_width() - |
| (i < num_browser_windows() - 1 ? kEdgeGapPixels : 0), |
| workarea_.y, |
| browser->unmaximized_width(), |
| workarea_.height); |
| prev_left_edge = (*bounds)[i].x - (i >= 1 ? kEdgeGapPixels : 0); |
| } |
| break; |
| } |
| default: |
| NOTREACHED() << "Unhandled layout mode " << layout_mode_; |
| } |
| } |
| |
| void LayoutManager2::RestackBrowserWindows( |
| int secondary_browser_index, |
| int left_offscreen_index, |
| int right_offscreen_index) { |
| if (browser_windows_.empty()) |
| return; |
| |
| if (fullscreen_browser_) |
| fullscreen_browser_->StackAtTopOfLayer( |
| StackingManager::LAYER_FULLSCREEN_WINDOW, |
| StackingManager::SHADOW_DIRECTLY_BELOW_ACTOR, |
| StackingManager::LAYER_FULLSCREEN_WINDOW); |
| |
| StackingManager::ShadowPolicy shadow_policy = |
| maximized() ? |
| StackingManager::SHADOW_AT_BOTTOM_OF_SHADOW_LAYER : |
| StackingManager::SHADOW_DIRECTLY_BELOW_ACTOR; |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| if (active_browser != fullscreen_browser_) |
| active_browser->StackAtTopOfLayer( |
| StackingManager::LAYER_ACTIVE_BROWSER_WINDOW, |
| shadow_policy, |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW); |
| |
| BrowserWindow* secondary_browser = |
| secondary_browser_index != -1 ? |
| browser_windows_[secondary_browser_index].get() : |
| NULL; |
| if (secondary_browser && secondary_browser != fullscreen_browser_) |
| secondary_browser->StackAtTopOfLayer( |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW, |
| shadow_policy, |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW); |
| |
| BrowserWindow* prev_browser = secondary_browser; |
| if (left_offscreen_index != -1) { |
| for (int i = left_offscreen_index; i >= 0; --i) { |
| BrowserWindow* browser = browser_windows_[i].get(); |
| if (browser == fullscreen_browser_) |
| continue; |
| if (prev_browser) |
| browser->StackBelowOtherBrowserWindow( |
| prev_browser, |
| shadow_policy, |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW); |
| else |
| browser->StackAtTopOfLayer( |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW, |
| shadow_policy, |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW); |
| prev_browser = browser; |
| } |
| } |
| |
| prev_browser = secondary_browser; |
| if (right_offscreen_index != -1) { |
| for (int i = right_offscreen_index; i < num_browser_windows(); ++i) { |
| BrowserWindow* browser = browser_windows_[i].get(); |
| if (browser == fullscreen_browser_) |
| continue; |
| if (prev_browser) |
| browser->StackBelowOtherBrowserWindow( |
| prev_browser, |
| shadow_policy, |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW); |
| else |
| browser->StackAtTopOfLayer( |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW, |
| shadow_policy, |
| StackingManager::LAYER_INACTIVE_BROWSER_WINDOW); |
| prev_browser = browser; |
| } |
| } |
| } |
| |
| void LayoutManager2::ArrangeBrowserWindows( |
| ArrangeSet arrange_set, |
| ArrangeFocusPolicy focus_policy, |
| XTime timestamp_for_focus, |
| int anim_ms) { |
| if (browser_windows_.empty()) { |
| non_chrome_key_bindings_group_->SetEnabled(false); |
| return; |
| } |
| |
| if (!windows_blocking_arrange_.empty()) |
| return; |
| |
| vector<Rect> bounds; |
| int left_offscreen_index = -1, right_offscreen_index = -1; |
| ComputeBrowserWindowBounds(&bounds, |
| &left_offscreen_index, |
| &right_offscreen_index); |
| |
| // Make sure that we're compositing before we start moving windows around; |
| // otherwise, if we're not currently compositing, making the active browser |
| // slide offscreen will look janky -- when we move its X window offscreen, |
| // it'll instantaneously disappear. |
| scoped_ptr<WindowManager::ScopedCompositingRequest> comp_request( |
| wm_->CreateScopedCompositingRequest()); |
| |
| if (fullscreen_browser_) { |
| fullscreen_browser_->SetStatusAreaDisplayed(true); |
| fullscreen_browser_->SetBounds(wm_->root_bounds(), 0); |
| fullscreen_browser_->SetBrightness(1.0, 0); |
| fullscreen_browser_->SetTransientWindowVisibility(true); |
| if (focus_policy == ASSIGN_FOCUS_DURING_ARRANGE) |
| fullscreen_browser_->TakeFocus(timestamp_for_focus); |
| non_chrome_key_bindings_group_->SetEnabled(false); |
| } |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| DCHECK(active_browser); |
| if (focus_policy == ASSIGN_FOCUS_DURING_ARRANGE && !fullscreen_browser_) { |
| active_browser->TakeFocus(timestamp_for_focus); |
| non_chrome_key_bindings_group_->SetEnabled( |
| active_browser->win()->type() == chromeos::WM_IPC_WINDOW_UNKNOWN); |
| } |
| |
| int secondary_browser_index = -1; |
| if (!maximized() && num_browser_windows() > 1) { |
| secondary_browser_index = |
| active_browser_index_ + (active_browser->anchored_to_left() ? 1 : -1); |
| DCHECK_GE(secondary_browser_index, 0); |
| DCHECK_LT(secondary_browser_index, num_browser_windows()); |
| } |
| |
| if (arrange_set == ARRANGE_ALL_BROWSERS) |
| RestackBrowserWindows(secondary_browser_index, |
| left_offscreen_index, |
| right_offscreen_index); |
| |
| // Determine if the active and secondary (if present) windows are actually |
| // Chrome windows. This can be removed if/when things like cros-term are |
| // gone. |
| const bool active_is_chrome = |
| active_browser->win()->type() == |
| chromeos::WM_IPC_WINDOW_CHROME_TOPLEVEL; |
| const bool secondary_is_chrome = |
| secondary_browser_index != -1 && |
| (browser_windows_[secondary_browser_index]->win()->type() == |
| chromeos::WM_IPC_WINDOW_CHROME_TOPLEVEL); |
| |
| for (int i = 0; i < num_browser_windows(); ++i) { |
| BrowserWindow* browser = browser_windows_[i].get(); |
| const bool active = (i == active_browser_index_); |
| const bool secondary = (i == secondary_browser_index); |
| |
| if (browser == fullscreen_browser_) |
| continue; |
| |
| if ((arrange_set == ARRANGE_ACTIVE_BROWSER && !active) || |
| (arrange_set == ARRANGE_INACTIVE_BROWSERS && active)) |
| continue; |
| |
| browser->SetTransientWindowVisibility(active && !fullscreen_browser_); |
| |
| // Is the window either partially or entirely offscreen? |
| const bool offscreen = |
| (maximized() && !active) || |
| (left_offscreen_index >= 0 && i <= left_offscreen_index) || |
| (right_offscreen_index >= 0 && i >= right_offscreen_index); |
| |
| // Tell this browser to display a status area if it's fully onscreen and on |
| // the right (or even if it's on the left, if the window on the right isn't |
| // Chrome). |
| const bool on_right = |
| (maximized() && active) || |
| (active && !browser->anchored_to_left()) || |
| (secondary && active_browser->anchored_to_left()); |
| const bool display_status = |
| on_right || |
| (active && !secondary_is_chrome) || |
| (secondary && !active_is_chrome); |
| browser->SetStatusAreaDisplayed(display_status); |
| |
| browser->SetMaximizedProperty(maximized()); |
| browser->SetBounds(bounds[i], anim_ms); |
| |
| double brightness = 1.0; |
| int brightness_anim_ms = anim_ms; |
| |
| if (active) { |
| brightness_anim_ms = 0; |
| } else if (i == secondary_browser_index) { |
| brightness = kSecondaryBrowserWindowBrightness; |
| const double current_brightness = browser->GetInstantaneousBrightness(); |
| if (current_brightness > kSecondaryBrowserWindowBrightness) { |
| // If we're brighter than the target brightness, dim to it gradually. |
| const double fraction = |
| (current_brightness - kSecondaryBrowserWindowBrightness) / |
| (1.0 - kSecondaryBrowserWindowBrightness); |
| brightness_anim_ms = kSecondaryBrowserWindowDimAnimMs * fraction; |
| } |
| } else if (!maximized() && offscreen) { |
| brightness = kEdgeBrowserWindowBrightness; |
| } |
| |
| browser->SetBrightness(brightness, brightness_anim_ms); |
| } |
| |
| ConfigureResizeHandle(); |
| ConfigureEdgeInputWindows(left_offscreen_index, right_offscreen_index); |
| } |
| |
| void LayoutManager2::SetActiveBrowserWindowIndex(int browser_index) { |
| DCHECK_GE(browser_index, 0); |
| DCHECK_LT(browser_index, num_browser_windows()); |
| |
| const int old_index = active_browser_index_; |
| active_browser_index_ = browser_index; |
| DLOG(INFO) << "Activating browser window at index " << browser_index |
| << " (old was " << old_index << ")"; |
| |
| BrowserWindow::AnchorPosition anchoring = BrowserWindow::ANCHOR_LEFT; |
| if (browser_index == 0) |
| anchoring = BrowserWindow::ANCHOR_LEFT; |
| else if (browser_index == num_browser_windows() - 1) |
| anchoring = BrowserWindow::ANCHOR_RIGHT; |
| else if (browser_index >= old_index) |
| anchoring = BrowserWindow::ANCHOR_RIGHT; |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| active_browser->set_unmaximized_anchoring(anchoring); |
| } |
| |
| void LayoutManager2::CycleActiveBrowserWindow(bool forward) { |
| if (browser_windows_.empty()) |
| return; |
| |
| if (wm_->key_bindings()->current_event_time()) { |
| const KeyBindings::KeyCombo& combo = |
| wm_->key_bindings()->current_key_combo(); |
| if (forward) { |
| if (combo.keysym == XK_Tab) |
| wm_->ReportUserAction("Accel_NextWindow_Tab"); |
| else if (combo.keysym == XK_F5) |
| wm_->ReportUserAction("Accel_NextWindow_F5"); |
| } else { |
| if (combo.keysym == XK_Tab) |
| wm_->ReportUserAction("Accel_PrevWindow_Tab"); |
| else if (combo.keysym == XK_F5) |
| wm_->ReportUserAction("Accel_PrevWindow_F5"); |
| } |
| } |
| |
| if (num_browser_windows() == 1) { |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| DCHECK(active_browser); |
| active_browser->TakeFocus(GetCurrentXTime()); |
| active_browser->DoNudgeAnimation(forward); |
| return; |
| } |
| |
| if (fullscreen_browser_) |
| SetFullscreenBrowserWindow(NULL); |
| |
| const int new_index = |
| (active_browser_index_ + num_browser_windows() + (forward ? 1 : -1)) % |
| num_browser_windows(); |
| const int anim_ms = GetWindowAnimationMs(new_index); |
| SetActiveBrowserWindowIndex(new_index); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), anim_ms); |
| } |
| |
| void LayoutManager2::ActivateBrowserWindowByIndex(int browser_index) { |
| if (browser_windows_.empty()) |
| return; |
| |
| if (browser_index < 0) |
| browser_index += num_browser_windows(); |
| |
| if (browser_index < 0 || browser_index >= num_browser_windows()) |
| return; |
| if (browser_index == active_browser_index_) |
| return; |
| |
| const int anim_ms = GetWindowAnimationMs(browser_index); |
| SetActiveBrowserWindowIndex(browser_index); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), anim_ms); |
| } |
| |
| void LayoutManager2::ToggleMaximized() { |
| if (num_browser_windows() == 1) { |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| active_browser->DoSquishAnimation(); |
| return; |
| } |
| |
| SetLayoutMode( |
| maximized() ? WM_IPC_LAYOUT_OVERLAPPING : WM_IPC_LAYOUT_MAXIMIZED, true); |
| } |
| |
| void LayoutManager2::SetLayoutMode(WmIpcLayoutMode mode, |
| bool arrange_browsers) { |
| if (mode != WM_IPC_LAYOUT_MAXIMIZED && mode != WM_IPC_LAYOUT_OVERLAPPING) { |
| LOG(WARNING) << "Ignoring request to set unknown layout mode " << mode; |
| return; |
| } |
| |
| if (mode == layout_mode_) |
| return; |
| |
| if (mode != WM_IPC_LAYOUT_MAXIMIZED && num_browser_windows() < 2) { |
| LOG(WARNING) << "Ignoring request to set non-maximized layout mode " << mode |
| << " with " << num_browser_windows() << " window(s)"; |
| return; |
| } |
| |
| if (fullscreen_browser_) |
| SetFullscreenBrowserWindow(NULL); |
| |
| const WmIpcLayoutMode old_mode = layout_mode_; |
| layout_mode_ = mode; |
| |
| wm_->xconn()->SetIntProperty( |
| wm_->root(), |
| wm_->GetXAtom(ATOM_CHROME_LAYOUT_MODE), |
| wm_->GetXAtom(ATOM_CARDINAL), |
| layout_mode_); |
| |
| if (!arrange_browsers || browser_windows_.empty()) |
| return; |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| Window* active_win = active_browser->win(); |
| |
| if (mode == WM_IPC_LAYOUT_MAXIMIZED) { |
| // If we're switching to maximized, resize the active browser window to |
| // cover the whole screen first and wait for it to get redrawn before |
| // arranging any of the other windows -- we don't want them to be visible |
| // moving around in the background. |
| ArrangeBrowserWindows(ARRANGE_ACTIVE_BROWSER, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), 0); |
| if (!active_win->client_has_redrawn_after_last_resize()) { |
| windows_blocking_arrange_.insert(active_win); |
| anim_ms_for_pending_arrange_ = 0; |
| } |
| } else if (old_mode == WM_IPC_LAYOUT_MAXIMIZED) { |
| // If we're switching to a non-maximized mode, resize all of the other |
| // browser windows first so they'll be in place when the active browser is |
| // resized to not cover the whole screen. |
| ArrangeBrowserWindows(ARRANGE_INACTIVE_BROWSERS, |
| ASSIGN_FOCUS_DURING_ARRANGE, GetCurrentXTime(), 0); |
| for (int i = 0; i < num_browser_windows(); ++i) { |
| if (i == active_browser_index_) |
| continue; |
| BrowserWindow* browser = browser_windows_[i].get(); |
| if (!browser->win()->client_has_redrawn_after_last_resize()) { |
| windows_blocking_arrange_.insert(browser->win()); |
| anim_ms_for_pending_arrange_ = 0; |
| } |
| } |
| } |
| |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), 0); |
| } |
| |
| void LayoutManager2::ChangeActiveBrowserUnmaximizedWidth(int new_width) { |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| DCHECK(active_browser); |
| if (active_browser->unmaximized_width() == new_width) |
| return; |
| |
| active_browser->set_unmaximized_width(new_width); |
| if (maximized()) |
| return; |
| |
| vector<Rect> bounds; |
| ComputeBrowserWindowBounds(&bounds, NULL, NULL); |
| active_browser->SetBounds(bounds[active_browser_index_], 0); |
| ConfigureResizeHandle(); |
| } |
| |
| void LayoutManager2::ResizeActiveBrowserWindowIncrementally(bool to_right) { |
| if (browser_windows_.empty()) |
| return; |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| |
| const bool should_widen = |
| (to_right && active_browser->anchored_to_left()) || |
| (!to_right && !active_browser->anchored_to_left()); |
| const int cur_width = active_browser->unmaximized_width(); |
| const int max_width = GetMaxUnmaximizedWidth(); |
| const int min_width = GetMinUnmaximizedWidth(); |
| |
| if (maximized()) { |
| if (!should_widen) { |
| ChangeActiveBrowserUnmaximizedWidth(max_width); |
| ToggleMaximized(); |
| } |
| return; |
| } |
| |
| if (should_widen && cur_width >= max_width) { |
| ToggleMaximized(); |
| return; |
| } |
| |
| const double increment = |
| static_cast<double>(max_width - min_width) / kNumIncrementalResizeSteps; |
| |
| int new_width = 0; |
| if (should_widen) { |
| const int min_new_size = |
| cur_width + kIncrementalResizeMinChangePercent * increment; |
| for (int i = 0; i <= kNumIncrementalResizeSteps; ++i) { |
| new_width = round(min_width + i * increment); |
| if (new_width >= min_new_size) |
| break; |
| } |
| } else { |
| const int max_new_size = |
| cur_width - kIncrementalResizeMinChangePercent * increment; |
| for (int i = 0; i <= kNumIncrementalResizeSteps; ++i) { |
| new_width = round(max_width - i * increment); |
| if (new_width <= max_new_size) |
| break; |
| } |
| } |
| |
| ChangeActiveBrowserUnmaximizedWidth(ConstrainUnmaximizedWidth(new_width)); |
| } |
| |
| void LayoutManager2::ConfigureResizeHandle() { |
| if (maximized() || fullscreen_browser_ || browser_windows_.empty()) { |
| wm_->xconn()->ConfigureWindowOffscreen(resize_handle_xid_); |
| } else { |
| BrowserWindow* browser = GetActiveBrowserWindow(); |
| int x = browser->win()->client_x() + |
| (browser->anchored_to_left() ? browser->unmaximized_width() : 0); |
| if (!browser->anchored_to_left()) |
| x -= kResizeHandleWidthPixels; |
| Rect bounds(x, workarea_.y, kResizeHandleWidthPixels, workarea_.height); |
| wm_->ConfigureInputWindow(resize_handle_xid_, bounds); |
| |
| XID cursor = browser->anchored_to_left() ? right_cursor_ : left_cursor_; |
| wm_->xconn()->SetWindowCursor(resize_handle_xid_, cursor); |
| |
| // If we're showing the handle's actor, move it to the updated position. |
| if (resize_handle_actor_is_visible_) { |
| resize_handle_actor_->Scale(1.0, 1.0, 0); |
| resize_handle_actor_->MoveX( |
| browser->win()->client_x() + |
| (browser->anchored_to_left() ? |
| browser->unmaximized_width() : |
| -kResizeHandleShadowExtendPixels), |
| 0); |
| } |
| } |
| } |
| |
| void LayoutManager2::ConfigureEdgeInputWindows(int left_offscreen_index, |
| int right_offscreen_index) { |
| if (left_offscreen_index != -1) { |
| wm_->ConfigureInputWindow( |
| left_edge_input_xid_, |
| Rect(workarea_.x, workarea_.y, kEdgeGapPixels, workarea_.height)); |
| } else { |
| wm_->xconn()->ConfigureWindowOffscreen(left_edge_input_xid_); |
| } |
| |
| if (right_offscreen_index != -1) { |
| wm_->ConfigureInputWindow( |
| right_edge_input_xid_, |
| Rect(workarea_.right() - kEdgeGapPixels, workarea_.y, |
| kEdgeGapPixels, workarea_.height)); |
| } else { |
| wm_->xconn()->ConfigureWindowOffscreen(right_edge_input_xid_); |
| } |
| } |
| |
| void LayoutManager2::HandleResizeDragStart(const Point& absolute_pos) { |
| if (browser_windows_.empty() || maximized()) |
| return; |
| |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| in_resize_drag_ = true; |
| resize_drag_start_pos_ = absolute_pos; |
| resize_width_at_start_ = active_browser->unmaximized_width(); |
| resize_box_is_snapped_ = false; |
| resize_box_unsnap_time_ = TimeTicks(); |
| |
| resize_box_.reset(new ResizeBox(wm_->compositor())); |
| Rect bounds(active_browser->win()->client_x(), workarea_.y, |
| active_browser->unmaximized_width(), workarea_.height); |
| resize_box_->SetBounds(bounds, 0); |
| resize_box_->actor()->SetOpacity(kResizeBoxOpacity, 0); |
| resize_box_->actor()->Show(); |
| wm_->stage()->AddActor(resize_box_->actor()); |
| } |
| |
| void LayoutManager2::HandleResizeDragMotion(const Point& absolute_pos) { |
| if (browser_windows_.empty() || !in_resize_drag_) |
| return; |
| |
| const int dx = absolute_pos.x - resize_drag_start_pos_.x; |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| int width = |
| resize_width_at_start_ + |
| (active_browser->anchored_to_left() ? 1 : -1) * dx; |
| |
| if (width >= GetMaxUnmaximizedWidth() + kResizeSnapThresholdPixels) { |
| if (!resize_box_is_snapped_) { |
| resize_box_is_snapped_ = true; |
| resize_box_->SetBounds(workarea_, kResizeBoxSnapAnimMs); |
| last_resize_box_bounds_ = workarea_; |
| } |
| } else { |
| width = ConstrainUnmaximizedWidth(width); |
| Rect bounds(active_browser->win()->client_x(), workarea_.y, |
| width, workarea_.height); |
| if (!active_browser->anchored_to_left()) |
| bounds.x -= (width - resize_width_at_start_); |
| |
| TimeTicks now = GetMonotonicTime(); |
| if (resize_box_is_snapped_) { |
| resize_box_is_snapped_ = false; |
| resize_box_unsnap_time_ = now; |
| } |
| |
| if (bounds != last_resize_box_bounds_) { |
| int anim_ms = 0; |
| if (!resize_box_unsnap_time_.is_null()) { |
| // Restarting the unsnap animation like this is a bit hacky. Ideally, |
| // we'd be able to adjust the endpoint of the existing unsnap animation |
| // or make the animations (if any) created by the SetBounds() call start |
| // at full speed. This seems to look okay, though... |
| const int elapsed_ms = static_cast<int>( |
| (now - resize_box_unsnap_time_).InMilliseconds()); |
| anim_ms = max(0, kResizeBoxSnapAnimMs - elapsed_ms); |
| } |
| resize_box_->SetBounds(bounds, anim_ms); |
| last_resize_box_bounds_ = bounds; |
| } |
| } |
| } |
| |
| void LayoutManager2::HandleResizeDragEnd(const Point& absolute_pos) { |
| if (browser_windows_.empty() || !in_resize_drag_) |
| return; |
| |
| in_resize_drag_ = false; |
| resize_box_.reset(); |
| |
| const int dx = absolute_pos.x - resize_drag_start_pos_.x; |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| int new_width = |
| active_browser->unmaximized_width() + |
| (active_browser->anchored_to_left() ? 1 : -1) * dx; |
| |
| if (new_width >= GetMaxUnmaximizedWidth() + kResizeSnapThresholdPixels) |
| SetLayoutMode(WM_IPC_LAYOUT_MAXIMIZED, true); |
| else |
| ChangeActiveBrowserUnmaximizedWidth(ConstrainUnmaximizedWidth(new_width)); |
| } |
| |
| void LayoutManager2::HandleWindowNoLongerBlockingArrange(Window* win) { |
| set<Window*>::iterator it = windows_blocking_arrange_.find(win); |
| if (it == windows_blocking_arrange_.end()) |
| return; |
| |
| windows_blocking_arrange_.erase(it); |
| if (windows_blocking_arrange_.empty()) |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), anim_ms_for_pending_arrange_); |
| } |
| |
| void LayoutManager2::FocusBrowserWindow(BrowserWindow* browser) { |
| if (fullscreen_browser_ && browser != fullscreen_browser_) { |
| SetFullscreenBrowserWindow(NULL); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), 0); |
| } |
| |
| if (browser == GetActiveBrowserWindow()) { |
| browser->TakeFocus(GetCurrentXTime()); |
| } else { |
| SetActiveBrowserWindowIndex(GetBrowserWindowIndex(*browser)); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), kWindowAnimMs); |
| } |
| } |
| |
| void LayoutManager2::SetFullscreenBrowserWindow(BrowserWindow* browser) { |
| if (browser == fullscreen_browser_) |
| return; |
| |
| if (fullscreen_browser_) { |
| fullscreen_browser_->SetFullscreenProperty(false); |
| fullscreen_browser_ = NULL; |
| } |
| |
| if (browser) { |
| browser->SetFullscreenProperty(true); |
| fullscreen_browser_ = browser; |
| } |
| } |
| |
| void LayoutManager2::HandleFullscreenRequest(Window* win, bool fullscreen) { |
| BrowserWindow* browser = FindBrowserWindowByXid(win->xid()); |
| if (!browser) |
| return; |
| |
| DLOG(INFO) << "Got _NET_WM_STATE request to make " << win->xid_str() |
| << (fullscreen ? " fullscreen" : " not fullscreen"); |
| |
| if (!fullscreen && fullscreen_browser_ == browser) { |
| SetFullscreenBrowserWindow(NULL); |
| } else if (fullscreen) { |
| SetFullscreenBrowserWindow(browser); |
| SetActiveBrowserWindowIndex(GetBrowserWindowIndex(*browser)); |
| ArrangeBrowserWindows(ARRANGE_ACTIVE_BROWSER, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), 0); |
| if (!win->client_has_redrawn_after_last_resize()) { |
| windows_blocking_arrange_.insert(win); |
| anim_ms_for_pending_arrange_ = 0; |
| } |
| } |
| |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| GetCurrentXTime(), 0); |
| } |
| |
| void LayoutManager2::HandleModalityRequest(Window* win, bool modal) { |
| BrowserWindow* owning_browser = FindBrowserWindowOwningTransientWindow(*win); |
| if (!owning_browser) |
| return; |
| |
| DLOG(INFO) << "Got _NET_WM_STATE request to make " << win->xid_str() |
| << (modal ? " modal" : " not modal"); |
| map<XAtom, bool> new_state; |
| new_state[wm_->GetXAtom(ATOM_NET_WM_STATE_MODAL)] = modal; |
| win->ChangeWmState(new_state); |
| |
| owning_browser->HandleTransientWindowModalityChange(win); |
| if (modal || owning_browser == GetActiveBrowserWindow()) |
| FocusBrowserWindow(owning_browser); |
| } |
| |
| void LayoutManager2::HandleActiveWindowRequest(Window* win, XTime timestamp) { |
| int index = FindBrowserWindowIndexByWindow(*win); |
| if (index == -1) { |
| BrowserWindow* browser = FindBrowserWindowOwningTransientWindow(*win); |
| if (!browser) |
| return; |
| browser->SetPreferredTransientWindowToFocus(win); |
| index = GetBrowserWindowIndex(*browser); |
| } |
| |
| DLOG(INFO) << "Got _NET_ACTIVE_WINDOW request to focus " << win->xid_str(); |
| |
| // Ignore the request if a modal window is already focused. |
| if (wm_->IsModalWindowFocused()) |
| return; |
| |
| if (fullscreen_browser_ && |
| browser_windows_[index].get() != fullscreen_browser_) |
| SetFullscreenBrowserWindow(NULL); |
| |
| if (index == active_browser_index_) { |
| FocusBrowserWindow(browser_windows_[index].get()); |
| } else { |
| SetActiveBrowserWindowIndex(index); |
| ArrangeBrowserWindows(ARRANGE_ALL_BROWSERS, ASSIGN_FOCUS_DURING_ARRANGE, |
| timestamp, kWindowAnimMs); |
| } |
| } |
| |
| void LayoutManager2::CloseFocusedNonChromeWindow() { |
| BrowserWindow* active_browser = GetActiveBrowserWindow(); |
| if (!active_browser) |
| return; |
| if (active_browser->win()->type() != chromeos::WM_IPC_WINDOW_UNKNOWN) |
| return; |
| active_browser->win()->SendDeleteRequest(GetCurrentXTime()); |
| } |
| |
| } // namespace window_manager |