| /* |
| * 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 <memory> |
| |
| #include "modules/desktop_capture/mouse_cursor_monitor.h" |
| |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| #include <X11/extensions/Xfixes.h> |
| |
| #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<SharedXDisplay> x_display_; |
| Callback* callback_; |
| Mode mode_; |
| Window window_; |
| |
| bool have_xfixes_; |
| int xfixes_event_base_; |
| int xfixes_error_base_; |
| |
| std::unique_ptr<MouseCursor> 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<DesktopFrame> 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<const XFixesCursorNotifyEvent*>(&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<DesktopFrame> 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<uint32_t*>(image->data()); |
| uint32_t* dst_end = dst + (img->width * img->height); |
| while (dst < dst_end) { |
| *dst++ = static_cast<uint32_t>(*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> MouseCursorMonitor::Create( |
| const DesktopCaptureOptions& options) { |
| return std::unique_ptr<MouseCursorMonitor>( |
| CreateForScreen(options, kFullDesktopScreenId)); |
| } |
| |
| } // namespace webrtc |