x11_whole_screen_move_loop.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// found in the LICENSE file. 490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/views/widget/desktop_aura/x11_whole_screen_move_loop.h" 690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include <X11/Xlib.h> 890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Get rid of a macro from Xlib.h that conflicts with Aura's RootWindow class. 990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#undef RootWindow 1090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/bind.h" 127d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)#include "base/message_loop/message_loop.h" 1390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/run_loop.h" 14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "third_party/skia/include/core/SkBitmap.h" 1590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/aura/env.h" 16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/aura/window.h" 17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/aura/window_event_dispatcher.h" 18f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/aura/window_tree_host.h" 1990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/base/x/x11_util.h" 20d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)#include "ui/events/event.h" 215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "ui/events/event_utils.h" 22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/events/keycodes/keyboard_code_conversion_x.h" 235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "ui/events/platform/scoped_event_dispatcher.h" 24c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "ui/events/platform/x11/x11_event_source.h" 25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/gfx/point_conversions.h" 2690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/gfx/screen.h" 27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/controls/image_view.h" 28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/widget/widget.h" 2990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 3090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)namespace views { 3190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 32f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace { 33f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// The minimum alpha before we declare a pixel transparent when searching in 35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// our source image. 36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)const uint32 kMinAlpha = 32; 37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst unsigned char kDragWidgetOpacity = 0xc0; 38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 39f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class ScopedCapturer { 40f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) public: 415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) explicit ScopedCapturer(aura::WindowTreeHost* host) 42f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) : host_(host) { 43f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) host_->SetCapture(); 44f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 45f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 46f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) ~ScopedCapturer() { 47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) host_->ReleaseCapture(); 48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 49f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) private: 515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aura::WindowTreeHost* host_; 52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(ScopedCapturer); 54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}; 55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)} // namespace 57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 5890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)X11WholeScreenMoveLoop::X11WholeScreenMoveLoop( 5990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) X11WholeScreenMoveLoopDelegate* delegate) 6090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) : delegate_(delegate), 6190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) in_move_loop_(false), 625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) should_reset_mouse_flags_(false), 63effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch grab_input_window_(None), 64c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch canceled_(false), 65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) has_grab_(false), 66effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch weak_factory_(this) { 67effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch last_xmotion_.type = LASTEvent; 6890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 6990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 7090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {} 7190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 72effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid X11WholeScreenMoveLoop::DispatchMouseMovement() { 73effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (!weak_factory_.HasWeakPtrs()) 74effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return; 75effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch weak_factory_.InvalidateWeakPtrs(); 76effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch DCHECK_EQ(MotionNotify, last_xmotion_.type); 77effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch delegate_->OnMouseMovement(&last_xmotion_); 78effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch last_xmotion_.type = LASTEvent; 79effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch} 80effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 81c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch//////////////////////////////////////////////////////////////////////////////// 82c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation: 83c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 84c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochbool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) { 855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return in_move_loop_; 86c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch} 87c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 88c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochuint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) { 895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // This method processes all events for the grab_input_window_ as well as 905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // mouse events for all windows while the move loop is active - even before 915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // the grab is granted by X. This allows mouse notification events that were 925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // sent after the capture was requested but before the capture was granted 935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // to be dispatched. It is especially important to process the mouse release 945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // event that should have stopped the drag even if that mouse release happened 955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // before the grab was granted. 965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!in_move_loop_) 975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ui::POST_DISPATCH_PERFORM_DEFAULT; 9890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) XEvent* xev = event; 9990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 10090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // Note: the escape key is handled in the tab drag controller, which has 10190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // keyboard focus even though we took pointer grab. 10290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) switch (xev->type) { 10390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) case MotionNotify: { 104f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) if (drag_widget_.get()) { 105f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 106f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) gfx::Point location = gfx::ToFlooredPoint( 107f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) screen->GetCursorScreenPoint() - drag_offset_); 108f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) drag_widget_->SetBounds(gfx::Rect(location, drag_image_.size())); 109effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch drag_widget_->StackAtTop(); 110effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 111effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch last_xmotion_ = xev->xmotion; 112effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (!weak_factory_.HasWeakPtrs()) { 113effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Post a task to dispatch mouse movement event when control returns to 114effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // the message loop. This allows smoother dragging since the events are 115effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // dispatched without waiting for the drag widget updates. 116effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch base::MessageLoopForUI::current()->PostTask( 117effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch FROM_HERE, 118effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch base::Bind(&X11WholeScreenMoveLoop::DispatchMouseMovement, 119effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch weak_factory_.GetWeakPtr())); 120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ui::POST_DISPATCH_NONE; 12290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 12390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) case ButtonRelease: { 124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (xev->xbutton.button == Button1) { 125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Assume that drags are being done with the left mouse button. Only 126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // break the drag if the left mouse button was released. 127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch DispatchMouseMovement(); 128eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch delegate_->OnMouseReleased(); 129eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ui::POST_DISPATCH_NONE; 13190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) case KeyPress: { 133c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if (ui::KeyboardCodeFromXKeyEvent(xev) == ui::VKEY_ESCAPE) { 134c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch canceled_ = true; 135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EndMoveLoop(); 1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ui::POST_DISPATCH_NONE; 137c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch } 138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) break; 139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) case FocusOut: { 141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (xev->xfocus.mode != NotifyGrab) 142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) has_grab_ = false; 143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) break; 144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu case GenericEvent: { 1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu ui::EventType type = ui::EventTypeFromNative(xev); 1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu switch (type) { 1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu case ui::ET_MOUSE_MOVED: 1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu case ui::ET_MOUSE_DRAGGED: 1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu case ui::ET_MOUSE_RELEASED: { 1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu XEvent xevent = {0}; 1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (type == ui::ET_MOUSE_RELEASED) { 1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.type = ButtonRelease; 1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xbutton.button = ui::EventButtonFromNative(xev); 1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } else { 1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.type = MotionNotify; 1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xany.display = xev->xgeneric.display; 1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xany.window = grab_input_window_; 1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // The fields used below are in the same place for all of events 1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // above. Using xmotion from XEvent's unions to avoid repeating 1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // the code. 1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xmotion.root = DefaultRootWindow(xev->xgeneric.display); 1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xmotion.time = ui::EventTimeFromNative(xev).InMilliseconds(); 1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu gfx::Point point(ui::EventSystemLocationFromNative(xev)); 1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xmotion.x_root = point.x(); 1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xevent.xmotion.y_root = point.y(); 1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu DispatchEvent(&xevent); 1695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ui::POST_DISPATCH_NONE; 1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu default: 1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu break; 1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 17590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 17690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return (event->xany.window == grab_input_window_) ? 1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu ui::POST_DISPATCH_NONE : ui::POST_DISPATCH_PERFORM_DEFAULT; 17990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 18090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 1817dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochbool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window* source, 1827dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch gfx::NativeCursor cursor) { 1830529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch DCHECK(!in_move_loop_); // Can only handle one nested loop at a time. 1840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // Start a capture on the host, so that it continues to receive events during 186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // the drag. This may be second time we are capturing the mouse events - the 187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // first being when a mouse is first pressed. That first capture needs to be 188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // released before the call to GrabPointerAndKeyboard below, otherwise it may 189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // get released while we still need the pointer grab, which is why we restrict 190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // the scope here. 191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) { 192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ScopedCapturer capturer(source->GetHost()); 193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1940529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch grab_input_window_ = CreateDragInputWindow(gfx::GetXDisplay()); 195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Releasing ScopedCapturer ensures that any other instance of 196a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // X11ScopedCapture will not prematurely release grab that will be acquired 197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // below. 198a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // TODO(varkha): Consider integrating GrabPointerAndKeyboard with 200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // ScopedCapturer to avoid possibility of logically keeping multiple grabs. 2010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if (!GrabPointerAndKeyboard(cursor)) { 2020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch XDestroyWindow(gfx::GetXDisplay(), grab_input_window_); 20390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return false; 2040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch } 2050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 2065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu scoped_ptr<ui::ScopedEventDispatcher> old_dispatcher = 2075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu nested_dispatcher_.Pass(); 2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu nested_dispatcher_ = 2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this); 2100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if (!drag_image_.isNull() && CheckIfIconValid()) 2110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch CreateDragImageWindow(); 21290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 2131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // We are handling a mouse drag outside of the aura::RootWindow system. We 2141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // must manually make aura think that the mouse button is pressed so that we 2151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // don't draw extraneous tooltips. 2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aura::Env* env = aura::Env::GetInstance(); 2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!env->IsMouseButtonDown()) { 2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) env->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); 2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) should_reset_mouse_flags_ = true; 2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 2211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 2220529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch in_move_loop_ = true; 223c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch canceled_ = false; 22490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 22590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); 2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::RunLoop run_loop; 22790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) quit_closure_ = run_loop.QuitClosure(); 22890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) run_loop.Run(); 2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu nested_dispatcher_ = old_dispatcher.Pass(); 230c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return !canceled_; 23190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 23290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 2337dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochvoid X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor) { 2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (in_move_loop_) { 2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // If we're still in the move loop, regrab the pointer with the updated 2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // cursor. Note: we can be called from handling an XdndStatus message after 2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // EndMoveLoop() was called, but before we return from the nested RunLoop. 238a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) GrabPointerAndKeyboard(cursor); 2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 2407dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch} 2417dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch 24290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void X11WholeScreenMoveLoop::EndMoveLoop() { 24390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) if (!in_move_loop_) 24490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return; 24590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 246effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Prevent DispatchMouseMovement from dispatching any posted motion event. 247effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch weak_factory_.InvalidateWeakPtrs(); 248effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch last_xmotion_.type = LASTEvent; 249effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 2501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // We undo our emulated mouse click from RunMoveLoop(); 2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (should_reset_mouse_flags_) { 2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) aura::Env::GetInstance()->set_mouse_button_flags(0); 2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) should_reset_mouse_flags_ = false; 2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 2551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 25690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // TODO(erg): Is this ungrab the cause of having to click to give input focus 25790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // on drawn out windows? Not ungrabbing here screws the X server until I kill 25890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // the chrome process. 25990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 26090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // Ungrab before we let go of the window. 26168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) XDisplay* display = gfx::GetXDisplay(); 262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // Only ungrab pointer if capture was not switched to another window. 263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (has_grab_) { 264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) XUngrabPointer(display, CurrentTime); 265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) XUngrabKeyboard(display, CurrentTime); 266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 26790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Restore the previous dispatcher. 2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu nested_dispatcher_.reset(); 270f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) drag_widget_.reset(); 27190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) delegate_->OnMoveLoopEnded(); 27290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) XDestroyWindow(display, grab_input_window_); 2735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu grab_input_window_ = None; 27490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 27590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) in_move_loop_ = false; 27690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) quit_closure_.Run(); 27790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 27890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 279f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void X11WholeScreenMoveLoop::SetDragImage(const gfx::ImageSkia& image, 280f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) gfx::Vector2dF offset) { 281f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) drag_image_ = image; 282f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) drag_offset_ = offset; 283f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // Reset the Y offset, so that the drag-image is always just below the cursor, 284f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // so that it is possible to see where the cursor is going. 285f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) drag_offset_.set_y(0.f); 286f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)} 287f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 288a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool X11WholeScreenMoveLoop::GrabPointerAndKeyboard(gfx::NativeCursor cursor) { 28968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) XDisplay* display = gfx::GetXDisplay(); 2907dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch XGrabServer(display); 291a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 2927dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch XUngrabPointer(display, CurrentTime); 2937dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch int ret = XGrabPointer( 2947dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch display, 2957dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch grab_input_window_, 2967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch False, 2977dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch ButtonPressMask | ButtonReleaseMask | PointerMotionMask, 2987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch GrabModeAsync, 2997dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch GrabModeAsync, 3007dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch None, 3017dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch cursor.platform(), 3027dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch CurrentTime); 3037dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch if (ret != GrabSuccess) { 304a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) DLOG(ERROR) << "Grabbing pointer for dragging failed: " 3057dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch << ui::GetX11ErrorString(display, ret); 306a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } else { 307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) has_grab_ = true; 308a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) XUngrabKeyboard(display, CurrentTime); 309a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ret = XGrabKeyboard( 310a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) display, 311a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) grab_input_window_, 312a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) False, 313a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) GrabModeAsync, 314a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) GrabModeAsync, 315a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) CurrentTime); 316a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (ret != GrabSuccess) { 317a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) DLOG(ERROR) << "Grabbing keyboard for dragging failed: " 318a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) << ui::GetX11ErrorString(display, ret); 319a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 3207dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch } 3217dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch 322a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) XUngrabServer(display); 3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu XFlush(display); 324a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return ret == GrabSuccess; 3257dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch} 3267dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch 327f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)Window X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay* display) { 328f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // Creates an invisible, InputOnly toplevel window. This window will receive 329f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // all mouse movement for drags. It turns out that normal windows doing a 330f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // grab doesn't redirect pointer motion events if the pointer isn't over the 331f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // grabbing window. But InputOnly windows are able to grab everything. This 332f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // is what GTK+ does, and I found a patch to KDE that did something similar. 333f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) unsigned long attribute_mask = CWEventMask | CWOverrideRedirect; 334f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) XSetWindowAttributes swa; 335f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) memset(&swa, 0, sizeof(swa)); 336f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) swa.event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | 337a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) KeyPressMask | KeyReleaseMask | StructureNotifyMask; 338f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) swa.override_redirect = True; 339f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) Window window = XCreateWindow(display, 340f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) DefaultRootWindow(display), 341f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) -100, -100, 10, 10, 342f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 0, CopyFromParent, InputOnly, CopyFromParent, 343f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) attribute_mask, &swa); 344f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) XMapRaised(display, window); 345c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(window); 346f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) return window; 347f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)} 348f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 349f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void X11WholeScreenMoveLoop::CreateDragImageWindow() { 350f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) Widget* widget = new Widget; 351f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) Widget::InitParams params(Widget::InitParams::TYPE_DRAG); 352f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) params.opacity = Widget::InitParams::OPAQUE_WINDOW; 353f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 354f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) params.accept_events = false; 355f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 356f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) gfx::Point location = gfx::ToFlooredPoint( 357f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() - drag_offset_); 358f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) params.bounds = gfx::Rect(location, drag_image_.size()); 359f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->set_focus_on_creation(false); 360f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE); 361f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->Init(params); 362effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch widget->SetOpacity(kDragWidgetOpacity); 363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->GetNativeWindow()->SetName("DragWindow"); 364f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 365f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) ImageView* image = new ImageView(); 366f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) image->SetImage(drag_image_); 367f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) image->SetBounds(0, 0, drag_image_.width(), drag_image_.height()); 368f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->SetContentsView(image); 369f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->Show(); 370f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false); 371f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 372f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) drag_widget_.reset(widget); 373f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)} 374f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 375a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool X11WholeScreenMoveLoop::CheckIfIconValid() { 376010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // Because we need a GL context per window, we do a quick check so that we 377010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // don't make another context if the window would just be displaying a mostly 378010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // transparent image. 379a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) const SkBitmap* in_bitmap = drag_image_.bitmap(); 380a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) SkAutoLockPixels in_lock(*in_bitmap); 381a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for (int y = 0; y < in_bitmap->height(); ++y) { 382a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) uint32* in_row = in_bitmap->getAddr32(0, y); 383a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 384a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for (int x = 0; x < in_bitmap->width(); ++x) { 385a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (SkColorGetA(in_row[x]) > kMinAlpha) 386a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return true; 387a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 388a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 389a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 390a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return false; 391a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)} 392a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 39390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} // namespace views 394