blob: e80c3adf446b493dd9a20156f53d61eea4de4fdc [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef WINDOW_MANAGER_WINDOW_H_
#define WINDOW_MANAGER_WINDOW_H_
#include <ctime>
#include <map>
#include <set>
#include <string>
#include <vector>
#include <gtest/gtest_prod.h> // for FRIEND_TEST() macro
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "cros/chromeos_wm_ipc_enums.h"
#include "window_manager/atom_cache.h" // for Atom enum
#include "window_manager/compositor/compositor.h"
#include "window_manager/geometry.h"
#include "window_manager/shadow.h"
#include "window_manager/wm_ipc.h"
#include "window_manager/x11/x_connection.h"
#include "window_manager/x11/x_types.h"
namespace window_manager {
class AnimationPair;
class DestroyedWindow;
struct Rect;
template<class T> class Stacker; // from util.h
class WindowManager;
// A client window.
//
// Because we use Xcomposite, there are (at least) two locations for a
// given window that we need to keep track of:
//
// - Where the client window is actually located on the X server. This is
// relevant for input -- we shape the compositing overlay window so that
// events fall through it to the client windows underneath.
// - Where the window gets drawn on the compositing overlay window. It'll
// typically just be drawn in the same location as the actual X window,
// but we could also e.g. draw a scaled-down version of it in a different
// location.
//
// These two locations are not necessarily the same. When animating a
// window move, it may be desirable to just move the X window once to the
// final location and then animate the move on the overlay. As a result,
// there are different sets of methods to manipulate the client window and
// the composited window.
class Window {
public:
Window(WindowManager* wm,
XWindow xid,
bool override_redirect,
const XConnection::WindowGeometry& geometry);
~Window();
XWindow xid() const { return xid_; }
const std::string& xid_str() const { return xid_str_; }
WindowManager* wm() { return wm_; }
Compositor::TexturePixmapActor* actor() { return actor_.get(); }
const Shadow* shadow() const { return shadow_.get(); }
XWindow transient_for_xid() const { return transient_for_xid_; }
bool override_redirect() const { return override_redirect_; }
chromeos::WmIpcWindowType type() const { return type_; }
const std::vector<int>& type_params() const { return type_params_; }
const char* type_str() const {
return chromeos::WmIpcWindowTypeToString(type_);
}
bool mapped() const { return mapped_; }
bool shaped() const { return shaped_; }
bool is_rgba() const { return client_depth_ == 32; }
int client_x() const { return client_x_; }
int client_y() const { return client_y_; }
int client_width() const { return client_width_; }
int client_height() const { return client_height_; }
int client_depth() const { return client_depth_; }
bool composited_shown() const { return composited_shown_; }
int composited_x() const { return composited_x_; }
int composited_y() const { return composited_y_; }
int composited_width() const { return client_width_ * composited_scale_x_; }
int composited_height() const { return client_height_ * composited_scale_y_; }
double composited_scale_x() const { return composited_scale_x_; }
double composited_scale_y() const { return composited_scale_y_; }
double composited_opacity() const { return composited_opacity_; }
// The client might've already requested that the window be translucent,
// in addition to whatever level has been set on the composited window.
double combined_opacity() const {
return composited_opacity_ * client_opacity_;
}
const std::string& title() const { return title_; }
const XConnection::SizeHints& size_hints() const { return size_hints_; }
bool supports_wm_ping() const { return supports_wm_ping_; }
const std::vector<XAtom>& wm_window_type_xatoms() const {
return wm_window_type_xatoms_;
}
bool wm_state_fullscreen() const { return wm_state_fullscreen_; }
bool wm_state_modal() const { return wm_state_modal_; }
bool wm_hint_urgent() const { return wm_hint_urgent_; }
const std::string& client_hostname() const { return client_hostname_; }
int client_pid() const { return client_pid_; }
// Have we received a pixmap for this window yet? If not, it won't be
// drawn onscreen.
bool has_initial_pixmap() const { return pixmap_ != 0; }
// Is this window currently focused? We don't go to the X server for
// this; we just check with the FocusManager.
bool IsFocused() const;
// Update |title_| based on _NET_WM_NAME.
void FetchAndApplyTitle();
// Get and apply hints that have been set for the client window.
bool FetchAndApplySizeHints();
bool FetchAndApplyTransientHint();
// Update the window based on its Chrome OS window type property.
bool FetchAndApplyWindowType();
// Update the window's opacity in response to the current value of its
// _NET_WM_WINDOW_OPACITY property.
void FetchAndApplyWindowOpacity();
// Fetch the window's WM_HINTS property (ICCCM 4.1.2.4) if it exists and
// apply any changes that we see.
void FetchAndApplyWmHints();
// Fetch the window's WM_PROTOCOLS property (ICCCM 4.1.2.7) if it exists
// and update the various |supports_wm_*| members. Also calls
// FetchAndApplyWmSyncRequestCounter(), if the window claims to support
// _NET_WM_SYNC_REQUEST.
void FetchAndApplyWmProtocols();
// Fetch the window's _NET_WM_SYNC_REQUEST_COUNTER property (as described
// in EWMH) and ask the Sync extension to notify us whenever it changes.
bool FetchAndApplyWmSyncRequestCounterProperty();
// Fetch the window's _NET_WM_STATE property and update our internal copy
// of it. ClientMessage events should be used to update the states of mapped
// windows, so this is primarily useful for getting the initial state of the
// window before it's been mapped.
void FetchAndApplyWmState();
// Fetch the window's _NET_WM_WINDOW_TYPE property and update
// wm_window_type_atoms_.
void FetchAndApplyWmWindowType();
// Fetch the window's _CHROME_STATE property and update our internal copy
// of it.
void FetchAndApplyChromeState();
// Fetch the window's WM_CLIENT_MACHINE property and update
// |client_hostname_|.
void FetchAndApplyWmClientMachine();
// Fetch the window's _NET_WM_PID property and update |client_pid_|.
void FetchAndApplyWmPid();
// Check if the window has been shaped using the Shape extension and
// update its compositing actor accordingly. If the window is shaped, we
// hide its shadow if it has one.
void FetchAndApplyShape();
// Query the X server to see if this window is currently mapped or not.
// This should only be used for checking the state of an existing window
// at startup; use mapped() after that.
bool FetchMapState();
// Parse a _NET_WM_STATE message about this window, storing the requested
// state changes in |states_out|. The passed-in data is from the
// ClientMessage event.
void ParseWmStateMessage(
const long data[5], std::map<XAtom, bool>* states_out) const;
// Set or unset _NET_WM_STATE values for this window. Updates our
// internal copy of the state and the window's _NET_WM_STATE property.
bool ChangeWmState(const std::map<XAtom, bool>& states);
// Set or unset particular _CHROME_STATE values for this window (each
// atom's bool value states whether it should be added or removed).
// Other existing values in the property remain unchanged.
bool ChangeChromeState(const std::map<XAtom, bool>& states);
// Give keyboard focus to the client window, using a WM_TAKE_FOCUS
// message if the client supports it or a SetInputFocus request
// otherwise. (Note that the client doesn't necessarily need to accept
// the focus if WM_TAKE_FOCUS is used; see ICCCM 4.1.7.)
//
// Most callers should call FocusManager::FocusWindow() instead of
// invoking this directly; FocusManager handles adding or removing button
// grabs for click-to-focus and updating the _NET_ACTIVE_WINDOW property.
bool TakeFocus(XTime timestamp);
// If the window supports WM_DELETE_WINDOW messages, ask it to delete
// itself. Just does nothing and returns false otherwise.
bool SendDeleteRequest(XTime timestamp);
// Send a _NET_WM_PING client message to the window so we can check that
// it's not frozen.
bool SendPing(XTime timestamp);
// Add or remove passive a passive grab on button presses within this
// window. When any button is pressed, a _synchronous_ active pointer
// grab will be installed. Note that this means that no pointer events
// will be received until the pointer grab is manually removed using
// XConnection::UngrabPointer() -- this can be used to ensure that the client
// receives the initial click on its window when implementing click-to-focus
// behavior.
//
// Most callers should use FocusManager::UseClickToFocusForWindow(),
// which will handle all of this for them.
bool AddButtonGrab();
bool RemoveButtonGrab();
// Get the largest possible size for this window smaller than or equal to
// the passed-in desired dimensions (while respecting any sizing hints it
// supplied via the WM_NORMAL_HINTS property).
void GetMaxSize(int desired_width, int desired_height,
int* width_out, int* height_out) const;
// Tell the X server to map or unmap this window.
bool MapClient();
bool UnmapClient();
// Update our internal copy of the client window's position or size.
// External callers should only use these methods to record position and
// size changes that they hear about for override-redirect windows;
// non-override-redirect windows can be moved or resized using
// MoveClient() and ResizeClient().
void SaveClientPosition(int x, int y) {
client_x_ = x;
client_y_ = y;
}
void SaveClientSize(int width, int height) {
client_width_ = width;
client_height_ = height;
}
// Ask the X server to move or resize the client window. Also calls the
// corresponding SetClient*() method on success. Returns false on
// failure.
bool MoveClient(int x, int y);
// Move the client window offscreen to prevent it from receiving input.
bool MoveClientOffscreen();
bool MoveClientToComposited();
// Center the client window over the passed-in window.
bool CenterClientOverWindow(Window* owner);
// Resize the client window. A southeast gravity means that the
// bottom-right corner of the window will remain fixed while the
// upper-left corner will move to accomodate the new size.
bool ResizeClient(int width, int height, Gravity gravity);
// Stack the client window directly above or below another window.
bool StackClientAbove(XWindow sibling_xid);
bool StackClientBelow(XWindow sibling_xid);
// Make various changes to the composited window (and its shadow).
void MoveComposited(int x, int y, int anim_ms);
void MoveCompositedX(int x, int anim_ms);
void MoveCompositedY(int y, int anim_ms);
void MoveCompositedToClient(); // no animation
void ShowComposited();
void HideComposited();
void SetCompositedOpacity(double opacity, int anim_ms);
void ScaleComposited(double scale_x, double scale_y, int anim_ms);
// Create and return a pair of Animation objects that can be used to animate
// the window's X and Y positions. Ownership of the object is passed to the
// caller, who should pass it back via SetMoveCompositedAnimation() after
// adding additional keyframes.
//
// Windows with shadows cannot currently be animated (this is DCHECK()-ed).
AnimationPair* CreateMoveCompositedAnimation();
// Use a pair of animations previously allocated with
// CreateMoveCompositedAnimation() to animate this window's position.
// Takes ownership of |animations|.
void SetMoveCompositedAnimation(AnimationPair* animations);
// Handle us having sent a request to the X server to map this
// (non-override-redirect) window. We send a _NET_WM_SYNC_REQUEST
// message to the window and send a synthetic ConfigureNotify event, so
// that we'll be notified by the client when it's finished painting the
// window.
void HandleMapRequested();
// Handle a MapNotify event about this window.
// We throw away the old pixmap for the window and get the new one.
void HandleMapNotify();
void HandleUnmapNotify();
// This method is called when this window is redirected for compositing
// after it has been unredirected. The previously stored pixmap is no longer
// valid, so it updates the pixmap by calling ResetPixmap. The content of
// the root window is copied to the new pixmap, so that the new pixmap's
// uninitialized contents are not briefly visible. This method can only be
// called if this window's contents are currently painted on the entire root
// window at (0, 0).
void HandleRedirect();
// Handle a ConfigureNotify event about this window.
// Currently, we just pass the window's width and height so we can resize
// the actor if needed.
void HandleConfigureNotify(int width, int height);
// Handle the window's contents being changed.
void HandleDamageNotify(const Rect& bounding_box);
// Handle the underlying X window being destroyed. If this method is
// invoked before destroying this Window object, a few
// compositing-related resources (actor, shadow, X pixmap) will be
// jettisoned via the returned DestroyedWindow object, ownership of which
// passes to the caller.
//
// Attempts to continue using the Window object after invoking this
// method will end in heartbreak -- almost every method will crash. Only
// call this when you're about to destroy the Window object. Just
// destroying the Window without calling this method first is fine if you
// have no desire to continue displaying the window's contents onscreen.
DestroyedWindow* HandleDestroyNotify();
// Enable drawing a drop shadow of a given type beneath this window.
// Note that even if this method is called, the shadow may not be visible
// (shadows aren't drawn for shaped windows, for instance) -- see
// UpdateShadowVisibility(). By default, we don't draw a shadow until
// this method is called.
void SetShadowType(Shadow::Type type);
// Disable drawing a drop shadow beneath this window.
void DisableShadow();
// Change the opacity of the window's shadow. The shadow's opacity is
// multiplied by that of the window itself.
void SetShadowOpacity(double opacity, int anim_ms);
// Stack the window directly above |actor| and its shadow directly above
// or below |shadow_actor| if supplied or below the window otherwise. If
// |actor| is NULL, the window's stacking isn't changed (but its shadow's
// still is). If |shadow_actor| is supplied, |stack_above_shadow_actor|
// determines whether the shadow will be stacked above or below it.
void StackCompositedAbove(Compositor::Actor* actor,
Compositor::Actor* shadow_actor,
bool stack_above_shadow_actor);
// Stack the window directly below |actor| and its shadow directly above
// or below |shadow_actor| if supplied or below the window otherwise. If
// |actor| is NULL, the window's stacking isn't changed (but its shadow's
// still is). If |shadow_actor| is supplied, |stack_above_shadow_actor|
// determines whether the shadow will be stacked above or below it.
void StackCompositedBelow(Compositor::Actor* actor,
Compositor::Actor* shadow_actor,
bool stack_above_shadow_actor);
// Return this window's bottom-most actor (either the window's shadow's
// group, or its actor itself if there's no shadow). This is useful for
// stacking another actor underneath this window.
Compositor::Actor* GetBottomActor();
// Store the client window's position and size in |rect|.
void CopyClientBoundsToRect(Rect* rect) const;
// Handle notification that a Sync extension alarm (presumably
// |wm_sync_request_alarm_|) has triggered. |value| contains the
// triggering value of the counter being watched.
void HandleSyncAlarmNotify(XID alarm_id, int64_t value);
// Send a synthetic ConfigureNotify event to the client containing the
// window's current position, size, etc.
void SendSyntheticConfigureNotify();
// Position to which we move X windows to prevent them from receiving input.
static const int kOffscreenX;
static const int kOffscreenY;
private:
friend class BasicWindowManagerTest;
FRIEND_TEST(FocusManagerTest, Modality); // sets |wm_state_modal_|
FRIEND_TEST(WindowTest, SyncRequest);
FRIEND_TEST(WindowTest, DeferFetchingPixmapUntilPainted);
FRIEND_TEST(WindowManagerTest, VideoTimeProperty);
FRIEND_TEST(WindowManagerTest, HandleLateSyncRequestCounter);
// Minimum dimensions and rate per second for damage events at which we
// conclude that a video is currently playing in this window.
static const int kVideoMinWidth;
static const int kVideoMinHeight;
static const int kVideoMinFramerate;
// Dimensions in which the actor should be moved by
// MoveActorToAdjustedPosition().
enum MoveDimensions {
MOVE_DIMENSIONS_X_AND_Y = 0,
MOVE_DIMENSIONS_X_ONLY,
MOVE_DIMENSIONS_Y_ONLY,
};
// Is the entirety of the client window currently offscreen?
bool IsClientWindowOffscreen() const;
// Helper method for ParseWmStateMessage() and ChangeWmState(). Given an
// action from a _NET_WM_STATE message (i.e. the XClientMessageEvent's
// data.l[0] field), updates |value| accordingly.
void SetWmStateInternal(int action, bool* value) const;
// Update the window's _NET_WM_STATE property based on the current values
// of the |wm_state_*| members.
bool UpdateWmStateProperty();
// Update the window's _CHROME_STATE property based on the current
// contents of |chrome_state_atoms_|.
bool UpdateChromeStateProperty();
// Destroys |wm_sync_request_alarm_| if non-NULL, unregisters our
// interest in it in the WindowManager, and resets
// |client_has_redrawn_after_last_resize_| to true.
void DestroyWmSyncRequestAlarm();
// Move the actor to its correct position over |anim_ms|, given
// |composited_x_| and |composited_y_|, the composited scale, and the actor's
// current size versus the client window's size. |dimensions| can be used to
// limit the dimension over which the actor is moved to just X or Y (when
// invoked by MoveCompositedX() or MoveCompositedY()).
//
// Way more background than you want to know: resizing a client window can be
// tricky for compositing window managers. Suppose that we have a 20x20
// window located at (10, 10) and we want to make it bigger so that its
// upper-left corner goes to (5, 10) while the right edge remains fixed,
// resulting in a 25x20 window. ResizeClient() asks the X server to
// atomically move and resize the window to the new bounds, but the window
// can't be drawn at the new size until the client has received the
// ConfigureNotify event and finished painting the new pixmap. If we move the
// actor to (5, 10) immediately and then update its pixmap later, the window
// will initially appear to jump to the left by 5 pixels; once we get the new
// pixmap, the right edge will expand by 5 pixels.
//
// To avoid this jank, we update |composited_x_| and |composited_y_|
// immediately in ResizeClient() if the window's origin moved due to the
// resize gravity but hold off on actually moving the actor until its size
// changes. Similarly, methods like MoveComposited() may not actually move
// the actor to the requested position immediately -- if we're waiting for the
// pixmap to be resized, we take the difference between its current size and
// the newly-requested size into account.
void MoveActorToAdjustedPosition(MoveDimensions dimensions, int anim_ms);
// Free |pixmap_|, store a new offscreen pixmap containing the window's
// contents in it, and notify |actor_| that the pixmap has changed.
void ResetPixmap();
// Update the visibility of |shadow_| if it's non-NULL. This window's
// shadow can be enabled or disabled via SetShouldUseShadow(), but there
// are still other reasons that we may not draw the shadow (e.g. the
// window is shaped or not yet mapped). This method is called when
// various states that can prevent us from drawing the shadow are changed.
void UpdateShadowVisibility();
// If the client supports _NET_WM_SYNC_REQUEST, increment
// |current_wm_sync_num_| and send the client a message telling it to
// update the counter after it's seen the ConfigureNotify and redrawn its
// contents.
void SendWmSyncRequestMessage();
XWindow xid_;
std::string xid_str_; // hex for debugging
WindowManager* wm_;
scoped_ptr<Compositor::TexturePixmapActor> actor_;
// This contains a shadow if SetShouldUseShadow(true) has been called and
// is NULL otherwise.
scoped_ptr<Shadow> shadow_;
// The XID that this window says it's transient for. Note that the
// client can arbitrarily supply an ID here; the window doesn't
// necessarily exist. A good general practice may be to examine this
// value when the window is mapped and ignore any changes after that.
XWindow transient_for_xid_;
// Was override-redirect set when the window was originally created?
bool override_redirect_;
// Is the client window currently mapped? This is only updated when the
// Window object is first created and when a MapNotify or UnmapNotify
// event is received (dependent on the receiver calling set_mapped()
// appropriately), so e.g. a call to MapClient() will not be immediately
// reflected in this variable.
bool mapped_;
// Is the window shaped (using the Shape extension)?
bool shaped_;
// Client-supplied window type.
chromeos::WmIpcWindowType type_;
// Parameters associated with |type_|. See chromeos::WmIpcWindowType for
// details.
std::vector<int> type_params_;
// Position and size of the client window.
int client_x_;
int client_y_;
int client_width_;
int client_height_;
// Bit depth of the client window.
int client_depth_;
// Client-requested opacity (via _NET_WM_WINDOW_OPACITY).
double client_opacity_;
bool composited_shown_;
int composited_x_;
int composited_y_;
double composited_scale_x_;
double composited_scale_y_;
double composited_opacity_;
// Gravity used to position the actor in the case where the actor's size
// differs from that of the client window. See MoveActorToAdjustedPosition()
// for details.
Gravity actor_gravity_;
// Current opacity requested for the window's shadow.
double shadow_opacity_;
std::string title_;
// Information from the WM_NORMAL_HINTS property.
XConnection::SizeHints size_hints_;
// Does the window have a WM_PROTOCOLS property claiming that it supports
// WM_TAKE_FOCUS or WM_DELETE_WINDOW messages?
bool supports_wm_take_focus_;
bool supports_wm_delete_window_;
bool supports_wm_ping_;
// EWMH window state, as set by _NET_WM_STATE client messages and exposed
// in the window's _NET_WM_STATE property.
// TODO: Just store these in a set like we do for _CHROME_STATE below.
bool wm_state_fullscreen_;
bool wm_state_maximized_horz_;
bool wm_state_maximized_vert_;
bool wm_state_modal_;
// Is this window marked urgent, per the ICCCM UrgencyHint flag in its
// WM_HINTS property?
bool wm_hint_urgent_;
// EWMH window types from the window's _NET_WM_WINDOW_TYPE property, in
// the order in which they appear (which is the window's preference for
// which type should be used).
std::vector<XAtom> wm_window_type_xatoms_;
// Chrome window state, as exposed in the window's _CHROME_STATE
// property.
std::set<XAtom> chrome_state_xatoms_;
// Damage object used to track changes to |xid_|.
XID damage_;
// Offscreen pixmap containing the window's redirected contents.
XID pixmap_;
// Number of "video-sized" or larger damage events that we've seen for
// this window during the second beginning at |video_damage_start_time_|.
// We use this as a rough heuristic to try to detect when the user is
// watching a video.
int num_video_damage_events_;
time_t video_damage_start_time_;
// XSync counter ID from the window's _NET_WM_SYNC_REQUEST_COUNTER
// property, or 0 if the window doesn't support _NET_WM_SYNC_REQUEST.
XID wm_sync_request_alarm_;
// Most-recent update request number that we've sent to the window before
// resizing it as part of _NET_WM_SYNC_REQUEST.
int64_t current_wm_sync_num_;
// Has the client indicated that it's redrawn the window after the last
// time that we resized it? (If not, we're currently waiting for the
// window to update |wm_sync_request_counter_| to match
// |current_wm_sync_num_|.) We also leave this at true if the client
// doesn't support _NET_WM_SYNC_REQUEST.
bool client_has_redrawn_after_last_resize_;
// Hostname of the system on which the client is running, as specified in
// the WM_CLIENT_MACHINE property.
std::string client_hostname_;
// The client's PID as specified in the _NET_WM_PID property, or -1 if
// unknown.
int client_pid_;
DISALLOW_COPY_AND_ASSIGN(Window);
};
// We sometimes want to continue displaying a window's contents onscreen
// even after receiving a DestroyNotify event indicating that the
// underlying X window was closed. DestroyedWindow contains a subset of
// compositing-related resources that have been released from an
// about-to-be-deleted Window object.
class DestroyedWindow {
public:
DestroyedWindow(WindowManager* wm,
XWindow xid,
Compositor::TexturePixmapActor* actor,
Shadow* shadow,
XID pixmap);
~DestroyedWindow();
WindowManager* wm() { return wm_; }
Compositor::TexturePixmapActor* actor() { return actor_.get(); }
Shadow* shadow() { return shadow_.get(); }
private:
WindowManager* wm_; // not owned
// Compositing actor being used to display |pixmap_|. This object
// initially uses the same position, scaling, stacking, opacity, etc.
// that it had when owned by the Window.
scoped_ptr<Compositor::TexturePixmapActor> actor_;
// Drop shadow that was set for the window, or NULL if no shadow was set.
// Note that changes made to |actor_| will need to be manually applied to
// |shadow_| as well.
scoped_ptr<Shadow> shadow_;
// X pixmap displayed by |actor_|; freed in our destructor.
// TODO: Can this be freed when the Window object is destroyed, or even
// earlier? The actor is displaying a GL texture bound to a GLX pixmap
// that was created from this X pixmap.
XID pixmap_;
DISALLOW_COPY_AND_ASSIGN(DestroyedWindow);
};
} // namespace window_manager
#endif // WINDOW_MANAGER_WINDOW_H_