1 /*
2  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include <assert.h>
12 #include <string.h>
13 
14 #include <memory>
15 
16 #include "modules/desktop_capture/desktop_capture_types.h"
17 #include "modules/desktop_capture/desktop_frame.h"
18 #include "modules/desktop_capture/desktop_geometry.h"
19 #include "modules/desktop_capture/mouse_cursor.h"
20 #include "modules/desktop_capture/mouse_cursor_monitor.h"
21 #include "modules/desktop_capture/win/cursor.h"
22 #include "modules/desktop_capture/win/screen_capture_utils.h"
23 #include "modules/desktop_capture/win/window_capture_utils.h"
24 #include "rtc_base/logging.h"
25 
26 namespace webrtc {
27 
28 namespace {
29 
IsSameCursorShape(const CURSORINFO & left,const CURSORINFO & right)30 bool IsSameCursorShape(const CURSORINFO& left, const CURSORINFO& right) {
31   // If the cursors are not showing, we do not care the hCursor handle.
32   return left.flags == right.flags &&
33          (left.flags != CURSOR_SHOWING || left.hCursor == right.hCursor);
34 }
35 
36 }  // namespace
37 
38 class MouseCursorMonitorWin : public MouseCursorMonitor {
39  public:
40   explicit MouseCursorMonitorWin(HWND window);
41   explicit MouseCursorMonitorWin(ScreenId screen);
42   ~MouseCursorMonitorWin() override;
43 
44   void Init(Callback* callback, Mode mode) override;
45   void Capture() override;
46 
47  private:
48   // Get the rect of the currently selected screen, relative to the primary
49   // display's top-left. If the screen is disabled or disconnected, or any error
50   // happens, an empty rect is returned.
51   DesktopRect GetScreenRect();
52 
53   HWND window_;
54   ScreenId screen_;
55 
56   Callback* callback_;
57   Mode mode_;
58 
59   HDC desktop_dc_;
60 
61   // The last CURSORINFO (converted to MouseCursor) we have sent to the client.
62   CURSORINFO last_cursor_;
63 };
64 
MouseCursorMonitorWin(HWND window)65 MouseCursorMonitorWin::MouseCursorMonitorWin(HWND window)
66     : window_(window),
67       screen_(kInvalidScreenId),
68       callback_(NULL),
69       mode_(SHAPE_AND_POSITION),
70       desktop_dc_(NULL) {
71   memset(&last_cursor_, 0, sizeof(CURSORINFO));
72 }
73 
MouseCursorMonitorWin(ScreenId screen)74 MouseCursorMonitorWin::MouseCursorMonitorWin(ScreenId screen)
75     : window_(NULL),
76       screen_(screen),
77       callback_(NULL),
78       mode_(SHAPE_AND_POSITION),
79       desktop_dc_(NULL) {
80   assert(screen >= kFullDesktopScreenId);
81   memset(&last_cursor_, 0, sizeof(CURSORINFO));
82 }
83 
~MouseCursorMonitorWin()84 MouseCursorMonitorWin::~MouseCursorMonitorWin() {
85   if (desktop_dc_)
86     ReleaseDC(NULL, desktop_dc_);
87 }
88 
Init(Callback * callback,Mode mode)89 void MouseCursorMonitorWin::Init(Callback* callback, Mode mode) {
90   assert(!callback_);
91   assert(callback);
92 
93   callback_ = callback;
94   mode_ = mode;
95 
96   desktop_dc_ = GetDC(NULL);
97 }
98 
Capture()99 void MouseCursorMonitorWin::Capture() {
100   assert(callback_);
101 
102   CURSORINFO cursor_info;
103   cursor_info.cbSize = sizeof(CURSORINFO);
104   if (!GetCursorInfo(&cursor_info)) {
105     RTC_LOG_F(LS_ERROR) << "Unable to get cursor info. Error = "
106                         << GetLastError();
107     return;
108   }
109 
110   if (!IsSameCursorShape(cursor_info, last_cursor_)) {
111     if (cursor_info.flags == CURSOR_SUPPRESSED) {
112       // The cursor is intentionally hidden now, send an empty bitmap.
113       last_cursor_ = cursor_info;
114       callback_->OnMouseCursor(new MouseCursor(
115           new BasicDesktopFrame(DesktopSize()), DesktopVector()));
116     } else {
117       // According to MSDN https://goo.gl/u6gyuC, HCURSOR instances returned by
118       // functions other than CreateCursor do not need to be actively destroyed.
119       // And CloseHandle function (https://goo.gl/ja5ycW) does not close a
120       // cursor, so assume a HCURSOR does not need to be closed.
121       if (cursor_info.flags == 0) {
122         // Host machine does not have a hardware mouse attached, we will send a
123         // default one instead.
124         // Note, Windows automatically caches cursor resource, so we do not need
125         // to cache the result of LoadCursor.
126         cursor_info.hCursor = LoadCursor(nullptr, IDC_ARROW);
127       }
128       std::unique_ptr<MouseCursor> cursor(
129           CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor));
130       if (cursor) {
131         last_cursor_ = cursor_info;
132         callback_->OnMouseCursor(cursor.release());
133       }
134     }
135   }
136 
137   if (mode_ != SHAPE_AND_POSITION)
138     return;
139 
140   // CURSORINFO::ptScreenPos is in full desktop coordinate.
141   DesktopVector position(cursor_info.ptScreenPos.x, cursor_info.ptScreenPos.y);
142   bool inside = cursor_info.flags == CURSOR_SHOWING;
143 
144   if (window_) {
145     DesktopRect original_rect;
146     DesktopRect cropped_rect;
147     if (!GetCroppedWindowRect(window_, /*avoid_cropping_border*/ false,
148                               &cropped_rect, &original_rect)) {
149       position.set(0, 0);
150       inside = false;
151     } else {
152       if (inside) {
153         HWND windowUnderCursor = WindowFromPoint(cursor_info.ptScreenPos);
154         inside = windowUnderCursor
155                      ? (window_ == GetAncestor(windowUnderCursor, GA_ROOT))
156                      : false;
157       }
158       position = position.subtract(cropped_rect.top_left());
159     }
160   } else {
161     assert(screen_ != kInvalidScreenId);
162     DesktopRect rect = GetScreenRect();
163     if (inside)
164       inside = rect.Contains(position);
165     position = position.subtract(rect.top_left());
166   }
167 
168   callback_->OnMouseCursorPosition(position);
169 }
170 
GetScreenRect()171 DesktopRect MouseCursorMonitorWin::GetScreenRect() {
172   assert(screen_ != kInvalidScreenId);
173   if (screen_ == kFullDesktopScreenId) {
174     return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN),
175                                  GetSystemMetrics(SM_YVIRTUALSCREEN),
176                                  GetSystemMetrics(SM_CXVIRTUALSCREEN),
177                                  GetSystemMetrics(SM_CYVIRTUALSCREEN));
178   }
179   DISPLAY_DEVICE device;
180   device.cb = sizeof(device);
181   BOOL result = EnumDisplayDevices(NULL, screen_, &device, 0);
182   if (!result)
183     return DesktopRect();
184 
185   DEVMODE device_mode;
186   device_mode.dmSize = sizeof(device_mode);
187   device_mode.dmDriverExtra = 0;
188   result = EnumDisplaySettingsEx(device.DeviceName, ENUM_CURRENT_SETTINGS,
189                                  &device_mode, 0);
190   if (!result)
191     return DesktopRect();
192 
193   return DesktopRect::MakeXYWH(
194       device_mode.dmPosition.x, device_mode.dmPosition.y,
195       device_mode.dmPelsWidth, device_mode.dmPelsHeight);
196 }
197 
CreateForWindow(const DesktopCaptureOptions & options,WindowId window)198 MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
199     const DesktopCaptureOptions& options,
200     WindowId window) {
201   return new MouseCursorMonitorWin(reinterpret_cast<HWND>(window));
202 }
203 
CreateForScreen(const DesktopCaptureOptions & options,ScreenId screen)204 MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
205     const DesktopCaptureOptions& options,
206     ScreenId screen) {
207   return new MouseCursorMonitorWin(screen);
208 }
209 
Create(const DesktopCaptureOptions & options)210 std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create(
211     const DesktopCaptureOptions& options) {
212   return std::unique_ptr<MouseCursorMonitor>(
213       CreateForScreen(options, kFullDesktopScreenId));
214 }
215 
216 }  // namespace webrtc
217