blob: d43b853093b6345fbfc772d611d1011879563e36 [file] [log] [blame]
// Copyright (c) 2012 The Chromium 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 "ui/views/widget/desktop_aura/x11_desktop_handler.h"
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include "base/message_loop/message_loop.h"
#include "ui/aura/env.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/x/x11_menu_list.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/x/x11_error_tracker.h"
#include "ui/views/ime/input_method.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
namespace {
const char* kAtomsToCache[] = {
"_NET_ACTIVE_WINDOW",
NULL
};
// Our global instance. Deleted when our Env() is deleted.
views::X11DesktopHandler* g_handler = NULL;
} // namespace
namespace views {
// static
X11DesktopHandler* X11DesktopHandler::get() {
if (!g_handler)
g_handler = new X11DesktopHandler;
return g_handler;
}
X11DesktopHandler::X11DesktopHandler()
: xdisplay_(gfx::GetXDisplay()),
x_root_window_(DefaultRootWindow(xdisplay_)),
wm_user_time_ms_(0),
current_window_(None),
atom_cache_(xdisplay_, kAtomsToCache),
wm_supports_active_window_(false) {
if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
aura::Env::GetInstance()->AddObserver(this);
XWindowAttributes attr;
XGetWindowAttributes(xdisplay_, x_root_window_, &attr);
XSelectInput(xdisplay_, x_root_window_,
attr.your_event_mask | PropertyChangeMask |
StructureNotifyMask | SubstructureNotifyMask);
::Window active_window;
wm_supports_active_window_ =
ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &active_window) &&
active_window;
}
X11DesktopHandler::~X11DesktopHandler() {
aura::Env::GetInstance()->RemoveObserver(this);
if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
}
void X11DesktopHandler::ActivateWindow(::Window window) {
if (wm_supports_active_window_) {
DCHECK_EQ(gfx::GetXDisplay(), xdisplay_);
XEvent xclient;
memset(&xclient, 0, sizeof(xclient));
xclient.type = ClientMessage;
xclient.xclient.window = window;
xclient.xclient.message_type = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW");
xclient.xclient.format = 32;
xclient.xclient.data.l[0] = 1; // Specified we are an app.
xclient.xclient.data.l[1] = wm_user_time_ms_;
xclient.xclient.data.l[2] = None;
xclient.xclient.data.l[3] = 0;
xclient.xclient.data.l[4] = 0;
XSendEvent(xdisplay_, x_root_window_, False,
SubstructureRedirectMask | SubstructureNotifyMask,
&xclient);
} else {
XRaiseWindow(xdisplay_, window);
// XRaiseWindow will not give input focus to the window. We now need to ask
// the X server to do that. Note that the call will raise an X error if the
// window is not mapped.
XSetInputFocus(xdisplay_, window, RevertToParent, CurrentTime);
OnActiveWindowChanged(window);
}
}
bool X11DesktopHandler::IsActiveWindow(::Window window) const {
return window == current_window_;
}
void X11DesktopHandler::ProcessXEvent(XEvent* event) {
switch (event->type) {
case FocusIn:
if (current_window_ != event->xfocus.window)
OnActiveWindowChanged(event->xfocus.window);
break;
case FocusOut:
if (current_window_ == event->xfocus.window)
OnActiveWindowChanged(None);
break;
default:
NOTREACHED();
}
}
bool X11DesktopHandler::CanDispatchEvent(const ui::PlatformEvent& event) {
return event->type == CreateNotify || event->type == DestroyNotify ||
(event->type == PropertyNotify &&
event->xproperty.window == x_root_window_);
}
uint32_t X11DesktopHandler::DispatchEvent(const ui::PlatformEvent& event) {
switch (event->type) {
case PropertyNotify: {
// Check for a change to the active window.
CHECK_EQ(x_root_window_, event->xproperty.window);
::Atom active_window_atom = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW");
if (event->xproperty.atom == active_window_atom) {
::Window window;
if (ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &window) &&
window) {
OnActiveWindowChanged(window);
}
}
break;
}
// Menus created by Chrome can be drag and drop targets. Since they are
// direct children of the screen root window and have override_redirect
// we cannot use regular _NET_CLIENT_LIST_STACKING property to find them
// and use a separate cache to keep track of them.
// TODO(varkha): Implement caching of all top level X windows and their
// coordinates and stacking order to eliminate repeated calls to X server
// during mouse movement, drag and shaping events.
case CreateNotify: {
// The window might be destroyed if the message pump haven't gotten a
// chance to run but we can safely ignore the X error.
gfx::X11ErrorTracker error_tracker;
XCreateWindowEvent *xcwe = &event->xcreatewindow;
ui::XMenuList::GetInstance()->MaybeRegisterMenu(xcwe->window);
break;
}
case DestroyNotify: {
XDestroyWindowEvent *xdwe = &event->xdestroywindow;
ui::XMenuList::GetInstance()->MaybeUnregisterMenu(xdwe->window);
break;
}
default:
NOTREACHED();
}
return ui::POST_DISPATCH_NONE;
}
void X11DesktopHandler::OnWindowInitialized(aura::Window* window) {
}
void X11DesktopHandler::OnWillDestroyEnv() {
g_handler = NULL;
delete this;
}
void X11DesktopHandler::OnActiveWindowChanged(::Window xid) {
if (current_window_ == xid)
return;
DesktopWindowTreeHostX11* old_host =
views::DesktopWindowTreeHostX11::GetHostForXID(current_window_);
if (old_host)
old_host->HandleNativeWidgetActivationChanged(false);
// Update the current window ID to effectively change the active widget.
current_window_ = xid;
DesktopWindowTreeHostX11* new_host =
views::DesktopWindowTreeHostX11::GetHostForXID(xid);
if (new_host)
new_host->HandleNativeWidgetActivationChanged(true);
}
} // namespace views