| // 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/window_manager.h" |
| |
| #include <cstring> |
| #include <ctime> |
| #include <list> |
| #include <queue> |
| #include <tr1/unordered_set> |
| |
| extern "C" { |
| #include <X11/cursorfont.h> |
| #include <X11/Xatom.h> |
| #include <X11/XF86keysym.h> |
| #include <X11/Xcursor/Xcursor.h> |
| } |
| #include <gflags/gflags.h> |
| |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/string_util.h" |
| #include "cros/chromeos_wm_ipc_enums.h" |
| #include "metrics/metrics_library.h" |
| #include "window_manager/callback.h" |
| #include "window_manager/chrome_watchdog.h" |
| #include "window_manager/dbus_interface.h" |
| #include "window_manager/event_consumer.h" |
| #include "window_manager/event_loop.h" |
| #include "window_manager/focus_manager.h" |
| #include "window_manager/geometry.h" |
| #include "window_manager/image_container.h" |
| #include "window_manager/image_enums.h" |
| #include "window_manager/key_bindings.h" |
| #include "window_manager/layout2/layout_manager2.h" |
| #include "window_manager/login/login_controller.h" |
| #include "window_manager/modality_handler.h" |
| #include "window_manager/panels/panel_manager.h" |
| #include "window_manager/profiler.h" |
| #include "window_manager/screen_locker_handler.h" |
| #include "window_manager/stacking_manager.h" |
| #include "window_manager/util.h" |
| #include "window_manager/window.h" |
| #include "window_manager/x11/x_connection.h" |
| |
| DEFINE_string(background_color, "#000", "Background color"); |
| DEFINE_string(screenshot_binary, |
| "/usr/bin/screenshot", |
| "Path to the screenshot binary"); |
| DEFINE_string(logged_in_screenshot_output_dir, |
| ".", "Output directory for screenshots when logged in"); |
| DEFINE_string(logged_out_screenshot_output_dir, |
| ".", "Output directory for screenshots when not logged in"); |
| DEFINE_string(logged_in_log_dir, |
| ".", "Directory to write logs to when logged in"); |
| DEFINE_string(logged_out_log_dir, |
| ".", "Directory to write logs to when not logged in"); |
| DEFINE_string(unaccelerated_graphics_image, |
| "../assets/images/unaccelerated_graphics.png", |
| "Image to display when using unaccelerated rendering"); |
| DEFINE_string(debug_build_image, |
| "../assets/images/debug_build.png", |
| "Image to display when running a debug build"); |
| DEFINE_bool(unredirect_fullscreen_window, |
| false, |
| "Enable/disable compositing optimization that automatically turns" |
| "off compositing if a topmost fullscreen window is present"); |
| DEFINE_bool(report_metrics, false, "Report user action metrics via Chrome"); |
| |
| using base::hash_map; |
| using base::TimeDelta; |
| using base::TimeTicks; |
| using chromeos::WmIpcMessageType; |
| using std::list; |
| using std::make_pair; |
| using std::map; |
| using std::set; |
| using std::string; |
| using std::tr1::shared_ptr; |
| using std::tr1::unordered_set; |
| using std::vector; |
| using window_manager::util::FindWithDefault; |
| using window_manager::util::GetTimeAsString; |
| using window_manager::util::GetCurrentTimeSec; |
| using window_manager::util::GetMonotonicTime; |
| using window_manager::util::RunCommandInBackground; |
| using window_manager::util::SetUpLogSymlink; |
| using window_manager::util::XidStr; |
| |
| namespace window_manager { |
| |
| // How many pixels should the "Unaccelerated graphics" message be offset from |
| // the upper-left corner of the screen? |
| static const int kUnacceleratedGraphicsActorOffsetPixels = 5; |
| |
| // How long should we wait before dropping |informational_actors_|? |
| static const int kDropInformationalActorsTimeoutMs = 15000; |
| |
| // How frequently should we send _NET_WM_PING messages to Chrome, and how |
| // long should we wait for a response to each before killing the process? |
| static const int kPingChromeFrequencyMs = 30000; |
| static const int kPingChromeTimeoutMs = 20000; |
| COMPILE_ASSERT(kPingChromeFrequencyMs > kPingChromeTimeoutMs, |
| ping_timeout_is_greater_than_ping_frequency); |
| |
| // Names of key binding actions that we register. |
| #ifndef NDEBUG |
| static const char* kToggleClientWindowDebuggingAction = |
| "toggle-client-window-debugging"; |
| static const char* kToggleDamageDebuggingAction = "toggle-damage-debugging"; |
| static const char* kToggleProfilerAction = "toggle-profiler"; |
| #endif |
| |
| static const char* kTakeRootScreenshotAction = "take-root-screenshot"; |
| static const char* kTakeRegionScreenshotAction = "take-region-screenshot"; |
| |
| const int WindowManager::kVideoTimePropertyUpdateSec = 5; |
| |
| // Invoke |function_call| for each event consumer in |consumers| (a set). |
| #define FOR_EACH_EVENT_CONSUMER(consumers, function_call) \ |
| do { \ |
| for (set<EventConsumer*>::iterator it = \ |
| consumers.begin(); it != consumers.end(); ++it) { \ |
| (*it)->function_call; \ |
| } \ |
| } while (0) |
| |
| // Look up the event consumers that have registered interest in |key| in |
| // |consumer_map| (one of the WindowManager::*_event_consumers_ member |
| // variables), and invoke |function_call| (e.g. |
| // "HandleWindowPropertyChange(e.window, e.atom)") on each. Helper macro |
| // used by WindowManager's event-handling methods. |
| // |
| // We iterate over a copy of the set of consumers so we won't crash if any of |
| // the handlers modify the set by registering or unregistering consumers. |
| #define FOR_EACH_INTERESTED_EVENT_CONSUMER(consumer_map, key, function_call) \ |
| do { \ |
| typeof(consumer_map.begin()) it = consumer_map.find(key); \ |
| if (it != consumer_map.end()) { \ |
| set<EventConsumer*> ecs = it->second; \ |
| for (set<EventConsumer*>::iterator ec_it = ecs.begin(); \ |
| ec_it != ecs.end(); ++ec_it) { \ |
| (*ec_it)->function_call; \ |
| } \ |
| } \ |
| } while (0) |
| |
| // Used by helper functions to generate |case| statements. |
| #define CASE_RETURN_LABEL(label) \ |
| case label: return #label |
| |
| #undef DEBUG_EVENTS // Turn this on if you want to debug events. |
| #ifdef DEBUG_EVENTS |
| static const char* XEventTypeToName(int type) { |
| switch (type) { |
| CASE_RETURN_LABEL(ButtonPress); |
| CASE_RETURN_LABEL(ButtonRelease); |
| CASE_RETURN_LABEL(CirculateNotify); |
| CASE_RETURN_LABEL(CirculateRequest); |
| CASE_RETURN_LABEL(ClientMessage); |
| CASE_RETURN_LABEL(ColormapNotify); |
| CASE_RETURN_LABEL(ConfigureNotify); |
| CASE_RETURN_LABEL(ConfigureRequest); |
| CASE_RETURN_LABEL(CreateNotify); |
| CASE_RETURN_LABEL(DestroyNotify); |
| CASE_RETURN_LABEL(EnterNotify); |
| CASE_RETURN_LABEL(Expose); |
| CASE_RETURN_LABEL(FocusIn); |
| CASE_RETURN_LABEL(FocusOut); |
| CASE_RETURN_LABEL(GraphicsExpose); |
| CASE_RETURN_LABEL(GravityNotify); |
| CASE_RETURN_LABEL(KeymapNotify); |
| CASE_RETURN_LABEL(KeyPress); |
| CASE_RETURN_LABEL(KeyRelease); |
| CASE_RETURN_LABEL(LeaveNotify); |
| CASE_RETURN_LABEL(MapNotify); |
| CASE_RETURN_LABEL(MappingNotify); |
| CASE_RETURN_LABEL(MapRequest); |
| CASE_RETURN_LABEL(MotionNotify); |
| CASE_RETURN_LABEL(NoExpose); |
| CASE_RETURN_LABEL(PropertyNotify); |
| CASE_RETURN_LABEL(ReparentNotify); |
| CASE_RETURN_LABEL(ResizeRequest); |
| CASE_RETURN_LABEL(SelectionClear); |
| CASE_RETURN_LABEL(SelectionNotify); |
| CASE_RETURN_LABEL(SelectionRequest); |
| CASE_RETURN_LABEL(UnmapNotify); |
| CASE_RETURN_LABEL(VisibilityNotify); |
| default: return "Unknown"; |
| } |
| } |
| #endif |
| |
| WindowManager::ScopedCompositingRequest::ScopedCompositingRequest( |
| WindowManager* wm) |
| : wm_(wm) { |
| wm_->IncrementCompositingRequests(); |
| } |
| |
| WindowManager::ScopedCompositingRequest::~ScopedCompositingRequest() { |
| wm_->DecrementCompositingRequests(); |
| wm_ = NULL; |
| } |
| |
| WindowManager::WindowManager(EventLoop* event_loop, |
| XConnection* xconn, |
| Compositor* compositor, |
| DBusInterface* dbus) |
| : event_loop_(event_loop), |
| xconn_(xconn), |
| compositor_(compositor), |
| dbus_(dbus), |
| root_(0), |
| wm_xid_(0), |
| stage_(NULL), |
| stage_xid_(0), |
| overlay_xid_(0), |
| startup_pixmap_(0), |
| mapped_xids_(new Stacker<XWindow>), |
| stacked_xids_(new Stacker<XWindow>), |
| damage_debugging_enabled_(false), |
| active_window_xid_(0), |
| query_keyboard_state_timeout_id_(EventLoop::kUnsetTimeoutId), |
| unredirected_fullscreen_xid_(0), |
| disable_compositing_task_is_pending_(false), |
| wm_ipc_version_(1), |
| logged_in_(false), |
| initialize_logging_(false), |
| drop_informational_actors_timeout_id_(EventLoop::kUnsetTimeoutId), |
| chrome_watchdog_timeout_id_(EventLoop::kUnsetTimeoutId), |
| num_compositing_requests_(0) { |
| CHECK(event_loop_); |
| CHECK(xconn_); |
| CHECK(compositor_); |
| } |
| |
| WindowManager::~WindowManager() { |
| if (startup_pixmap_) |
| xconn_->FreePixmap(startup_pixmap_); |
| |
| event_loop_->RemoveTimeoutIfSet(&query_keyboard_state_timeout_id_); |
| event_loop_->RemoveTimeoutIfSet(&chrome_watchdog_timeout_id_); |
| event_loop_->RemoveTimeoutIfSet(&drop_informational_actors_timeout_id_); |
| |
| if (panel_manager_.get()) |
| panel_manager_->UnregisterAreaChangeListener(this); |
| if (compositor_) |
| compositor_->UnregisterCompositionChangeListener(this); |
| } |
| |
| void WindowManager::HandlePanelManagerAreaChange() { |
| SetEwmhWorkareaProperty(); |
| } |
| |
| void WindowManager::HandleTopFullscreenActorChange( |
| const Compositor::TexturePixmapActor* top_fullscreen_actor) { |
| const bool was_compositing = unredirected_fullscreen_xid_ == 0; |
| bool should_composite = true; |
| XWindow window_to_unredirect = 0; |
| |
| // If we're debugging damage events, trying to unredirect a window puts us in |
| // a flickery loop: |
| // |
| // 10 We unredirect the browser window, causing a redraw. |
| // 20 We display a debugging actor to visualize the redraw. |
| // 30 We redirect the browser window since there's another actor onscreen. |
| // 40 The debugging actor fades away, leaving us with only the browser window |
| // onscreen. |
| // 50 GOTO 10 |
| const bool unredirection_permitted = |
| FLAGS_unredirect_fullscreen_window && |
| !damage_debugging_enabled_ && |
| !num_compositing_requests_; |
| |
| if (unredirection_permitted && top_fullscreen_actor) { |
| Window* win = GetWindowOwningActor(*top_fullscreen_actor); |
| if (win != NULL && |
| win->client_x() == win->composited_x() && |
| win->client_y() == win->composited_y() && |
| win->composited_scale_x() == 1.0 && |
| win->composited_scale_y() == 1.0 && |
| // If we're waiting for the window to be repainted so we can fetch a |
| // resized pixmap for it, we don't want to turn off compositing. |
| win->client_has_redrawn_after_last_resize()) { |
| window_to_unredirect = win->xid(); |
| should_composite = false; |
| } |
| } |
| |
| if (unredirected_fullscreen_xid_) { |
| if (unredirected_fullscreen_xid_ == window_to_unredirect) { |
| window_to_unredirect = 0; |
| } else { |
| Window* win = GetWindow(unredirected_fullscreen_xid_); |
| if (win) { |
| // Grab the server here to avoid a race condition between Chrome and |
| // window manager that result in window been reset while Chrome is |
| // writing into it. |
| scoped_ptr<XConnection::ScopedServerGrab> grab( |
| xconn_->CreateScopedServerGrab()); |
| xconn_->RedirectWindowForCompositing(unredirected_fullscreen_xid_); |
| win->HandleRedirect(); |
| } else { |
| DLOG(WARNING) << "The previously unredirected window with XID=" |
| << unredirected_fullscreen_xid_ << " no longer exists"; |
| } |
| unredirected_fullscreen_xid_ = 0; |
| // Force the frame to draw when changing from one fullscreen actor to |
| // another fullscreen actor in case X does not redraw the entire |
| // screen and we get a partially updated frame. |
| } |
| } |
| |
| if (window_to_unredirect) { |
| // Don't update should_draw_frame here; we want to draw the current frame |
| // before we disable compositing. The flag is updated in the |
| // DisableCompositing callback, which does the actual disabling. |
| unredirected_fullscreen_xid_ = window_to_unredirect; |
| if (!disable_compositing_task_is_pending_) { |
| event_loop_->PostTask( |
| NewPermanentCallback(this, &WindowManager::DisableCompositing)); |
| disable_compositing_task_is_pending_ = true; |
| } |
| } |
| |
| if (!was_compositing && should_composite) { |
| xconn_->ResetWindowBoundingRegionToDefault(overlay_xid_); |
| DLOG(INFO) << "Turned compositing on"; |
| compositor_->set_should_draw_frame(true); |
| } |
| } |
| |
| bool WindowManager::Init() { |
| CHECK(!root_) << "Init() may only be called once"; |
| root_ = xconn_->GetRootWindow(); |
| xconn_->SelectRandREventsOnWindow(root_); |
| xconn_->SelectInputOnWindow(root_, StructureNotifyMask, true); |
| XConnection::WindowGeometry root_geometry; |
| CHECK(xconn_->GetWindowGeometry(root_, &root_geometry)); |
| root_bounds_ = root_geometry.bounds; |
| root_depth_ = root_geometry.depth; |
| |
| if (FLAGS_unredirect_fullscreen_window) { |
| // Disable automatic background painting for the root window. It |
| // should never be visible, and the automatic updates cause flickering |
| // when enabling and disabling compositing. |
| xconn_->SetWindowBackgroundPixmap(root_, None); |
| } |
| |
| // Create the atom cache first; RegisterExistence() needs it. |
| atom_cache_.reset(new AtomCache(xconn_)); |
| |
| CHECK(RegisterExistence()); |
| SetEwmhGeneralProperties(); |
| SetEwmhSizeProperties(); |
| |
| // First make sure that we'll get notified if the login state changes |
| // and then query its current value. |
| CHECK(xconn_->SelectInputOnWindow(root_, PropertyChangeMask, true)); |
| int logged_in_value = 0; |
| xconn_->GetIntProperty( |
| root_, GetXAtom(ATOM_CHROME_LOGGED_IN), &logged_in_value); |
| logged_in_ = logged_in_value; |
| |
| stage_ = compositor_->GetDefaultStage(); |
| stage_xid_ = stage_->GetStageXWindow(); |
| stage_->SetName("stage"); |
| stage_->SetSize(root_bounds_.width, root_bounds_.height); |
| stage_->SetStageColor(Compositor::Color(FLAGS_background_color)); |
| stage_->Show(); |
| |
| wm_ipc_.reset(new WmIpc(xconn_, atom_cache_.get())); |
| |
| stacking_manager_.reset( |
| new StackingManager(xconn_, compositor_, atom_cache_.get())); |
| focus_manager_.reset(new FocusManager(this)); |
| |
| if (FLAGS_report_metrics) { |
| metrics_library_.reset(new MetricsLibrary); |
| metrics_library_->Init(); |
| } |
| |
| if (!logged_in_) |
| CreateStartupBackground(); |
| |
| // Draw the scene first to make sure that it's ready. |
| compositor_->ForceDraw(); |
| CHECK(xconn_->RedirectSubwindowsForCompositing(root_)); |
| // Create the compositing overlay, put the stage's window inside of it, |
| // and make events fall through both to the client windows underneath. |
| overlay_xid_ = xconn_->GetCompositingOverlayWindow(root_); |
| CHECK(overlay_xid_); |
| LOG(INFO) << "Reparenting stage window " << XidStr(stage_xid_) |
| << " into Xcomposite overlay window " << XidStr(overlay_xid_); |
| CHECK(xconn_->ReparentWindow(stage_xid_, overlay_xid_, Point())); |
| CHECK(xconn_->RemoveInputRegionFromWindow(overlay_xid_)); |
| CHECK(xconn_->RemoveInputRegionFromWindow(stage_xid_)); |
| |
| InitInformationalActors(); |
| |
| compositor_->RegisterCompositionChangeListener(this); |
| |
| key_bindings_.reset(new KeyBindings(xconn())); |
| key_bindings_actions_.reset( |
| new KeyBindingsActionRegistrar(key_bindings_.get())); |
| RegisterKeyBindings(); |
| |
| modality_handler_.reset(new ModalityHandler(this)); |
| event_consumers_.insert(modality_handler_.get()); |
| |
| SetLoggedInState(logged_in_, true); // initial=true |
| |
| screen_locker_handler_.reset(new ScreenLockerHandler(this)); |
| event_consumers_.insert(screen_locker_handler_.get()); |
| |
| chrome_watchdog_.reset(new ChromeWatchdog(this)); |
| event_consumers_.insert(chrome_watchdog_.get()); |
| |
| #ifndef TOUCH_UI |
| // Our X server has been modified to clear the cursor on the root window at |
| // startup. We set it back to the nice (Xcursor-loaded) arrow here so that |
| // other windows won't inherit the empty cursor (but note that if we're not |
| // logged in at this point, LoginController is also using XFixes to hide the |
| // cursor until the user moves the pointer). |
| XID cursor = xconn_->CreateShapedCursor(XC_left_ptr); |
| xconn_->SetWindowCursor(root_, cursor); |
| xconn_->FreeCursor(cursor); |
| #endif |
| |
| chrome_watchdog_timeout_id_ = |
| event_loop_->AddTimeout( |
| NewPermanentCallback(this, &WindowManager::PingChrome), |
| kPingChromeFrequencyMs, kPingChromeFrequencyMs); |
| |
| // Select window management events before we look up existing windows -- |
| // we want to make sure that we eventually hear about any resizes that we |
| // did while setting up existing windows so that the Window class will |
| // know that it needs to fetch new redirected pixmaps for them. |
| scoped_ptr<XConnection::ScopedServerGrab> grab( |
| xconn_->CreateScopedServerGrab()); |
| CHECK(xconn_->SelectInputOnWindow( |
| root_, |
| SubstructureRedirectMask | SubstructureNotifyMask, |
| true)); // preserve the existing event mask |
| ManageExistingWindows(); |
| grab.reset(); |
| |
| return true; |
| } |
| |
| void WindowManager::SetLoggedInState(bool logged_in, bool initial) { |
| if (!initial && logged_in_ && !logged_in) { |
| LOG(WARNING) << "Ignoring request to transition from logged-in to " |
| << "not-logged-in state"; |
| return; |
| } |
| |
| DLOG(INFO) << "User " << (logged_in ? "is" : "isn't") << " logged in"; |
| if (!initial && logged_in == logged_in_) |
| return; |
| |
| logged_in_ = logged_in; |
| |
| if (initialize_logging_) { |
| const string& log_dir = logged_in ? |
| FLAGS_logged_in_log_dir : |
| FLAGS_logged_out_log_dir; |
| |
| const string log_basename = StringPrintf( |
| "%s.%s", WindowManager::GetWmName(), |
| GetTimeAsString(GetCurrentTimeSec()).c_str()); |
| if (!file_util::CreateDirectory(FilePath(log_dir))) { |
| LOG(ERROR) << "Unable to create logging directory " << log_dir; |
| } else { |
| SetUpLogSymlink(StringPrintf("%s/%s.LATEST", |
| log_dir.c_str(), |
| WindowManager::GetWmName()), |
| log_basename); |
| } |
| |
| const string log_path = log_dir + "/" + log_basename; |
| LOG(INFO) << "Switching to log " << log_path; |
| logging::InitLogging( |
| log_path.c_str(), |
| logging::LOG_ONLY_TO_FILE, |
| logging::DONT_LOCK_LOG_FILE, |
| logging::APPEND_TO_OLD_LOG_FILE, |
| logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); |
| } |
| |
| if (logged_in_) { |
| DCHECK(!panel_manager_.get()); |
| panel_manager_.reset(new PanelManager(this)); |
| event_consumers_.insert(panel_manager_.get()); |
| panel_manager_->RegisterAreaChangeListener(this); |
| HandlePanelManagerAreaChange(); |
| |
| DCHECK(!layout_manager_.get()); |
| layout_manager_.reset(new LayoutManager2(this)); |
| layout_manager_->SetPanelAreaNotifier(panel_manager_.get()); |
| event_consumers_.insert(layout_manager_.get()); |
| |
| // We've probably already dropped the background containing the initial |
| // contents of the root window in response to the login background |
| // window being shown, but it doesn't hurt to be sure here. |
| DropStartupBackground(); |
| } else { |
| DCHECK(!login_controller_.get()); |
| login_controller_.reset(new LoginController(this)); |
| event_consumers_.insert(login_controller_.get()); |
| } |
| |
| FOR_EACH_EVENT_CONSUMER(event_consumers_, HandleLoggedInStateChange()); |
| } |
| |
| void WindowManager::ProcessPendingEvents() { |
| while (xconn_->IsEventPending()) { |
| XEvent event; |
| xconn_->GetNextEvent(&event); |
| HandleEvent(&event); |
| } |
| } |
| |
| void WindowManager::HandleEvent(XEvent* event) { |
| DCHECK(root_) << "Init() must be called before events can be handled"; |
| #ifdef DEBUG_EVENTS |
| if (event->type == xconn_->damage_event_base() + XDamageNotify) { |
| DLOG(INFO) << "Got DAMAGE" << " event (" << event->type << ")"; |
| } else { |
| DLOG(INFO) << "Got " << XEventTypeToName(event->type) |
| << " event (" << event->type << ") in window manager."; |
| } |
| #endif |
| static int damage_notify = xconn_->damage_event_base() + XDamageNotify; |
| static int shape_notify = xconn_->shape_event_base() + ShapeNotify; |
| static int randr_notify = xconn_->randr_event_base() + RRScreenChangeNotify; |
| static int sync_alarm_notify = xconn_->sync_event_base() + XSyncAlarmNotify; |
| |
| switch (event->type) { |
| case ButtonPress: |
| HandleButtonPress(event->xbutton); break; |
| case ButtonRelease: |
| HandleButtonRelease(event->xbutton); break; |
| case ClientMessage: |
| HandleClientMessage(event->xclient); break; |
| case ConfigureNotify: |
| HandleConfigureNotify(event->xconfigure); break; |
| case ConfigureRequest: |
| HandleConfigureRequest(event->xconfigurerequest); break; |
| case CreateNotify: |
| HandleCreateNotify(event->xcreatewindow); break; |
| case DestroyNotify: |
| HandleDestroyNotify(event->xdestroywindow); break; |
| case EnterNotify: |
| HandleEnterNotify(event->xcrossing); break; |
| case KeyPress: |
| HandleKeyPress(event->xkey); break; |
| case KeyRelease: |
| HandleKeyRelease(event->xkey); break; |
| case LeaveNotify: |
| HandleLeaveNotify(event->xcrossing); break; |
| case MapNotify: |
| HandleMapNotify(event->xmap); break; |
| case MapRequest: |
| HandleMapRequest(event->xmaprequest); break; |
| case MappingNotify: |
| HandleMappingNotify(event->xmapping); break; |
| case MotionNotify: |
| HandleMotionNotify(event->xmotion); break; |
| case PropertyNotify: |
| HandlePropertyNotify(event->xproperty); break; |
| case ReparentNotify: |
| HandleReparentNotify(event->xreparent); break; |
| case UnmapNotify: |
| HandleUnmapNotify(event->xunmap); break; |
| default: |
| if (event->type == damage_notify) { |
| HandleDamageNotify(*(reinterpret_cast<XDamageNotifyEvent*>(event))); |
| } else if (event->type == sync_alarm_notify) { |
| HandleSyncAlarmNotify( |
| *(reinterpret_cast<XSyncAlarmNotifyEvent*>(event))); |
| } else if (event->type == shape_notify) { |
| HandleShapeNotify(*(reinterpret_cast<XShapeEvent*>(event))); |
| } else if (event->type == randr_notify) { |
| HandleRRScreenChangeNotify( |
| *(reinterpret_cast<XRRScreenChangeNotifyEvent*>(event))); |
| } |
| } |
| } |
| |
| XWindow WindowManager::CreateInputWindow(const Rect& bounds, int event_mask) { |
| XWindow xid = xconn_->CreateWindow( |
| root_, // parent |
| bounds, |
| true, // override redirect |
| true, // input only |
| event_mask, |
| 0); // visual |
| CHECK(xid); |
| |
| // Since the stage has been reparented into the overlay window, we need |
| // to stack the input window under the overlay instead of under the stage |
| // (because the stage isn't a sibling of the input window). |
| CHECK(xconn_->StackWindow(xid, overlay_xid_, false)); |
| CHECK(xconn_->MapWindow(xid)); |
| return xid; |
| } |
| |
| bool WindowManager::ConfigureInputWindow(XWindow xid, const Rect& bounds) { |
| DLOG(INFO) << "Configuring input window " << XidStr(xid) << " to " << bounds; |
| return xconn_->ConfigureWindow(xid, bounds); |
| } |
| |
| XAtom WindowManager::GetXAtom(Atom atom) { |
| return atom_cache_->GetXAtom(atom); |
| } |
| |
| const string& WindowManager::GetXAtomName(XAtom xatom) { |
| return atom_cache_->GetName(xatom); |
| } |
| |
| XTime WindowManager::GetCurrentTimeFromServer() { |
| // Just set a bogus property on our window and wait for the |
| // PropertyNotify event so we can get its timestamp. |
| CHECK(xconn_->SetIntProperty( |
| wm_xid_, |
| GetXAtom(ATOM_CHROME_GET_SERVER_TIME), // atom |
| XA_ATOM, // type |
| GetXAtom(ATOM_CHROME_GET_SERVER_TIME))); // value |
| XTime timestamp = 0; |
| xconn_->WaitForPropertyChange(wm_xid_, ×tamp); |
| return timestamp; |
| } |
| |
| Window* WindowManager::GetWindow(XWindow xid) { |
| return FindWithDefault(client_windows_, xid, shared_ptr<Window>()).get(); |
| } |
| |
| Window* WindowManager::GetWindowOrDie(XWindow xid) { |
| Window* win = GetWindow(xid); |
| CHECK(win) << "Unable to find window " << XidStr(xid); |
| return win; |
| } |
| |
| Window* WindowManager::GetWindowOwningActor( |
| const Compositor::TexturePixmapActor& actor) { |
| for (WindowMap::const_iterator it = client_windows_.begin(); |
| it != client_windows_.end(); it++) { |
| if (it->second->actor() == &actor) |
| return it->second.get(); |
| } |
| return NULL; |
| } |
| |
| void WindowManager::FocusWindow(Window* win, XTime timestamp) { |
| focus_manager_->FocusWindow(win, timestamp); |
| } |
| |
| bool WindowManager::IsModalWindowFocused() const { |
| return modality_handler_->modal_window_is_focused(); |
| } |
| |
| void WindowManager::TakeFocus(XTime timestamp) { |
| if (layout_manager_.get() && layout_manager_->TakeFocus(timestamp)) |
| return; |
| if (panel_manager_.get() && panel_manager_->TakeFocus(timestamp)) |
| return; |
| focus_manager_->FocusWindow(NULL, timestamp); |
| } |
| |
| bool WindowManager::SetActiveWindowProperty(XWindow xid) { |
| if (active_window_xid_ == xid) |
| return true; |
| |
| DLOG(INFO) << "Setting active window to " << XidStr(xid); |
| if (!xconn_->SetIntProperty( |
| root_, GetXAtom(ATOM_NET_ACTIVE_WINDOW), XA_WINDOW, xid)) { |
| return false; |
| } |
| active_window_xid_ = xid; |
| return true; |
| } |
| |
| bool WindowManager::SetNamePropertiesForXid(XWindow xid, const string& name) { |
| bool success = xconn_->SetStringProperty( |
| xid, atom_cache_->GetXAtom(ATOM_WM_NAME), name); |
| success &= xconn_->SetStringProperty( |
| xid, atom_cache_->GetXAtom(ATOM_NET_WM_NAME), name); |
| return success; |
| } |
| |
| bool WindowManager::SetVideoTimeProperty(time_t video_time) { |
| // Rate-limit how often we'll update the property. |
| TimeTicks now = GetMonotonicTime(); |
| if (video_property_update_time_.is_null() || |
| (now - video_property_update_time_) > |
| TimeDelta::FromSeconds(kVideoTimePropertyUpdateSec)) { |
| video_property_update_time_ = now; |
| XAtom atom = atom_cache_->GetXAtom(ATOM_CHROME_VIDEO_TIME); |
| return xconn_->SetIntProperty( |
| root_, atom, atom, static_cast<int>(video_time)); |
| } |
| return true; |
| } |
| |
| void WindowManager::HandleWindowPixmapFetch(Window* win) { |
| DCHECK(win); |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, win->xid(), HandleWindowPixmapFetch(win)); |
| } |
| |
| void WindowManager::RegisterEventConsumerForWindowEvents( |
| XWindow xid, EventConsumer* event_consumer) { |
| DCHECK(event_consumer); |
| if (!window_event_consumers_[xid].insert(event_consumer).second) { |
| LOG(WARNING) << "Got request to register already-present window event " |
| << "consumer " << event_consumer << " for window " |
| << XidStr(xid); |
| } |
| } |
| |
| void WindowManager::UnregisterEventConsumerForWindowEvents( |
| XWindow xid, EventConsumer* event_consumer) { |
| DCHECK(event_consumer); |
| WindowEventConsumerMap::iterator it = window_event_consumers_.find(xid); |
| if (it == window_event_consumers_.end() || |
| it->second.erase(event_consumer) != 1) { |
| LOG(WARNING) << "Got request to unregister not-registered window event " |
| << "consumer " << event_consumer << " for window " |
| << XidStr(xid); |
| } else { |
| if (it->second.empty()) |
| window_event_consumers_.erase(it); |
| } |
| } |
| |
| void WindowManager::RegisterEventConsumerForPropertyChanges( |
| XWindow xid, XAtom xatom, EventConsumer* event_consumer) { |
| DCHECK(event_consumer); |
| if (!property_change_event_consumers_[make_pair(xid, xatom)].insert( |
| event_consumer).second) { |
| LOG(WARNING) << "Got request to register already-present window property " |
| << "listener " << event_consumer << " for window " |
| << XidStr(xid) << " and atom " << XidStr(xatom) << " (" |
| << GetXAtomName(xatom) << ")"; |
| } |
| } |
| |
| void WindowManager::UnregisterEventConsumerForPropertyChanges( |
| XWindow xid, XAtom xatom, EventConsumer* event_consumer) { |
| DCHECK(event_consumer); |
| PropertyChangeEventConsumerMap::iterator it = |
| property_change_event_consumers_.find(make_pair(xid, xatom)); |
| if (it == property_change_event_consumers_.end() || |
| it->second.erase(event_consumer) != 1) { |
| LOG(WARNING) << "Got request to unregister not-registered window property " |
| << "listener " << event_consumer << " for window " |
| << XidStr(xid) << " and atom " << XidStr(xatom) << " (" |
| << GetXAtomName(xatom) << ")"; |
| } else { |
| if (it->second.empty()) |
| property_change_event_consumers_.erase(it); |
| } |
| } |
| |
| void WindowManager::RegisterEventConsumerForChromeMessages( |
| WmIpcMessageType message_type, EventConsumer* event_consumer) { |
| DCHECK(event_consumer); |
| if (!chrome_message_event_consumers_[message_type].insert( |
| event_consumer).second) { |
| LOG(WARNING) << "Got request to register already-present Chrome message " |
| << "event consumer " << event_consumer << " for message type " |
| << message_type; |
| } |
| } |
| |
| void WindowManager::UnregisterEventConsumerForChromeMessages( |
| WmIpcMessageType message_type, EventConsumer* event_consumer) { |
| DCHECK(event_consumer); |
| ChromeMessageEventConsumerMap::iterator it = |
| chrome_message_event_consumers_.find(message_type); |
| if (it == chrome_message_event_consumers_.end() || |
| it->second.erase(event_consumer) != 1) { |
| LOG(WARNING) << "Got request to unregister not-registered Chrome message " |
| << "event consumer " << event_consumer << " for message type " |
| << message_type; |
| } else { |
| if (it->second.empty()) |
| chrome_message_event_consumers_.erase(it); |
| } |
| } |
| |
| void WindowManager::RegisterEventConsumerForDestroyedWindow( |
| XWindow xid, EventConsumer* event_consumer) { |
| DCHECK(xid); |
| DCHECK(event_consumer); |
| CHECK(destroyed_window_event_consumers_.insert( |
| make_pair(xid, event_consumer)).second) |
| << "Another EventConsumer already requested ownership of window " |
| << XidStr(xid) << " after it gets destroyed"; |
| } |
| |
| void WindowManager::UnregisterEventConsumerForDestroyedWindow( |
| XWindow xid, EventConsumer* event_consumer) { |
| hash_map<XWindow, EventConsumer*>::iterator it = |
| destroyed_window_event_consumers_.find(xid); |
| CHECK(it != destroyed_window_event_consumers_.end()) |
| << "No event consumer requested ownership of window " << XidStr(xid) |
| << " after it gets destroyed, but got a request to unregister " |
| << event_consumer; |
| CHECK(it->second == event_consumer) |
| << "Event consumer " << it->second << " requested ownership of window " |
| << XidStr(xid) << " after it gets destroyed, but got a request to " |
| << "unregister " << event_consumer; |
| destroyed_window_event_consumers_.erase(it); |
| } |
| |
| void WindowManager::RegisterSyncAlarm(XID alarm_id, Window* win) { |
| base::hash_map<XID, Window*>::const_iterator it = |
| sync_alarms_to_windows_.find(alarm_id); |
| DCHECK(it == sync_alarms_to_windows_.end()) |
| << "Registering sync alarm " << XidStr(alarm_id) << " for window " |
| << win->xid_str() << " while it's already being used by " |
| << it->second->xid_str(); |
| sync_alarms_to_windows_[alarm_id] = win; |
| } |
| |
| void WindowManager::UnregisterSyncAlarm(XID alarm_id) { |
| size_t num_erased = sync_alarms_to_windows_.erase(alarm_id); |
| DCHECK_EQ(num_erased, static_cast<size_t>(1)) |
| << "Tried to unregister unknown sync alarm " << XidStr(alarm_id); |
| } |
| |
| void WindowManager::ToggleClientWindowDebugging() { |
| if (client_window_debugging_enabled()) { |
| client_window_debugging_actors_.clear(); |
| return; |
| } |
| UpdateClientWindowDebugging(); |
| } |
| |
| void WindowManager::ToggleDamageDebugging() { |
| damage_debugging_enabled_ = !damage_debugging_enabled_; |
| } |
| |
| void WindowManager::ToggleProfiler() { |
| #if defined(PROFILE_BUILD) |
| Profiler* profiler = Singleton<Profiler>::get(); |
| if (profiler->status() == Profiler::STATUS_RUN) { |
| profiler->Pause(); |
| } else if (profiler->status() == Profiler::STATUS_SUSPEND) { |
| profiler->Resume(); |
| } |
| #endif |
| } |
| |
| void WindowManager::UpdateClientWindowDebugging() { |
| DLOG(INFO) << "Compositing actors:\n" << stage_->GetDebugString(0); |
| |
| ActorVector new_actors; |
| |
| vector<XWindow> xids; |
| if (!xconn_->GetChildWindows(root_, &xids)) |
| return; |
| |
| static const int kDebugFadeMs = 100; |
| |
| int cnt = 0; |
| float step = 6.f / xids.size(); |
| for (vector<XWindow>::iterator it = xids.begin(); it != xids.end(); ++it) { |
| Compositor::Color bg_color; |
| bg_color.SetHsv(cnt++ * step, 1.f, 1.f); |
| XConnection::WindowGeometry geometry; |
| if (!xconn_->GetWindowGeometry(*it, &geometry)) |
| continue; |
| |
| Compositor::ColoredBoxActor* rect = |
| compositor_->CreateColoredBox( |
| geometry.bounds.width, geometry.bounds.height, bg_color); |
| stage_->AddActor(rect); |
| stacking_manager_->StackActorAtTopOfLayer( |
| rect, StackingManager::LAYER_DEBUGGING); |
| rect->SetName("debug box"); |
| rect->Move(geometry.bounds.x, geometry.bounds.y, 0); |
| rect->SetOpacity(0, 0); |
| rect->SetOpacity(0.3, kDebugFadeMs); |
| rect->Show(); |
| |
| new_actors.push_back(shared_ptr<Compositor::Actor>(rect)); |
| } |
| |
| client_window_debugging_actors_.swap(new_actors); |
| } |
| |
| void WindowManager::DropStartupBackground() { |
| if (startup_background_.get()) { |
| startup_background_.reset(); |
| xconn_->FreePixmap(startup_pixmap_); |
| startup_pixmap_ = 0; |
| } |
| } |
| |
| int WindowManager::GetNumWindows() const { |
| int num_windows = 0; |
| if (panel_manager_.get()) |
| num_windows += panel_manager_->num_panels(); |
| if (layout_manager_.get()) |
| num_windows += layout_manager_->num_browser_windows(); |
| return num_windows; |
| } |
| |
| void WindowManager::DestroyLoginController() { |
| event_loop_->PostTask( |
| NewPermanentCallback( |
| this, &WindowManager::DestroyLoginControllerInternal)); |
| } |
| |
| void WindowManager::ReportUserAction(const string& action) { |
| if (metrics_library_.get()) |
| metrics_library_->SendUserActionToUMA(action); |
| } |
| |
| void WindowManager::ReportHistogramSample(const std::string& histogram_name, |
| int sample, |
| int min_value, |
| int max_value, |
| int num_buckets) { |
| if (metrics_library_.get()) |
| metrics_library_->SendToUMA( |
| histogram_name, sample, min_value, max_value, num_buckets); |
| } |
| |
| void WindowManager::IncrementCompositingRequests() { |
| num_compositing_requests_++; |
| if (num_compositing_requests_ == 1 && unredirected_fullscreen_xid_) |
| compositor_->ForceDraw(); |
| } |
| |
| void WindowManager::DecrementCompositingRequests() { |
| DCHECK_GE(num_compositing_requests_, 1); |
| num_compositing_requests_--; |
| } |
| |
| WindowManager::ScopedCompositingRequest* |
| WindowManager::CreateScopedCompositingRequest() { |
| return new ScopedCompositingRequest(this); |
| } |
| |
| bool WindowManager::IsSessionEnding() const { |
| if (!screen_locker_handler_.get()) |
| return false; |
| return screen_locker_handler_->session_ending(); |
| } |
| |
| bool WindowManager::GetManagerSelection( |
| XAtom atom, XWindow manager_win, XTime timestamp) { |
| // Find the current owner of the selection and select events on it so |
| // we'll know when it's gone away. |
| XWindow current_manager = xconn_->GetSelectionOwner(atom); |
| if (current_manager) |
| xconn_->SelectInputOnWindow(current_manager, StructureNotifyMask, false); |
| |
| // Take ownership of the selection. |
| CHECK(xconn_->SetSelectionOwner(atom, manager_win, timestamp)); |
| if (xconn_->GetSelectionOwner(atom) != manager_win) { |
| LOG(WARNING) << "Couldn't take ownership of " |
| << GetXAtomName(atom) << " selection"; |
| return false; |
| } |
| |
| // Announce that we're here. |
| long data[5]; |
| memset(data, 0, sizeof(data)); |
| data[0] = timestamp; |
| data[1] = atom; |
| data[2] = manager_win; |
| CHECK(xconn_->SendClientMessageEvent( |
| root_, root_, GetXAtom(ATOM_MANAGER), data, StructureNotifyMask)); |
| |
| // If there was an old manager running, wait for its window to go away. |
| if (current_manager) |
| CHECK(xconn_->WaitForWindowToBeDestroyed(current_manager)); |
| |
| return true; |
| } |
| |
| bool WindowManager::RegisterExistence() { |
| // Create an offscreen window to take ownership of the selection and |
| // receive properties. |
| wm_xid_ = xconn_->CreateWindow(root_, // parent |
| Rect(-1, -1, 1, 1), |
| true, // override redirect |
| false, // input only |
| PropertyChangeMask, // event mask |
| 0); // visual |
| CHECK(wm_xid_); |
| LOG(INFO) << "Created window " << XidStr(wm_xid_) |
| << " for registering ourselves as the window manager"; |
| wm_xid_destroyer_.reset(new XConnection::WindowDestroyer(xconn_, wm_xid_)); |
| |
| // Set the window's title and wait for the notify event so we can get a |
| // timestamp from the server. |
| CHECK(SetNamePropertiesForXid(wm_xid_, GetWmName())); |
| XTime timestamp = 0; |
| xconn_->WaitForPropertyChange(wm_xid_, ×tamp); |
| |
| if (!GetManagerSelection(GetXAtom(ATOM_WM_S0), wm_xid_, timestamp) || |
| !GetManagerSelection(GetXAtom(ATOM_NET_WM_CM_S0), wm_xid_, timestamp)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool WindowManager::SetEwmhGeneralProperties() { |
| bool success = true; |
| |
| success &= xconn_->SetIntProperty( |
| root_, GetXAtom(ATOM_NET_NUMBER_OF_DESKTOPS), XA_CARDINAL, 1); |
| success &= xconn_->SetIntProperty( |
| root_, GetXAtom(ATOM_NET_CURRENT_DESKTOP), XA_CARDINAL, 0); |
| |
| // Let clients know that we're the current WM and that we at least |
| // partially conform to EWMH. |
| XAtom check_atom = GetXAtom(ATOM_NET_SUPPORTING_WM_CHECK); |
| success &= xconn_->SetIntProperty(root_, check_atom, XA_WINDOW, wm_xid_); |
| success &= xconn_->SetIntProperty(wm_xid_, check_atom, XA_WINDOW, wm_xid_); |
| |
| // State which parts of EWMH we support. |
| vector<int> supported; |
| supported.push_back(GetXAtom(ATOM_NET_ACTIVE_WINDOW)); |
| supported.push_back(GetXAtom(ATOM_NET_CLIENT_LIST)); |
| supported.push_back(GetXAtom(ATOM_NET_CLIENT_LIST_STACKING)); |
| supported.push_back(GetXAtom(ATOM_NET_CURRENT_DESKTOP)); |
| supported.push_back(GetXAtom(ATOM_NET_DESKTOP_GEOMETRY)); |
| supported.push_back(GetXAtom(ATOM_NET_DESKTOP_VIEWPORT)); |
| supported.push_back(GetXAtom(ATOM_NET_NUMBER_OF_DESKTOPS)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_MOVERESIZE)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_NAME)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_STATE)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_STATE_FULLSCREEN)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_STATE_MODAL)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_SYNC_REQUEST)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_SYNC_REQUEST_COUNTER)); |
| supported.push_back(GetXAtom(ATOM_NET_WM_WINDOW_OPACITY)); |
| supported.push_back(GetXAtom(ATOM_NET_WORKAREA)); |
| success &= xconn_->SetIntArrayProperty( |
| root_, GetXAtom(ATOM_NET_SUPPORTED), XA_ATOM, supported); |
| |
| return success; |
| } |
| |
| bool WindowManager::SetEwmhSizeProperties() { |
| bool success = true; |
| |
| // We don't use pseudo-large desktops, so this is just the screen size. |
| vector<int> geometry; |
| geometry.push_back(root_bounds_.width); |
| geometry.push_back(root_bounds_.height); |
| success &= xconn_->SetIntArrayProperty( |
| root_, GetXAtom(ATOM_NET_DESKTOP_GEOMETRY), XA_CARDINAL, geometry); |
| |
| // The viewport (top-left corner of the desktop) is just (0, 0) for us. |
| vector<int> viewport(2, 0); |
| success &= xconn_->SetIntArrayProperty( |
| root_, GetXAtom(ATOM_NET_DESKTOP_VIEWPORT), XA_CARDINAL, viewport); |
| |
| success &= SetEwmhWorkareaProperty(); |
| |
| return success; |
| } |
| |
| bool WindowManager::SetEwmhWorkareaProperty() { |
| // _NET_WORKAREA describes the region of the screen "minus space occupied |
| // by dock and panel windows", so we subtract out the space used by panel |
| // docks. :-P Chrome can use this to guess the initial size for new |
| // windows. |
| int panel_manager_left_width = 0, panel_manager_right_width = 0; |
| if (panel_manager_.get()) { |
| // We invoke this before the panel manager has been created to try to |
| // get the hints set as soon as possible. |
| panel_manager_->GetArea(&panel_manager_left_width, |
| &panel_manager_right_width); |
| } |
| vector<int> workarea; |
| workarea.push_back(panel_manager_left_width); // x |
| workarea.push_back(0); // y |
| workarea.push_back( |
| root_bounds_.width - |
| panel_manager_left_width - |
| panel_manager_right_width); |
| workarea.push_back(root_bounds_.height); |
| return xconn_->SetIntArrayProperty( |
| root_, GetXAtom(ATOM_NET_WORKAREA), XA_CARDINAL, workarea); |
| } |
| |
| void WindowManager::RegisterKeyBindings() { |
| #ifndef NDEBUG |
| static const uint32_t kCtrlAltShiftMask = |
| KeyBindings::kControlMask | |
| KeyBindings::kAltMask | |
| KeyBindings::kShiftMask; |
| |
| key_bindings_actions_->AddAction( |
| kToggleClientWindowDebuggingAction, |
| NewPermanentCallback(this, &WindowManager::ToggleClientWindowDebugging), |
| NULL, NULL); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo(XK_w, kCtrlAltShiftMask), |
| kToggleClientWindowDebuggingAction); |
| |
| key_bindings_actions_->AddAction( |
| kToggleDamageDebuggingAction, |
| NewPermanentCallback(this, &WindowManager::ToggleDamageDebugging), |
| NULL, NULL); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo(XK_d, kCtrlAltShiftMask), |
| kToggleDamageDebuggingAction); |
| |
| key_bindings_actions_->AddAction( |
| kToggleProfilerAction, |
| NewPermanentCallback(this, &WindowManager::ToggleProfiler), |
| NULL, NULL); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo(XK_p, kCtrlAltShiftMask), |
| kToggleProfilerAction); |
| #endif |
| |
| key_bindings_actions_->AddAction( |
| kTakeRootScreenshotAction, |
| NewPermanentCallback(this, &WindowManager::TakeScreenshot, false), |
| NULL, NULL); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo(XK_F5, KeyBindings::kControlMask), |
| kTakeRootScreenshotAction); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo(XK_Print, 0), kTakeRootScreenshotAction); |
| |
| key_bindings_actions_->AddAction( |
| kTakeRegionScreenshotAction, |
| NewPermanentCallback(this, &WindowManager::TakeScreenshot, true), |
| NULL, NULL); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo( |
| XK_F5, KeyBindings::kControlMask | KeyBindings::kShiftMask), |
| kTakeRegionScreenshotAction); |
| key_bindings_->AddBinding( |
| KeyBindings::KeyCombo(XK_Print, KeyBindings::kShiftMask), |
| kTakeRegionScreenshotAction); |
| } |
| |
| bool WindowManager::ManageExistingWindows() { |
| vector<XWindow> windows; |
| if (!xconn_->GetChildWindows(root_, &windows)) |
| return false; |
| |
| // Snapshot and panel content windows that are already mapped. We defer |
| // calling HandleMappedWindow() on these until we've handled all other |
| // windows to make sure that we handle the corresponding panel titlebar |
| // windows and Chrome toplevel windows first -- the panel code requires |
| // that the titlebars be mapped before the content windows, and the |
| // snapshot code requires that the toplevel windows are mapped before |
| // their snapshots. |
| vector<Window*> first_deferred_mapped_windows; |
| vector<Window*> second_deferred_mapped_windows; |
| |
| LOG(INFO) << "Taking ownership of " << windows.size() << " window" |
| << (windows.size() == 1 ? "" : "s"); |
| for (size_t i = 0; i < windows.size(); ++i) { |
| XWindow xid = windows[i]; |
| XConnection::WindowAttributes attr; |
| XConnection::WindowGeometry geometry; |
| if (!xconn_->GetWindowAttributes(xid, &attr) || |
| !xconn_->GetWindowGeometry(xid, &geometry)) |
| continue; |
| |
| // XQueryTree() returns child windows in bottom-to-top stacking order. |
| stacked_xids_->AddOnTop(xid); |
| Window* win = TrackWindow(xid, attr.override_redirect, geometry); |
| if (win && win->FetchMapState()) { |
| if (win->type() == chromeos::WM_IPC_WINDOW_CHROME_PANEL_CONTENT || |
| win->type() == chromeos::WM_IPC_WINDOW_CHROME_TAB_SNAPSHOT) { |
| first_deferred_mapped_windows.push_back(win); |
| } else if (win->type() == chromeos::WM_IPC_WINDOW_CHROME_TAB_TITLE || |
| win->type() == chromeos::WM_IPC_WINDOW_CHROME_TAB_FAV_ICON) { |
| second_deferred_mapped_windows.push_back(win); |
| } else { |
| HandleMappedWindow(win); |
| } |
| } |
| } |
| |
| for (vector<Window*>::iterator it = first_deferred_mapped_windows.begin(); |
| it != first_deferred_mapped_windows.end(); ++it) { |
| HandleMappedWindow(*it); |
| } |
| |
| for (vector<Window*>::iterator it = second_deferred_mapped_windows.begin(); |
| it != second_deferred_mapped_windows.end(); ++it) { |
| HandleMappedWindow(*it); |
| } |
| |
| UpdateClientListStackingProperty(); |
| return true; |
| } |
| |
| Window* WindowManager::TrackWindow(XWindow xid, |
| bool override_redirect, |
| XConnection::WindowGeometry& geometry) { |
| // Don't manage our internal windows. |
| if (IsInternalWindow(xid) || stacking_manager_->IsInternalWindow(xid)) |
| return NULL; |
| for (set<EventConsumer*>::const_iterator it = event_consumers_.begin(); |
| it != event_consumers_.end(); ++it) { |
| if ((*it)->IsInputWindow(xid)) |
| return NULL; |
| } |
| |
| // We don't care about InputOnly windows either. |
| // TODO: Don't call GetWindowAttributes() so many times; we call in it |
| // Window's c'tor as well. |
| XConnection::WindowAttributes attr; |
| if (xconn_->GetWindowAttributes(xid, &attr) && |
| attr.window_class == |
| XConnection::WindowAttributes::WINDOW_CLASS_INPUT_ONLY) |
| return NULL; |
| |
| DLOG(INFO) << "Managing window " << XidStr(xid); |
| Window* win = GetWindow(xid); |
| if (win) { |
| LOG(WARNING) << "Window " << XidStr(xid) << " is already being managed"; |
| } else { |
| shared_ptr<Window> win_ref( |
| new Window(this, xid, override_redirect, geometry)); |
| client_windows_.insert(make_pair(xid, win_ref)); |
| win = win_ref.get(); |
| } |
| return win; |
| } |
| |
| void WindowManager::HandleMappedWindow(Window* win) { |
| CHECK(win); |
| CHECK(!win->mapped()) << "Window " << win->xid_str() << " is already mapped"; |
| |
| win->HandleMapNotify(); |
| FOR_EACH_EVENT_CONSUMER(event_consumers_, HandleWindowMap(win)); |
| |
| if (win->override_redirect()) { |
| if (!win->is_rgba()) { |
| // Check if this window has a menu hint; if so, display a shadow under it. |
| const static XAtom combo_xatom = GetXAtom(ATOM_NET_WM_WINDOW_TYPE_COMBO); |
| const static XAtom dropdown_xatom = |
| GetXAtom(ATOM_NET_WM_WINDOW_TYPE_DROPDOWN_MENU); |
| const static XAtom menu_xatom = GetXAtom(ATOM_NET_WM_WINDOW_TYPE_MENU); |
| const static XAtom popup_xatom = |
| GetXAtom(ATOM_NET_WM_WINDOW_TYPE_POPUP_MENU); |
| for (vector<XAtom>::const_iterator it = |
| win->wm_window_type_xatoms().begin(); |
| it != win->wm_window_type_xatoms().end(); ++it) { |
| if (*it == combo_xatom || *it == dropdown_xatom || *it == menu_xatom || |
| *it == popup_xatom) { |
| win->SetShadowType(Shadow::TYPE_RECTANGULAR); |
| break; |
| } |
| } |
| } |
| return; |
| } |
| |
| if (mapped_xids_->Contains(win->xid())) { |
| LOG(WARNING) << "Handling mapped window " << win->xid_str() |
| << ", which is already listed in |mapped_xids_|"; |
| } else { |
| mapped_xids_->AddOnTop(win->xid()); |
| UpdateClientListProperty(); |
| // This only includes mapped windows, so we need to update it now. |
| UpdateClientListStackingProperty(); |
| } |
| |
| SetWmStateProperty(win->xid(), 1); // NormalState |
| } |
| |
| void WindowManager::HandleUnmappedWindow(Window* win) { |
| CHECK(win); |
| CHECK(win->mapped()) << "Window " << win->xid_str() << " isn't mapped"; |
| |
| win->HandleUnmapNotify(); |
| FOR_EACH_EVENT_CONSUMER(event_consumers_, HandleWindowUnmap(win)); |
| |
| // Notify the focus manager last in case any event consumers need to do |
| // something special when they see the focused window getting unmapped. |
| focus_manager_->HandleWindowUnmap(win); |
| |
| if (!win->override_redirect()) { |
| SetWmStateProperty(win->xid(), 0); // WithdrawnState |
| |
| if (mapped_xids_->Contains(win->xid())) { |
| mapped_xids_->Remove(win->xid()); |
| UpdateClientListProperty(); |
| UpdateClientListStackingProperty(); |
| } |
| } |
| } |
| |
| void WindowManager::HandleScreenResize(const Size& new_size) { |
| // We move windows offscreen to prevent them from receiving input. |
| // Check that the screen doesn't get so huge that they end up onscreen. |
| DCHECK_LE(new_size.width, Window::kOffscreenX); |
| DCHECK_LE(new_size.height, Window::kOffscreenY); |
| |
| // The window manager sometimes tries to fetch an updated |
| // pixmap for a resized window while the window is unredirected, resulting |
| // in a black screen until compositing is toggled on and off again. |
| // To avoid this, disable unredirection while resizing. |
| scoped_ptr<ScopedCompositingRequest> comp_request( |
| CreateScopedCompositingRequest()); |
| |
| #ifdef BOGUS_SCREEN_RESIZES |
| // If the screen doesn't actually get resized and we instead just receive a |
| // synthetic ConfigureNotify event about the root, then in the |
| // screen-getting-smaller case, we'll end up with a region of the overlay |
| // window that we're no longer painting. Clear the stage first to make sure |
| // that we're not showing stale junk in the region after we resize the stage. |
| if (new_size.width < root_bounds_.width || |
| new_size.height < root_bounds_.height) { |
| unordered_set<int> old_groups; |
| compositor_->GetActiveVisibilityGroups(&old_groups); |
| compositor_->SetActiveVisibilityGroup(VISIBILITY_GROUP_BLANK); |
| compositor_->ForceDraw(); |
| compositor_->SetActiveVisibilityGroups(old_groups); |
| } |
| #endif |
| |
| root_bounds_.resize(new_size, GRAVITY_NORTHWEST); |
| SetEwmhSizeProperties(); |
| stage_->SetSize(root_bounds_.width, root_bounds_.height); |
| FOR_EACH_EVENT_CONSUMER(event_consumers_, HandleScreenResize()); |
| } |
| |
| bool WindowManager::SetWmStateProperty(XWindow xid, int state) { |
| vector<int> values; |
| values.push_back(state); |
| values.push_back(0); // we don't use icons |
| XAtom xatom = GetXAtom(ATOM_WM_STATE); |
| return xconn_->SetIntArrayProperty(xid, xatom, xatom, values); |
| } |
| |
| bool WindowManager::UpdateClientListProperty() { |
| vector<int> values; |
| const list<XWindow>& xids = mapped_xids_->items(); |
| // We store windows in most-to-least-recently-mapped order, but |
| // _NET_CLIENT_LIST is least-to-most-recently-mapped. |
| for (list<XWindow>::const_reverse_iterator it = xids.rbegin(); |
| it != xids.rend(); ++it) { |
| const Window* win = GetWindow(*it); |
| if (!win || !win->mapped() || win->override_redirect()) { |
| LOG(WARNING) << "Skipping " |
| << (!win ? "missing" : |
| (!win->mapped() ? "unmapped" : "override-redirect")) |
| << " window " << XidStr(*it) |
| << " when updating _NET_CLIENT_LIST"; |
| } else { |
| values.push_back(*it); |
| } |
| } |
| if (!values.empty()) { |
| return xconn_->SetIntArrayProperty( |
| root_, GetXAtom(ATOM_NET_CLIENT_LIST), XA_WINDOW, values); |
| } else { |
| return xconn_->DeletePropertyIfExists( |
| root_, GetXAtom(ATOM_NET_CLIENT_LIST)); |
| } |
| } |
| |
| bool WindowManager::UpdateClientListStackingProperty() { |
| vector<int> values; |
| const list<XWindow>& xids = stacked_xids_->items(); |
| // We store windows in top-to-bottom stacking order, but |
| // _NET_CLIENT_LIST_STACKING is bottom-to-top. |
| for (list<XWindow>::const_reverse_iterator it = xids.rbegin(); |
| it != xids.rend(); ++it) { |
| const Window* win = GetWindow(*it); |
| if (win && win->mapped() && !win->override_redirect()) |
| values.push_back(*it); |
| } |
| if (!values.empty()) { |
| return xconn_->SetIntArrayProperty( |
| root_, GetXAtom(ATOM_NET_CLIENT_LIST_STACKING), XA_WINDOW, values); |
| } else { |
| return xconn_->DeletePropertyIfExists( |
| root_, GetXAtom(ATOM_NET_CLIENT_LIST_STACKING)); |
| } |
| } |
| |
| void WindowManager::HandleButtonPress(const XButtonEvent& e) { |
| // Scrollwheel events can be bursty; don't log them. |
| if (e.button <= 3) { |
| DLOG(INFO) << "Handling button press in window " << XidStr(e.window) |
| << " at relative (" << e.x << ", " << e.y << "), absolute (" |
| << e.x_root << ", " << e.y_root << ") with button " << e.button; |
| } |
| |
| Window* win = GetWindow(e.window); |
| if (win) |
| focus_manager_->HandleButtonPressInWindow(win, e.time); |
| |
| const Point relative_pos(e.x, e.y); |
| const Point absolute_pos(e.x_root, e.y_root); |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandleButtonPress( |
| e.window, relative_pos, absolute_pos, e.button, e.time)); |
| } |
| |
| void WindowManager::HandleButtonRelease(const XButtonEvent& e) { |
| // Scrollwheel events can be bursty; don't log them. |
| if (e.button <= 3) { |
| DLOG(INFO) << "Handling button release in window " << XidStr(e.window) |
| << " at relative (" << e.x << ", " << e.y << "), absolute (" |
| << e.x_root << ", " << e.y_root << ") with button " << e.button; |
| } |
| |
| const Point relative_pos(e.x, e.y); |
| const Point absolute_pos(e.x_root, e.y_root); |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandleButtonRelease( |
| e.window, relative_pos, absolute_pos, e.button, e.time)); |
| } |
| |
| void WindowManager::HandleClientMessage(const XClientMessageEvent& e) { |
| // _NET_WM_PING responses are spammy; don't log them. |
| if (!(e.message_type == GetXAtom(ATOM_WM_PROTOCOLS) && |
| e.format == XConnection::kLongFormat && |
| static_cast<XAtom>(e.data.l[0]) == GetXAtom(ATOM_NET_WM_PING))) { |
| DLOG(INFO) << "Handling client message for window " << XidStr(e.window) |
| << " with type " << XidStr(e.message_type) << " (" |
| << GetXAtomName(e.message_type) << ") and format " << e.format; |
| } |
| |
| WmIpc::Message msg; |
| if (wm_ipc_->GetMessage(e.window, e.message_type, e.format, e.data.l, &msg)) { |
| if (msg.type() == chromeos::WM_IPC_MESSAGE_WM_NOTIFY_IPC_VERSION) { |
| wm_ipc_version_ = msg.param(0); |
| LOG(INFO) << "Got WM_NOTIFY_IPC_VERSION message saying that Chrome is " |
| << "using version " << wm_ipc_version_; |
| } else { |
| DLOG(INFO) << "Decoded " << chromeos::WmIpcMessageTypeToString(msg.type()) |
| << " message"; |
| FOR_EACH_INTERESTED_EVENT_CONSUMER(chrome_message_event_consumers_, |
| msg.type(), |
| HandleChromeMessage(msg)); |
| } |
| } else { |
| if (static_cast<XAtom>(e.message_type) == GetXAtom(ATOM_MANAGER) && |
| e.format == XConnection::kLongFormat && |
| (static_cast<XAtom>(e.data.l[1]) == GetXAtom(ATOM_WM_S0) || |
| static_cast<XAtom>(e.data.l[1]) == GetXAtom(ATOM_NET_WM_CM_S0))) { |
| if (static_cast<XWindow>(e.data.l[2]) != wm_xid_) { |
| LOG(WARNING) << "Ignoring client message saying that window " |
| << XidStr(e.data.l[2]) << " got the " |
| << GetXAtomName(e.data.l[1]) << " manager selection"; |
| } |
| return; |
| } |
| if (e.format == XConnection::kLongFormat) { |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandleClientMessage(e.window, e.message_type, e.data.l)); |
| } else { |
| LOG(WARNING) << "Ignoring client message event with unsupported format " |
| << e.format << " (we only handle 32-bit data currently)"; |
| } |
| } |
| } |
| |
| void WindowManager::HandleConfigureNotify(const XConfigureEvent& e) { |
| const Rect bounds(e.x, e.y, e.width, e.height); |
| |
| if (e.window == root_ && bounds.size() != root_bounds_.size()) { |
| DLOG(INFO) << "Got configure notify saying that root window has been " |
| << "resized to " << bounds.size(); |
| HandleScreenResize(bounds.size()); |
| return; |
| } |
| |
| // Even though _NET_CLIENT_LIST_STACKING only contains client windows |
| // that we're managing, we also need to keep track of other (e.g. |
| // override-redirect, or even untracked) windows: we receive |
| // notifications of the form "X is on top of Y", so we need to know where |
| // Y is even if we're not managing it so that we can stack X correctly. |
| |
| // If this isn't an immediate child of the root window, ignore the |
| // ConfigureNotify. |
| if (!stacked_xids_->Contains(e.window)) |
| return; |
| |
| // Did the window get restacked from its previous position in |
| // |stacked_xids_|? |
| bool restacked = false; |
| |
| // Check whether the stacking order changed. |
| const XWindow* prev_above = stacked_xids_->GetUnder(e.window); |
| // If we're on the bottom but weren't previously, or aren't on the bottom |
| // now but previously were or are now above a different sibling, update |
| // the order. |
| if ((!e.above && prev_above != NULL) || |
| (e.above && (prev_above == NULL || *prev_above != e.above))) { |
| restacked = true; |
| stacked_xids_->Remove(e.window); |
| |
| if (e.above && stacked_xids_->Contains(e.above)) { |
| stacked_xids_->AddAbove(e.window, e.above); |
| } else { |
| // |above| being unset means that the window is stacked beneath its |
| // siblings. |
| if (e.above) { |
| LOG(WARNING) << "ConfigureNotify for " << XidStr(e.window) |
| << " said that it's stacked above " << XidStr(e.above) |
| << ", which we don't know about"; |
| } |
| stacked_xids_->AddOnBottom(e.window); |
| } |
| } |
| |
| Window* win = GetWindow(e.window); |
| if (!win) |
| return; |
| DLOG(INFO) << "Handling configure notify for " << win->xid_str() |
| << " to " << bounds << ", above " << XidStr(e.above); |
| |
| // Notify the window so it can reset its pixmap if the size changed, or update |
| // its actor's position and stacking if it's an override-redirect window. |
| win->HandleConfigureNotify(bounds, e.above); |
| |
| if (!win->override_redirect() && restacked) { |
| // _NET_CLIENT_LIST_STACKING only includes managed (i.e. |
| // non-override-redirect) windows, so we only update it when a |
| // managed window's stacking position changed. |
| UpdateClientListStackingProperty(); |
| } |
| } |
| |
| void WindowManager::HandleConfigureRequest(const XConfigureRequestEvent& e) { |
| Window* win = GetWindow(e.window); |
| if (!win) |
| return; |
| |
| DLOG(INFO) |
| << "Handling configure request for " << XidStr(e.window) |
| << " to pos (" |
| << ((e.value_mask & CWX) ? StringPrintf("%d", e.x) : string("undef")) |
| << ", " |
| << ((e.value_mask & CWY) ? StringPrintf("%d", e.y) : string("undef")) |
| << ") and size " |
| << ((e.value_mask & CWWidth) ? |
| StringPrintf("%d", e.width) : string("undef ")) |
| << "x" |
| << ((e.value_mask & CWHeight) ? |
| StringPrintf("%d", e.height) : string(" undef")); |
| if (win->override_redirect()) { |
| LOG(WARNING) << "Huh? Got a ConfigureRequest event for override-redirect " |
| << "window " << win->xid_str(); |
| } |
| |
| const Rect requested_bounds( |
| (e.value_mask & CWX) ? e.x : win->client_x(), |
| (e.value_mask & CWY) ? e.y : win->client_y(), |
| (e.value_mask & CWWidth) ? e.width : win->client_width(), |
| (e.value_mask & CWHeight) ? e.height : win->client_height()); |
| |
| // The X server should reject bogus requests before they get to us, but |
| // just in case... |
| if (requested_bounds.width <= 0 || requested_bounds.height <= 0) { |
| LOG(WARNING) << "Ignoring request to resize window " << win->xid_str() |
| << " to " << requested_bounds.size(); |
| return; |
| } |
| |
| if (!win->mapped()) { |
| // If the window is unmapped, it's unlikely that any event consumers |
| // will know what to do with it. Do whatever we were asked to do. |
| if (win->client_bounds() != requested_bounds) { |
| if (win->visibility() != Window::VISIBILITY_UNSET) { |
| // Previously-mapped windows may have their visibility set -- see |
| // http://crosbug.com/17376. |
| win->SetBounds(requested_bounds, 0); |
| } else { |
| if (requested_bounds.position() != win->client_origin()) { |
| win->MoveClient(requested_bounds.x, requested_bounds.y); |
| win->MoveComposited(requested_bounds.x, requested_bounds.y, 0); |
| } |
| if (requested_bounds.size() != win->client_size()) |
| win->Resize(requested_bounds.size(), GRAVITY_NORTHWEST); |
| } |
| } |
| } else { |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandleWindowConfigureRequest(win, requested_bounds)); |
| } |
| } |
| |
| void WindowManager::HandleCreateNotify(const XCreateWindowEvent& e) { |
| if (GetWindow(e.window)) { |
| LOG(WARNING) << "Ignoring create notify for already-known window " |
| << XidStr(e.window); |
| return; |
| } |
| |
| DLOG(INFO) << "Handling create notify for " |
| << (e.override_redirect ? "override-redirect" : "normal") |
| << " window " << XidStr(e.window) << " at (" << e.x << ", " << e.y |
| << ") with size " << e.width << "x" << e.height; |
| |
| // Don't bother doing anything else for windows which aren't direct |
| // children of the root window. |
| if (e.parent != root_) |
| return; |
| |
| // Grab the server while we're doing all of this; we can get a ton of |
| // errors when trying to track short-lived windows otherwise. |
| scoped_ptr<XConnection::ScopedServerGrab> grab( |
| xconn_->CreateScopedServerGrab()); |
| |
| // We want to pass the geometry from the CreateNotify event to Window's |
| // constructor, but we also need to include the window's depth, which sadly |
| // isn't included in CreateNotify events. Query the geometry again here to |
| // get the depth (and also to check that the window still exists). |
| XConnection::WindowGeometry new_geometry; |
| if (!xconn_->GetWindowGeometry(e.window, &new_geometry)) { |
| LOG(WARNING) << "Window " << XidStr(e.window) |
| << " went away while we were handling its CreateNotify event"; |
| return; |
| } |
| |
| // CreateWindow stacks the new window on top of its siblings. |
| DCHECK(!stacked_xids_->Contains(e.window)); |
| stacked_xids_->AddOnTop(e.window); |
| |
| XConnection::WindowGeometry geometry; |
| geometry.bounds.reset(e.x, e.y, e.width, e.height); |
| geometry.border_width = e.border_width; |
| geometry.depth = new_geometry.depth; |
| |
| // override-redirect means that the window manager isn't going to |
| // intercept this window's structure events, but we still need to |
| // composite the window, so we'll create a Window object for it |
| // regardless. |
| TrackWindow(e.window, e.override_redirect, geometry); |
| } |
| |
| void WindowManager::HandleDamageNotify(const XDamageNotifyEvent& e) { |
| Window* win = GetWindow(e.drawable); |
| if (!win) |
| return; |
| win->HandleDamageNotify( |
| Rect(e.area.x, e.area.y, e.area.width, e.area.height)); |
| } |
| |
| void WindowManager::HandleDestroyNotify(const XDestroyWindowEvent& e) { |
| DLOG(INFO) << "Handling destroy notify for " << XidStr(e.window); |
| |
| Window* win = GetWindow(e.window); |
| |
| // Horrible things will happen if we never got an UnmapNotify about the |
| // window, since EventConsumers will still be holding references to it. |
| // This shouldn't happen, but seems like it might sometimes |
| // (http://crosbug.com/13792), so try to handle it as well as we can. |
| if (win && win->mapped()) { |
| LOG(WARNING) << "Got destroy notify for window " << win->xid_str() |
| << " while it's still mapped"; |
| HandleUnmappedWindow(win); |
| } |
| |
| DCHECK(window_event_consumers_.find(e.window) == |
| window_event_consumers_.end()) |
| << "One or more event consumers are still registered for destroyed " |
| << "window " << XidStr(e.window); |
| |
| if (stacked_xids_->Contains(e.window)) |
| stacked_xids_->Remove(e.window); |
| |
| // Don't bother doing anything else for windows which aren't direct |
| // children of the root window. |
| if (!win) |
| return; |
| |
| if (!win->override_redirect()) |
| UpdateClientListStackingProperty(); |
| |
| hash_map<XWindow, EventConsumer*>::iterator ec_it = |
| destroyed_window_event_consumers_.find(e.window); |
| if (ec_it != destroyed_window_event_consumers_.end()) { |
| // Transfer ownership of the window's compositing-related resources to |
| // the event consumer that wanted it. |
| DestroyedWindow* destroyed_win = win->HandleDestroyNotify(); |
| ec_it->second->OwnDestroyedWindow(destroyed_win, e.window); |
| destroyed_window_event_consumers_.erase(ec_it); |
| } |
| |
| client_windows_.erase(e.window); |
| win = NULL; // Erasing from |client_windows_| deletes |window|. |
| } |
| |
| void WindowManager::HandleEnterNotify(const XEnterWindowEvent& e) { |
| DLOG(INFO) << "Handling enter notify for " << XidStr(e.window); |
| const Point relative_pos(e.x, e.y); |
| const Point absolute_pos(e.x_root, e.y_root); |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandlePointerEnter(e.window, relative_pos, absolute_pos, e.time)); |
| } |
| |
| void WindowManager::HandleKeyPress(const XKeyEvent& e) { |
| // We grab the keyboard while shutting down or signing out; ignore any events |
| // that we get. |
| if (IsSessionEnding()) |
| return; |
| key_bindings_->HandleKeyPress(e.keycode, e.state, e.time); |
| } |
| |
| void WindowManager::HandleKeyRelease(const XKeyEvent& e) { |
| if (IsSessionEnding()) |
| return; |
| key_bindings_->HandleKeyRelease(e.keycode, e.state, e.time); |
| } |
| |
| void WindowManager::HandleLeaveNotify(const XLeaveWindowEvent& e) { |
| DLOG(INFO) << "Handling leave notify for " << XidStr(e.window); |
| const Point relative_pos(e.x, e.y); |
| const Point absolute_pos(e.x_root, e.y_root); |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandlePointerLeave(e.window, relative_pos, absolute_pos, e.time)); |
| } |
| |
| void WindowManager::HandleMapNotify(const XMapEvent& e) { |
| DLOG(INFO) << "Handling map notify for " << XidStr(e.window); |
| Window* win = GetWindow(e.window); |
| if (!win || win->mapped()) |
| return; |
| HandleMappedWindow(win); |
| } |
| |
| void WindowManager::HandleMapRequest(const XMapRequestEvent& e) { |
| DLOG(INFO) << "Handling map request for " << XidStr(e.window); |
| Window* win = GetWindow(e.window); |
| if (!win) { |
| // This probably won't do much good; if we don't know about the window, |
| // we're not going to be compositing it. |
| LOG(WARNING) << "Mapping " << XidStr(e.window) |
| << ", which we somehow didn't already know about"; |
| xconn_->MapWindow(e.window); |
| return; |
| } |
| |
| // We've seen this happen before (see http://crosbug.com/4176), and it |
| // can cause problems in code further down the pipe. |
| if (win->mapped()) { |
| LOG(WARNING) << "Ignoring map request for already-mapped window " |
| << win->xid_str(); |
| return; |
| } |
| |
| if (win->override_redirect()) { |
| LOG(WARNING) << "Huh? Got a MapRequest event for override-redirect " |
| << "window " << win->xid_str(); |
| } |
| |
| for (set<EventConsumer*>::iterator it = event_consumers_.begin(); |
| it != event_consumers_.end(); ++it) { |
| if ((*it)->HandleWindowMapRequest(win)) { |
| // Map the window if the consumer approved it. If the request fails |
| // (probably because the window has already been destroyed), bail out -- |
| // we won't get an UnmapNotify event later, so we don't want to |
| // incorrectly think that the window is mapped. |
| if (win->MapClient()) { |
| win->HandleMapRequested(); |
| HandleMappedWindow(win); |
| } |
| return; |
| } |
| } |
| |
| // Nobody was interested in the window. |
| LOG(WARNING) << "Not mapping window " << win->xid_str() << " with type " |
| << win->type(); |
| } |
| |
| void WindowManager::HandleMappingNotify(const XMappingEvent& e) { |
| DLOG(INFO) << "Handling (keyboard) mapping notify"; |
| xconn()->RefreshKeyboardMap(e.request, e.first_keycode, e.count); |
| key_bindings_->RefreshKeyMappings(); |
| } |
| |
| void WindowManager::HandleMotionNotify(const XMotionEvent& e) { |
| const Point relative_pos(e.x, e.y); |
| const Point absolute_pos(e.x_root, e.y_root); |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| window_event_consumers_, |
| e.window, |
| HandlePointerMotion(e.window, relative_pos, absolute_pos, e.time)); |
| } |
| |
| void WindowManager::HandlePropertyNotify(const XPropertyEvent& e) { |
| if (e.window == root_ && e.atom == GetXAtom(ATOM_CHROME_LOGGED_IN)) { |
| int value = 0; |
| bool logged_in = xconn_->GetIntProperty(root_, e.atom, &value) && value; |
| SetLoggedInState(logged_in, false); // initial=false |
| return; |
| } |
| |
| // TODO: These can currently be very spammy. The property is changed in |
| // response to user interaction, including scrollwheel events, which can |
| // be generated very quickly (thousands per second!). Exit early for now |
| // so we don't get bogged down writing to the log, but this can be |
| // deleted later once we have a better solution for handling fast |
| // scrolling. |
| if (e.atom == GetXAtom(ATOM_NET_WM_USER_TIME)) |
| return; |
| |
| Window* win = GetWindow(e.window); |
| if (win) { |
| const bool deleted = (e.state == PropertyDelete); |
| DLOG(INFO) << "Handling property notify for " << win->xid_str() << " about " |
| << (deleted ? "deleted " : "") << "property " |
| << XidStr(e.atom) << " (" << GetXAtomName(e.atom) << ")"; |
| if (e.atom == GetXAtom(ATOM_CHROME_FREEZE_UPDATES)) { |
| win->HandleFreezeUpdatesPropertyChange(!deleted); |
| } else if (e.atom == GetXAtom(ATOM_CHROME_STATUS_BOUNDS)) { |
| win->FetchAndApplyChromeStatusBounds(); |
| } else if (e.atom == GetXAtom(ATOM_CHROME_WINDOW_TYPE)) { |
| win->FetchAndApplyWindowType(); |
| } else if (e.atom == GetXAtom(ATOM_NET_WM_NAME)) { |
| win->FetchAndApplyTitle(); |
| } else if (e.atom == GetXAtom(ATOM_NET_WM_PID)) { |
| win->FetchAndApplyWmPid(); |
| } else if (e.atom == GetXAtom(ATOM_NET_WM_STATE)) { |
| win->FetchAndApplyWmState(); |
| } else if (e.atom == GetXAtom(ATOM_NET_WM_SYNC_REQUEST_COUNTER)) { |
| // Just re-fetch WM_PROTOCOLS here; this property is sometimes only set |
| // after we've already read WM_PROTOCOLS -- see comment #3 at |
| // http://crosbug.com/5846. |
| win->FetchAndApplyWmProtocols(); |
| } else if (e.atom == GetXAtom(ATOM_NET_WM_WINDOW_OPACITY)) { |
| win->FetchAndApplyWindowOpacity(); |
| } else if (e.atom == GetXAtom(ATOM_NET_WM_WINDOW_TYPE)) { |
| win->FetchAndApplyWmWindowType(); |
| } else if (e.atom == GetXAtom(ATOM_WM_CLIENT_MACHINE)) { |
| win->FetchAndApplyWmClientMachine(); |
| } else if (e.atom == GetXAtom(ATOM_WM_HINTS)) { |
| win->FetchAndApplyWmHints(); |
| } else if (e.atom == GetXAtom(ATOM_WM_NORMAL_HINTS)) { |
| win->FetchAndApplySizeHints(); |
| } else if (e.atom == GetXAtom(ATOM_WM_PROTOCOLS)) { |
| win->FetchAndApplyWmProtocols(); |
| } else if (e.atom == GetXAtom(ATOM_WM_TRANSIENT_FOR)) { |
| win->FetchAndApplyTransientHint(); |
| } |
| } |
| |
| // Notify any event consumers that were interested in this property. |
| FOR_EACH_INTERESTED_EVENT_CONSUMER( |
| property_change_event_consumers_, |
| make_pair(e.window, e.atom), |
| HandleWindowPropertyChange(e.window, e.atom)); |
| } |
| |
| void WindowManager::HandleReparentNotify(const XReparentEvent& e) { |
| DLOG(INFO) << "Handling reparent notify for " << XidStr(e.window) |
| << " to " << XidStr(e.parent); |
| |
| if (e.parent == root_) { |
| // If a window got reparented *to* the root window, we want to track |
| // it in the stacking order (we don't bother tracking it as a true |
| // toplevel window; we don't see this happen much outside of Flash |
| // windows). The window gets stacked on top of its new siblings. |
| DCHECK(!stacked_xids_->Contains(e.window)); |
| stacked_xids_->AddOnTop(e.window); |
| } else { |
| // Otherwise, if it got reparented away from us, stop tracking it. |
| // We ignore windows that aren't immediate children of the root window. |
| if (!stacked_xids_->Contains(e.window)) |
| return; |
| |
| stacked_xids_->Remove(e.window); |
| |
| if (mapped_xids_->Contains(e.window)) { |
| mapped_xids_->Remove(e.window); |
| UpdateClientListProperty(); |
| } |
| |
| Window* win = GetWindow(e.window); |
| if (win) { |
| if (!win->override_redirect()) |
| UpdateClientListStackingProperty(); |
| |
| // Make sure that all event consumers know that the window's going away. |
| if (win->mapped()) |
| FOR_EACH_EVENT_CONSUMER(event_consumers_, HandleWindowUnmap(win)); |
| focus_manager_->HandleWindowUnmap(win); |
| |
| client_windows_.erase(e.window); |
| |
| // We're not going to be compositing the window anymore, so |
| // unredirect it so it'll get drawn using the usual path. |
| xconn_->UnredirectWindowForCompositing(e.window); |
| } |
| } |
| } |
| |
| void WindowManager::HandleRRScreenChangeNotify( |
| const XRRScreenChangeNotifyEvent& e) { |
| DLOG(INFO) << "Got RRScreenChangeNotify event to " |
| << e.width << "x" << e.height; |
| // TODO: This doesn't seem to work reliably (we're not seeing any events |
| // right now), so we get size change notifications via ConfigureNotify |
| // events on the root window instead. Either stop using randr or figure |
| // out what's wrong if we need it for e.g. rotate. |
| } |
| |
| void WindowManager::HandleShapeNotify(const XShapeEvent& e) { |
| Window* win = GetWindow(e.window); |
| if (!win) |
| return; |
| |
| DLOG(INFO) << "Handling " << (e.kind == ShapeBounding ? "bounding" : "clip") |
| << " shape notify for " << XidStr(e.window); |
| if (e.kind == ShapeBounding) |
| win->FetchAndApplyShape(); |
| } |
| |
| void WindowManager::HandleSyncAlarmNotify(const XSyncAlarmNotifyEvent& e) { |
| int64_t value = |
| (static_cast<int64_t>(XSyncValueHigh32(e.counter_value)) << 32) | |
| XSyncValueLow32(e.counter_value); |
| DLOG(INFO) << "Handling sync alarm notify for alarm " << XidStr(e.alarm) |
| << " with value " << value; |
| Window* win = FindWithDefault( |
| sync_alarms_to_windows_, e.alarm, static_cast<Window*>(NULL)); |
| if (!win) { |
| // We get notification that counters have been reset to 0 after the |
| // corresponding windows are destroyed; don't log a warning for that. |
| if (value != 0) |
| LOG(WARNING) << "Ignoring unregistered alarm " << XidStr(e.alarm); |
| return; |
| } |
| win->HandleSyncAlarmNotify(e.alarm, value); |
| } |
| |
| void WindowManager::HandleUnmapNotify(const XUnmapEvent& e) { |
| DLOG(INFO) << "Handling unmap notify for " << XidStr(e.window); |
| Window* win = GetWindow(e.window); |
| if (!win) |
| return; |
| if (!win->mapped()) { |
| LOG(WARNING) << "Ignoring unmap notify for non-mapped window " |
| << win->xid_str(); |
| return; |
| } |
| HandleUnmappedWindow(win); |
| } |
| |
| XWindow WindowManager::GetArbitraryChromeWindow() { |
| for (WindowMap::const_iterator i = client_windows_.begin(); |
| i != client_windows_.end(); ++i) { |
| if (WmIpcWindowTypeIsChrome(i->second->type())) |
| return i->first; |
| } |
| return 0; |
| } |
| |
| void WindowManager::TakeScreenshot(bool select_region) { |
| const string& dir = logged_in_ ? |
| FLAGS_logged_in_screenshot_output_dir : |
| FLAGS_logged_out_screenshot_output_dir; |
| |
| if (access(dir.c_str(), F_OK) != 0 && |
| !file_util::CreateDirectory(FilePath(dir))) { |
| LOG(ERROR) << "Unable to create screenshot directory " << dir; |
| return; |
| } |
| |
| vector<string> argv; |
| argv.push_back(FLAGS_screenshot_binary); |
| |
| if (select_region) |
| argv.push_back("--region"); |
| |
| string filename = StringPrintf("%s/screenshot-%s.png", dir.c_str(), |
| GetTimeAsString(GetCurrentTimeSec()).c_str()); |
| argv.push_back(filename); |
| |
| LOG(INFO) << "Saving screenshot to " << filename; |
| RunCommandInBackground(argv); |
| } |
| |
| void WindowManager::CreateStartupBackground() { |
| startup_pixmap_ = |
| xconn_->CreatePixmap(root_, root_bounds_.size(), root_depth_); |
| xconn_->CopyArea(root_, // src |
| startup_pixmap_, // dest |
| Point(0, 0), // src_pos |
| Point(0, 0), // dest_pos |
| root_bounds_.size()); |
| Compositor::TexturePixmapActor* pixmap_actor = |
| compositor_->CreateTexturePixmap(); |
| pixmap_actor->SetPixmap(startup_pixmap_); |
| startup_background_.reset(pixmap_actor); |
| startup_background_->SetName("startup background"); |
| stage_->AddActor(startup_background_.get()); |
| stacking_manager_->StackActorAtTopOfLayer( |
| startup_background_.get(), StackingManager::LAYER_BACKGROUND); |
| startup_background_->Show(); |
| } |
| |
| void WindowManager::InitInformationalActors() { |
| if (!compositor_->TexturePixmapActorUsesFastPath() && |
| !FLAGS_unaccelerated_graphics_image.empty()) { |
| shared_ptr<Compositor::Actor> unaccelerated_graphics_actor( |
| compositor_->CreateImageFromFile(FLAGS_unaccelerated_graphics_image)); |
| informational_actors_.push_back(unaccelerated_graphics_actor); |
| |
| stage_->AddActor(unaccelerated_graphics_actor.get()); |
| unaccelerated_graphics_actor->Move( |
| kUnacceleratedGraphicsActorOffsetPixels, |
| kUnacceleratedGraphicsActorOffsetPixels, |
| 0); |
| stacking_manager_->StackActorAtTopOfLayer( |
| unaccelerated_graphics_actor.get(), StackingManager::LAYER_DEBUGGING); |
| } |
| |
| #ifndef NDEBUG |
| if (!FLAGS_debug_build_image.empty()) { |
| shared_ptr<Compositor::Actor> debug_build_actor( |
| compositor_->CreateImageFromFile(FLAGS_debug_build_image)); |
| informational_actors_.push_back(debug_build_actor); |
| |
| stage_->AddActor(debug_build_actor.get()); |
| debug_build_actor->Move( |
| root_bounds_.right() - debug_build_actor->GetWidth(), |
| root_bounds_.bottom() - debug_build_actor->GetHeight(), |
| 0); |
| stacking_manager_->StackActorAtTopOfLayer( |
| debug_build_actor.get(), StackingManager::LAYER_DEBUGGING); |
| } |
| #endif |
| |
| if (!informational_actors_.empty()) { |
| DCHECK_EQ(drop_informational_actors_timeout_id_, |
| EventLoop::kUnsetTimeoutId); |
| drop_informational_actors_timeout_id_ = |
| event_loop_->AddTimeout( |
| NewPermanentCallback( |
| this, &WindowManager::HandleDropInformationalActorsTimeout), |
| kDropInformationalActorsTimeoutMs, 0); |
| } |
| } |
| |
| void WindowManager::HandleDropInformationalActorsTimeout() { |
| informational_actors_.clear(); |
| event_loop_->RemoveTimeoutIfSet(&drop_informational_actors_timeout_id_); |
| } |
| |
| void WindowManager::DisableCompositing() { |
| DCHECK(disable_compositing_task_is_pending_); |
| disable_compositing_task_is_pending_ = false; |
| |
| // Looks like we changed our minds about disabling compositing. |
| if (!unredirected_fullscreen_xid_) |
| return; |
| |
| // Give the overlay window an empty bounding region before unredirection. |
| // Unredirection won't create exposure events, so we need to remove the |
| // bounding region first to let the client know that it should refresh the |
| // content. |
| xconn_->SetWindowBoundingRegionToRect(overlay_xid_, Rect()); |
| xconn_->UnredirectWindowForCompositing(unredirected_fullscreen_xid_); |
| compositor_->set_should_draw_frame(false); |
| DLOG(INFO) << "Turned compositing off"; |
| } |
| |
| void WindowManager::DestroyLoginControllerInternal() { |
| if (!login_controller_.get()) |
| return; |
| DLOG(INFO) << "Destroying login controller"; |
| event_consumers_.erase(login_controller_.get()); |
| login_controller_.reset(); |
| } |
| |
| void WindowManager::PingChrome() { |
| chrome_watchdog_->SendPingToChrome(GetCurrentTimeFromServer(), |
| kPingChromeTimeoutMs); |
| } |
| |
| } // namespace window_manager |