blob: a1681cf0d80c564be7293db2b1f22622c1f3226c [file] [log] [blame]
// 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