1// Copyright (c) 2012 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_resizer.h"
6
7#include <map>
8#include <windows.h>
9
10#include "base/logging.h"
11
12namespace {
13// TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
14const int kDefaultDPI = 96;
15}  // namespace
16
17namespace remoting {
18
19// Provide comparison operation for ScreenResolution so we can use it in
20// std::map.
21static inline bool operator <(const ScreenResolution& a,
22                              const ScreenResolution& b) {
23  if (a.dimensions().width() != b.dimensions().width())
24    return a.dimensions().width() < b.dimensions().width();
25  if (a.dimensions().height() != b.dimensions().height())
26    return a.dimensions().height() < b.dimensions().height();
27  if (a.dpi().x() != b.dpi().x())
28    return a.dpi().x() < b.dpi().x();
29  return a.dpi().y() < b.dpi().y();
30}
31
32class DesktopResizerWin : public DesktopResizer {
33 public:
34  DesktopResizerWin();
35  virtual ~DesktopResizerWin();
36
37  // DesktopResizer interface.
38  virtual ScreenResolution GetCurrentResolution() OVERRIDE;
39  virtual std::list<ScreenResolution> GetSupportedResolutions(
40      const ScreenResolution& preferred) OVERRIDE;
41  virtual void SetResolution(const ScreenResolution& resolution) OVERRIDE;
42  virtual void RestoreResolution(const ScreenResolution& original) OVERRIDE;
43
44 private:
45  static bool IsResizeSupported();
46
47  // Calls EnumDisplaySettingsEx() for the primary monitor.
48  // Returns false if |mode_number| does not exist.
49  static bool GetPrimaryDisplayMode(
50      DWORD mode_number, DWORD flags, DEVMODE* mode);
51
52  // Returns true if the mode has width, height, bits-per-pixel, frequency
53  // and orientation fields.
54  static bool IsModeValid(const DEVMODE& mode);
55
56  // Returns the width & height of |mode|, or 0x0 if they are missing.
57  static ScreenResolution GetModeResolution(const DEVMODE& mode);
58
59  std::map<ScreenResolution, DEVMODE> best_mode_for_resolution_;
60
61  DISALLOW_COPY_AND_ASSIGN(DesktopResizerWin);
62};
63
64DesktopResizerWin::DesktopResizerWin() {
65}
66
67DesktopResizerWin::~DesktopResizerWin() {
68}
69
70ScreenResolution DesktopResizerWin::GetCurrentResolution() {
71  DEVMODE current_mode;
72  if (GetPrimaryDisplayMode(ENUM_CURRENT_SETTINGS, 0, &current_mode) &&
73      IsModeValid(current_mode))
74    return GetModeResolution(current_mode);
75  return ScreenResolution();
76}
77
78std::list<ScreenResolution> DesktopResizerWin::GetSupportedResolutions(
79    const ScreenResolution& preferred) {
80  if (!IsResizeSupported())
81    return std::list<ScreenResolution>();
82
83  // Enumerate the resolutions to return, and where there are multiple modes of
84  // the same resolution, store the one most closely matching the current mode
85  // in |best_mode_for_resolution_|.
86  DEVMODE current_mode;
87  if (!GetPrimaryDisplayMode(ENUM_CURRENT_SETTINGS, 0, &current_mode) ||
88      !IsModeValid(current_mode))
89    return std::list<ScreenResolution>();
90
91  std::list<ScreenResolution> resolutions;
92  best_mode_for_resolution_.clear();
93  for (DWORD i = 0; ; ++i) {
94    DEVMODE candidate_mode;
95    if (!GetPrimaryDisplayMode(i, EDS_ROTATEDMODE, &candidate_mode))
96      break;
97
98    // Ignore modes missing the fields that we expect.
99    if (!IsModeValid(candidate_mode))
100      continue;
101
102    // Ignore modes with differing bits-per-pixel.
103    if (candidate_mode.dmBitsPerPel != current_mode.dmBitsPerPel)
104      continue;
105
106    // If there are multiple modes with the same dimensions:
107    // - Prefer the modes which match the current rotation.
108    // - Among those, prefer modes which match the current frequency.
109    // - Otherwise, prefer modes with a higher frequency.
110    ScreenResolution candidate_resolution = GetModeResolution(candidate_mode);
111    if (best_mode_for_resolution_.count(candidate_resolution) != 0) {
112      DEVMODE best_mode = best_mode_for_resolution_[candidate_resolution];
113
114      if ((candidate_mode.dmDisplayOrientation !=
115           current_mode.dmDisplayOrientation) &&
116          (best_mode.dmDisplayOrientation ==
117           current_mode.dmDisplayOrientation)) {
118        continue;
119      }
120
121      if ((candidate_mode.dmDisplayFrequency !=
122           current_mode.dmDisplayFrequency) &&
123          (best_mode.dmDisplayFrequency >=
124           candidate_mode.dmDisplayFrequency)) {
125        continue;
126      }
127    } else {
128      // If we haven't seen this resolution before, add it to those we return.
129      resolutions.push_back(candidate_resolution);
130    }
131
132    best_mode_for_resolution_[candidate_resolution] = candidate_mode;
133  }
134
135  return resolutions;
136}
137
138void DesktopResizerWin::SetResolution(const ScreenResolution& resolution) {
139  if (best_mode_for_resolution_.count(resolution) == 0)
140    return;
141
142  DEVMODE new_mode = best_mode_for_resolution_[resolution];
143  DWORD result = ChangeDisplaySettings(&new_mode, CDS_FULLSCREEN);
144  if (result != DISP_CHANGE_SUCCESSFUL)
145    LOG(ERROR) << "SetResolution failed: " << result;
146}
147
148void DesktopResizerWin::RestoreResolution(const ScreenResolution& original) {
149  // Restore the display mode based on the registry configuration.
150  DWORD result = ChangeDisplaySettings(NULL, 0);
151  if (result != DISP_CHANGE_SUCCESSFUL)
152    LOG(ERROR) << "RestoreResolution failed: " << result;
153}
154
155// static
156bool DesktopResizerWin::IsResizeSupported() {
157  // Resize is supported only on single-monitor systems.
158  return GetSystemMetrics(SM_CMONITORS) == 1;
159}
160
161// static
162bool DesktopResizerWin::GetPrimaryDisplayMode(
163    DWORD mode_number, DWORD flags, DEVMODE* mode) {
164 memset(mode, 0, sizeof(DEVMODE));
165 mode->dmSize = sizeof(DEVMODE);
166 if (!EnumDisplaySettingsEx(NULL, mode_number, mode, flags))
167   return false;
168 return true;
169}
170
171// static
172bool DesktopResizerWin::IsModeValid(const DEVMODE& mode) {
173  const DWORD kRequiredFields =
174      DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL |
175      DM_DISPLAYFREQUENCY | DM_DISPLAYORIENTATION;
176  return (mode.dmFields & kRequiredFields) == kRequiredFields;
177}
178
179// static
180ScreenResolution DesktopResizerWin::GetModeResolution(const DEVMODE& mode) {
181  DCHECK(IsModeValid(mode));
182  return ScreenResolution(
183      webrtc::DesktopSize(mode.dmPelsWidth, mode.dmPelsHeight),
184      webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
185}
186
187scoped_ptr<DesktopResizer> DesktopResizer::Create() {
188  return scoped_ptr<DesktopResizer>(new DesktopResizerWin);
189}
190
191}  // namespace remoting
192