| /* | |
| * 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 <cstring> | |
| #include <memory> | |
| #include "modules/desktop_capture/desktop_capture_types.h" | |
| #include "modules/desktop_capture/desktop_frame.h" | |
| #include "modules/desktop_capture/desktop_geometry.h" | |
| #include "modules/desktop_capture/mouse_cursor.h" | |
| #include "modules/desktop_capture/mouse_cursor_monitor.h" | |
| #include "modules/desktop_capture/win/cursor.h" | |
| #include "modules/desktop_capture/win/window_capture_utils.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/logging.h" | |
| namespace webrtc { | |
| namespace { | |
| bool IsSameCursorShape(const CURSORINFO& left, const CURSORINFO& right) { | |
| // If the cursors are not showing, we do not care the hCursor handle. | |
| return left.flags == right.flags && | |
| (left.flags != CURSOR_SHOWING || left.hCursor == right.hCursor); | |
| } | |
| } // namespace | |
| class MouseCursorMonitorWin : public MouseCursorMonitor { | |
| public: | |
| explicit MouseCursorMonitorWin(HWND window); | |
| explicit MouseCursorMonitorWin(ScreenId screen); | |
| ~MouseCursorMonitorWin() override; | |
| void Init(Callback* callback, Mode mode) override; | |
| void Capture() override; | |
| private: | |
| // Get the rect of the currently selected screen, relative to the primary | |
| // display's top-left. If the screen is disabled or disconnected, or any error | |
| // happens, an empty rect is returned. | |
| DesktopRect GetScreenRect(); | |
| HWND window_; | |
| ScreenId screen_; | |
| Callback* callback_; | |
| Mode mode_; | |
| HDC desktop_dc_; | |
| // The last CURSORINFO (converted to MouseCursor) we have sent to the client. | |
| CURSORINFO last_cursor_; | |
| }; | |
| MouseCursorMonitorWin::MouseCursorMonitorWin(HWND window) | |
| : window_(window), | |
| screen_(kInvalidScreenId), | |
| callback_(NULL), | |
| mode_(SHAPE_AND_POSITION), | |
| desktop_dc_(NULL) { | |
| memset(&last_cursor_, 0, sizeof(CURSORINFO)); | |
| } | |
| MouseCursorMonitorWin::MouseCursorMonitorWin(ScreenId screen) | |
| : window_(NULL), | |
| screen_(screen), | |
| callback_(NULL), | |
| mode_(SHAPE_AND_POSITION), | |
| desktop_dc_(NULL) { | |
| RTC_DCHECK_GE(screen, kFullDesktopScreenId); | |
| memset(&last_cursor_, 0, sizeof(CURSORINFO)); | |
| } | |
| MouseCursorMonitorWin::~MouseCursorMonitorWin() { | |
| if (desktop_dc_) | |
| ReleaseDC(NULL, desktop_dc_); | |
| } | |
| void MouseCursorMonitorWin::Init(Callback* callback, Mode mode) { | |
| RTC_DCHECK(!callback_); | |
| RTC_DCHECK(callback); | |
| callback_ = callback; | |
| mode_ = mode; | |
| desktop_dc_ = GetDC(NULL); | |
| } | |
| void MouseCursorMonitorWin::Capture() { | |
| RTC_DCHECK(callback_); | |
| CURSORINFO cursor_info; | |
| cursor_info.cbSize = sizeof(CURSORINFO); | |
| if (!GetCursorInfo(&cursor_info)) { | |
| RTC_LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " | |
| << GetLastError(); | |
| return; | |
| } | |
| if (!IsSameCursorShape(cursor_info, last_cursor_)) { | |
| if (cursor_info.flags == CURSOR_SUPPRESSED) { | |
| // The cursor is intentionally hidden now, send an empty bitmap. | |
| last_cursor_ = cursor_info; | |
| callback_->OnMouseCursor(new MouseCursor( | |
| new BasicDesktopFrame(DesktopSize()), DesktopVector())); | |
| } else { | |
| // According to MSDN https://goo.gl/u6gyuC, HCURSOR instances returned by | |
| // functions other than CreateCursor do not need to be actively destroyed. | |
| // And CloseHandle function (https://goo.gl/ja5ycW) does not close a | |
| // cursor, so assume a HCURSOR does not need to be closed. | |
| if (cursor_info.flags == 0) { | |
| // Host machine does not have a hardware mouse attached, we will send a | |
| // default one instead. | |
| // Note, Windows automatically caches cursor resource, so we do not need | |
| // to cache the result of LoadCursor. | |
| cursor_info.hCursor = LoadCursor(nullptr, IDC_ARROW); | |
| } | |
| std::unique_ptr<MouseCursor> cursor( | |
| CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); | |
| if (cursor) { | |
| last_cursor_ = cursor_info; | |
| callback_->OnMouseCursor(cursor.release()); | |
| } | |
| } | |
| } | |
| if (mode_ != SHAPE_AND_POSITION) | |
| return; | |
| // CURSORINFO::ptScreenPos is in full desktop coordinate. | |
| DesktopVector position(cursor_info.ptScreenPos.x, cursor_info.ptScreenPos.y); | |
| bool inside = cursor_info.flags == CURSOR_SHOWING; | |
| if (window_) { | |
| DesktopRect original_rect; | |
| DesktopRect cropped_rect; | |
| if (!GetCroppedWindowRect(window_, /*avoid_cropping_border*/ false, | |
| &cropped_rect, &original_rect)) { | |
| position.set(0, 0); | |
| inside = false; | |
| } else { | |
| if (inside) { | |
| HWND windowUnderCursor = WindowFromPoint(cursor_info.ptScreenPos); | |
| inside = windowUnderCursor | |
| ? (window_ == GetAncestor(windowUnderCursor, GA_ROOT)) | |
| : false; | |
| } | |
| position = position.subtract(cropped_rect.top_left()); | |
| } | |
| } else { | |
| RTC_DCHECK_NE(screen_, kInvalidScreenId); | |
| DesktopRect rect = GetScreenRect(); | |
| if (inside) | |
| inside = rect.Contains(position); | |
| position = position.subtract(rect.top_left()); | |
| } | |
| callback_->OnMouseCursorPosition(position); | |
| } | |
| DesktopRect MouseCursorMonitorWin::GetScreenRect() { | |
| RTC_DCHECK_NE(screen_, kInvalidScreenId); | |
| if (screen_ == kFullDesktopScreenId) { | |
| return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN), | |
| GetSystemMetrics(SM_YVIRTUALSCREEN), | |
| GetSystemMetrics(SM_CXVIRTUALSCREEN), | |
| GetSystemMetrics(SM_CYVIRTUALSCREEN)); | |
| } | |
| DISPLAY_DEVICE device; | |
| device.cb = sizeof(device); | |
| BOOL result = EnumDisplayDevices(NULL, screen_, &device, 0); | |
| if (!result) | |
| return DesktopRect(); | |
| DEVMODE device_mode; | |
| device_mode.dmSize = sizeof(device_mode); | |
| device_mode.dmDriverExtra = 0; | |
| result = EnumDisplaySettingsEx(device.DeviceName, ENUM_CURRENT_SETTINGS, | |
| &device_mode, 0); | |
| if (!result) | |
| return DesktopRect(); | |
| return DesktopRect::MakeXYWH( | |
| device_mode.dmPosition.x, device_mode.dmPosition.y, | |
| device_mode.dmPelsWidth, device_mode.dmPelsHeight); | |
| } | |
| MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( | |
| const DesktopCaptureOptions& options, | |
| WindowId window) { | |
| return new MouseCursorMonitorWin(reinterpret_cast<HWND>(window)); | |
| } | |
| MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( | |
| const DesktopCaptureOptions& options, | |
| ScreenId screen) { | |
| return new MouseCursorMonitorWin(screen); | |
| } | |
| std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create( | |
| const DesktopCaptureOptions& options) { | |
| return std::unique_ptr<MouseCursorMonitor>( | |
| CreateForScreen(options, kFullDesktopScreenId)); | |
| } | |
| } // namespace webrtc |