1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/desktop_shape_tracker.h"
6
7#include <vector>
8
9#include "base/memory/scoped_ptr.h"
10#include "base/win/scoped_gdi_object.h"
11#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
12#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
13
14namespace remoting {
15
16namespace {
17
18struct EnumDesktopShapeData {
19  EnumDesktopShapeData()
20    : window_region(CreateRectRgn(0, 0, 0, 0)),
21      desktop_region(CreateRectRgn(0, 0, 0, 0)) {
22  }
23  base::win::ScopedRegion window_region;
24  base::win::ScopedRegion desktop_region;
25};
26
27class DesktopShapeTrackerWin : public DesktopShapeTracker {
28 public:
29  DesktopShapeTrackerWin();
30  virtual ~DesktopShapeTrackerWin();
31
32  virtual void RefreshDesktopShape();
33  virtual const webrtc::DesktopRegion& desktop_shape();
34
35 private:
36  // Callback passed to EnumWindows() to enumerate windows.
37  static BOOL CALLBACK EnumWindowsCallback(HWND window, LPARAM lparam);
38
39  // The most recently calculated desktop region.
40  webrtc::DesktopRegion desktop_shape_;
41
42  // Stored to compare with newly calculated desktop shapes, to avoid converting
43  // to an DesktopRegion unless the shape has actually changed.
44  base::win::ScopedRegion old_desktop_region_;
45
46  DISALLOW_COPY_AND_ASSIGN(DesktopShapeTrackerWin);
47};
48
49DesktopShapeTrackerWin::DesktopShapeTrackerWin()
50  : old_desktop_region_(CreateRectRgn(0, 0, 0, 0)) {
51}
52
53DesktopShapeTrackerWin::~DesktopShapeTrackerWin() {
54}
55
56void DesktopShapeTrackerWin::RefreshDesktopShape() {
57  // Accumulate a new desktop shape from current window positions.
58  scoped_ptr<EnumDesktopShapeData> shape_data(new EnumDesktopShapeData);
59  if (!EnumWindows(EnumWindowsCallback, (LPARAM)shape_data.get())) {
60    PLOG(ERROR) << "Failed to enumerate windows";
61    desktop_shape_.Clear();
62    return;
63  }
64
65  // If the shape has changed, refresh |desktop_shape_|.
66  if (!EqualRgn(shape_data->desktop_region, old_desktop_region_)) {
67    old_desktop_region_.Set(shape_data->desktop_region.release());
68
69    // Determine the size of output buffer required to receive the region.
70    DWORD bytes_size = GetRegionData(old_desktop_region_, 0, NULL);
71    CHECK(bytes_size != 0);
72
73    // Fetch the Windows RECTs that comprise the region.
74    std::vector<char> buffer(bytes_size);
75    LPRGNDATA region_data = reinterpret_cast<LPRGNDATA>(buffer.data());
76    DWORD result = GetRegionData(old_desktop_region_, bytes_size, region_data);
77    CHECK(result == bytes_size);
78    const LPRECT rects = reinterpret_cast<LPRECT>(&region_data->Buffer[0]);
79
80    // Reset |desktop_shape_| and add new rectangles into it.
81    desktop_shape_.Clear();
82    for (size_t i = 0; i < region_data->rdh.nCount; ++i) {
83      desktop_shape_.AddRect(webrtc::DesktopRect::MakeLTRB(
84          rects[i].left, rects[i].top, rects[i].right, rects[i].bottom));
85    }
86  }
87}
88
89const webrtc::DesktopRegion& DesktopShapeTrackerWin::desktop_shape() {
90  return desktop_shape_;
91}
92
93// static
94BOOL DesktopShapeTrackerWin::EnumWindowsCallback(HWND window, LPARAM lparam) {
95  EnumDesktopShapeData* data = reinterpret_cast<EnumDesktopShapeData*>(lparam);
96  HRGN desktop_region = data->desktop_region.Get();
97  HRGN window_region = data->window_region.Get();
98
99  // Is the window visible?
100  if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
101    return TRUE;
102
103  // Find the desktop position of the window (including non-client-area).
104  RECT window_rect;
105  if (!GetWindowRect(window, &window_rect))
106    return TRUE;
107
108  // Find the shape of the window, in window coords.
109  // GetWindowRgn will overwrite the current contents of |window_region|.
110  if (GetWindowRgn(window, window_region) != ERROR) {
111    // Translate the window region into desktop coordinates.
112    OffsetRgn(window_region, window_rect.left, window_rect.top);
113  } else {
114    // Window has no shape, or an error occurred, so assume it's rectangular.
115    SetRectRgn(window_region, window_rect.left, window_rect.top,
116               window_rect.right, window_rect.bottom);
117  }
118
119  // TODO(wez): If the window is maximized then we should clip it to the
120  // display on which it is maximized.
121  //  if (IsZoomed(window))
122  //    CombineRgn(window_region, window_region, screen_region, RGN_AND);
123
124  // Merge the window region into the accumulated desktop region.  Window
125  // regions are combined together before converting the result to
126  // DesktopRegion. It assumed that this approach is more efficient than
127  // converting each window region individually.
128  CombineRgn(desktop_region, desktop_region, window_region, RGN_OR);
129
130  return TRUE;
131}
132
133} // namespace
134
135// static
136scoped_ptr<DesktopShapeTracker> DesktopShapeTracker::Create(
137    webrtc::DesktopCaptureOptions options) {
138  return scoped_ptr<DesktopShapeTracker>(new DesktopShapeTrackerWin());
139}
140
141}  // namespace remoting
142