/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include #include "modules/desktop_capture/mouse_cursor_monitor.h" #include #include #include #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capture_types.h" #include "modules/desktop_capture/desktop_frame.h" #include "modules/desktop_capture/mouse_cursor.h" #include "modules/desktop_capture/x11/x_error_trap.h" #include "rtc_base/logging.h" namespace { // WindowCapturer returns window IDs of X11 windows with WM_STATE attribute. // These windows may not be immediate children of the root window, because // window managers may re-parent them to add decorations. However, // XQueryPointer() expects to be passed children of the root. This function // searches up the list of the windows to find the root child that corresponds // to |window|. Window GetTopLevelWindow(Display* display, Window window) { while (true) { // If the window is in WithdrawnState then look at all of its children. ::Window root, parent; ::Window* children; unsigned int num_children; if (!XQueryTree(display, window, &root, &parent, &children, &num_children)) { RTC_LOG(LS_ERROR) << "Failed to query for child windows although window" << "does not have a valid WM_STATE."; return None; } if (children) XFree(children); if (parent == root) break; window = parent; } return window; } } // namespace namespace webrtc { class MouseCursorMonitorX11 : public MouseCursorMonitor, public SharedXDisplay::XEventHandler { public: MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window); ~MouseCursorMonitorX11() override; void Init(Callback* callback, Mode mode) override; void Capture() override; private: // SharedXDisplay::XEventHandler interface. bool HandleXEvent(const XEvent& event) override; Display* display() { return x_display_->display(); } // Captures current cursor shape and stores it in |cursor_shape_|. void CaptureCursor(); rtc::scoped_refptr x_display_; Callback* callback_; Mode mode_; Window window_; bool have_xfixes_; int xfixes_event_base_; int xfixes_error_base_; std::unique_ptr cursor_shape_; }; MouseCursorMonitorX11::MouseCursorMonitorX11( const DesktopCaptureOptions& options, Window window) : x_display_(options.x_display()), callback_(NULL), mode_(SHAPE_AND_POSITION), window_(window), have_xfixes_(false), xfixes_event_base_(-1), xfixes_error_base_(-1) { // Set a default initial cursor shape in case XFixes is not present. const int kSize = 5; std::unique_ptr default_cursor( new BasicDesktopFrame(DesktopSize(kSize, kSize))); const uint8_t pixels[kSize * kSize] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; uint8_t* ptr = default_cursor->data(); for (int y = 0; y < kSize; ++y) { for (int x = 0; x < kSize; ++x) { *ptr++ = pixels[kSize * y + x]; *ptr++ = pixels[kSize * y + x]; *ptr++ = pixels[kSize * y + x]; *ptr++ = 0xff; } } DesktopVector hotspot(2, 2); cursor_shape_.reset(new MouseCursor(default_cursor.release(), hotspot)); } MouseCursorMonitorX11::~MouseCursorMonitorX11() { if (have_xfixes_) { x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify, this); } } void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) { // Init can be called only once per instance of MouseCursorMonitor. RTC_DCHECK(!callback_); RTC_DCHECK(callback); callback_ = callback; mode_ = mode; have_xfixes_ = XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_); if (have_xfixes_) { // Register for changes to the cursor shape. XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask); x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this); CaptureCursor(); } else { RTC_LOG(LS_INFO) << "X server does not support XFixes."; } } void MouseCursorMonitorX11::Capture() { RTC_DCHECK(callback_); // Process X11 events in case XFixes has sent cursor notification. x_display_->ProcessPendingXEvents(); // cursor_shape_| is set only if we were notified of a cursor shape change. if (cursor_shape_.get()) callback_->OnMouseCursor(cursor_shape_.release()); // Get cursor position if necessary. if (mode_ == SHAPE_AND_POSITION) { int root_x; int root_y; int win_x; int win_y; Window root_window; Window child_window; unsigned int mask; XErrorTrap error_trap(display()); Bool result = XQueryPointer(display(), window_, &root_window, &child_window, &root_x, &root_y, &win_x, &win_y, &mask); CursorState state; if (!result || error_trap.GetLastErrorAndDisable() != 0) { state = OUTSIDE; } else { // In screen mode (window_ == root_window) the mouse is always inside. // XQueryPointer() sets |child_window| to None if the cursor is outside // |window_|. state = (window_ == root_window || child_window != None) ? INSIDE : OUTSIDE; } // As the comments to GetTopLevelWindow() above indicate, in window capture, // the cursor position capture happens in |window_|, while the frame catpure // happens in |child_window|. These two windows are not alwyas same, as // window manager may add some decorations to the |window_|. So translate // the coordinate in |window_| to the coordinate space of |child_window|. if (window_ != root_window && state == INSIDE) { int translated_x, translated_y; Window unused; if (XTranslateCoordinates(display(), window_, child_window, win_x, win_y, &translated_x, &translated_y, &unused)) { win_x = translated_x; win_y = translated_y; } } // X11 always starts the coordinate from (0, 0), so we do not need to // translate here. callback_->OnMouseCursorPosition(DesktopVector(root_x, root_y)); } } bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) { if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) { const XFixesCursorNotifyEvent* cursor_event = reinterpret_cast(&event); if (cursor_event->subtype == XFixesDisplayCursorNotify) { CaptureCursor(); } // Return false, even if the event has been handled, because there might be // other listeners for cursor notifications. } return false; } void MouseCursorMonitorX11::CaptureCursor() { RTC_DCHECK(have_xfixes_); XFixesCursorImage* img; { XErrorTrap error_trap(display()); img = XFixesGetCursorImage(display()); if (!img || error_trap.GetLastErrorAndDisable() != 0) return; } std::unique_ptr image( new BasicDesktopFrame(DesktopSize(img->width, img->height))); // Xlib stores 32-bit data in longs, even if longs are 64-bits long. unsigned long* src = img->pixels; uint32_t* dst = reinterpret_cast(image->data()); uint32_t* dst_end = dst + (img->width * img->height); while (dst < dst_end) { *dst++ = static_cast(*src++); } DesktopVector hotspot(std::min(img->width, img->xhot), std::min(img->height, img->yhot)); XFree(img); cursor_shape_.reset(new MouseCursor(image.release(), hotspot)); } // static MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( const DesktopCaptureOptions& options, WindowId window) { if (!options.x_display()) return NULL; window = GetTopLevelWindow(options.x_display()->display(), window); if (window == None) return NULL; return new MouseCursorMonitorX11(options, window); } MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( const DesktopCaptureOptions& options, ScreenId screen) { if (!options.x_display()) return NULL; return new MouseCursorMonitorX11( options, DefaultRootWindow(options.x_display()->display())); } std::unique_ptr MouseCursorMonitor::Create( const DesktopCaptureOptions& options) { return std::unique_ptr( CreateForScreen(options, kFullDesktopScreenId)); } } // namespace webrtc