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 <Carbon/Carbon.h>
8
9#include "base/basictypes.h"
10#include "base/mac/foundation_util.h"
11#include "base/mac/mac_util.h"
12#include "base/mac/scoped_cftyperef.h"
13#include "remoting/base/logging.h"
14
15namespace {
16// TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
17const int kDefaultDPI = 96;
18}  // namespace
19
20namespace remoting {
21
22class DesktopResizerMac : public DesktopResizer {
23 public:
24  DesktopResizerMac();
25
26  // DesktopResizer interface
27  virtual ScreenResolution GetCurrentResolution() OVERRIDE;
28  virtual std::list<ScreenResolution> GetSupportedResolutions(
29      const ScreenResolution& preferred) OVERRIDE;
30  virtual void SetResolution(const ScreenResolution& resolution) OVERRIDE;
31  virtual void RestoreResolution(const ScreenResolution& original) OVERRIDE;
32
33 private:
34  // If there is a single display, get its id and return true, otherwise return
35  // false. We don't currently support resize-to-client on multi-monitor Macs.
36  bool GetSoleDisplayId(CGDirectDisplayID* display);
37
38  void GetSupportedModesAndResolutions(
39      base::ScopedCFTypeRef<CFMutableArrayRef>* modes,
40      std::list<ScreenResolution>* resolutions);
41
42  DISALLOW_COPY_AND_ASSIGN(DesktopResizerMac);
43};
44
45DesktopResizerMac::DesktopResizerMac() {}
46
47ScreenResolution DesktopResizerMac::GetCurrentResolution() {
48  CGDirectDisplayID display;
49  if (!base::mac::IsOSSnowLeopard() && GetSoleDisplayId(&display)) {
50    CGRect rect = CGDisplayBounds(display);
51    return ScreenResolution(
52        webrtc::DesktopSize(rect.size.width, rect.size.height),
53        webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
54  }
55  return ScreenResolution();
56}
57
58std::list<ScreenResolution> DesktopResizerMac::GetSupportedResolutions(
59    const ScreenResolution& preferred) {
60  base::ScopedCFTypeRef<CFMutableArrayRef> modes;
61  std::list<ScreenResolution> resolutions;
62  GetSupportedModesAndResolutions(&modes, &resolutions);
63  return resolutions;
64}
65
66void DesktopResizerMac::SetResolution(const ScreenResolution& resolution) {
67  CGDirectDisplayID display;
68  if (base::mac::IsOSSnowLeopard() || !GetSoleDisplayId(&display)) {
69    return;
70  }
71
72  base::ScopedCFTypeRef<CFMutableArrayRef> modes;
73  std::list<ScreenResolution> resolutions;
74  GetSupportedModesAndResolutions(&modes, &resolutions);
75  // There may be many modes with the requested resolution. Pick the one with
76  // the highest color depth.
77  int index = 0, best_depth = 0;
78  CGDisplayModeRef best_mode = NULL;
79  for (std::list<ScreenResolution>::const_iterator i = resolutions.begin();
80       i != resolutions.end(); ++i, ++index) {
81    if (i->Equals(resolution)) {
82      CGDisplayModeRef mode = const_cast<CGDisplayModeRef>(
83          static_cast<const CGDisplayMode*>(
84              CFArrayGetValueAtIndex(modes, index)));
85      int depth = 0;
86      base::ScopedCFTypeRef<CFStringRef> encoding(
87          CGDisplayModeCopyPixelEncoding(mode));
88      if (CFStringCompare(encoding, CFSTR(IO32BitDirectPixels),
89                          kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
90        depth = 32;
91      } else if (CFStringCompare(
92          encoding, CFSTR(IO16BitDirectPixels),
93          kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
94        depth = 16;
95      } else if(CFStringCompare(
96          encoding, CFSTR(IO8BitIndexedPixels),
97          kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
98        depth = 8;
99      }
100      if (depth > best_depth) {
101        best_depth = depth;
102        best_mode = mode;
103      }
104    }
105  }
106  if (best_mode) {
107    HOST_LOG << "Changing mode to " << best_mode << " ("
108              << resolution.dimensions().width() << "x"
109              << "x" << resolution.dimensions().height() << "x"
110              << best_depth << " @ "
111              << resolution.dpi().x() << "x" << resolution.dpi().y() << " dpi)";
112    CGDisplaySetDisplayMode(display, best_mode, NULL);
113  }
114}
115
116void DesktopResizerMac::RestoreResolution(const ScreenResolution& original) {
117  SetResolution(original);
118}
119
120void DesktopResizerMac::GetSupportedModesAndResolutions(
121    base::ScopedCFTypeRef<CFMutableArrayRef>* modes,
122    std::list<ScreenResolution>* resolutions) {
123  CGDirectDisplayID display;
124  if (!GetSoleDisplayId(&display)) {
125    return;
126  }
127
128  base::ScopedCFTypeRef<CFArrayRef> all_modes(
129      CGDisplayCopyAllDisplayModes(display, NULL));
130  if (!all_modes) {
131    return;
132  }
133
134  modes->reset(CFArrayCreateMutableCopy(NULL, 0, all_modes));
135  CFIndex count = CFArrayGetCount(*modes);
136  for (CFIndex i = 0; i < count; ++i) {
137    CGDisplayModeRef mode = const_cast<CGDisplayModeRef>(
138        static_cast<const CGDisplayMode*>(
139            CFArrayGetValueAtIndex(*modes, i)));
140    if (CGDisplayModeIsUsableForDesktopGUI(mode)) {
141      // TODO(jamiewalch): Get the correct DPI: http://crbug.com/172405.
142      ScreenResolution resolution(
143          webrtc::DesktopSize(CGDisplayModeGetWidth(mode),
144                              CGDisplayModeGetHeight(mode)),
145          webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
146      resolutions->push_back(resolution);
147    } else {
148      CFArrayRemoveValueAtIndex(*modes, i);
149      --count;
150      --i;
151    }
152  }
153}
154
155bool DesktopResizerMac::GetSoleDisplayId(CGDirectDisplayID* display) {
156  // This code only supports a single display, but allocates space for two
157  // to allow the multi-monitor case to be detected.
158  CGDirectDisplayID displays[2];
159  uint32_t num_displays;
160  CGError err = CGGetActiveDisplayList(arraysize(displays),
161                                       displays, &num_displays);
162  if (err != kCGErrorSuccess || num_displays != 1) {
163    return false;
164  }
165  *display = displays[0];
166  return true;
167}
168
169scoped_ptr<DesktopResizer> DesktopResizer::Create() {
170  return scoped_ptr<DesktopResizer>(new DesktopResizerMac);
171}
172
173}  // namespace remoting
174