OLD | NEW |
| (Empty) |
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 "ui/gfx/screen.h" | |
6 | |
7 #import <ApplicationServices/ApplicationServices.h> | |
8 #import <Cocoa/Cocoa.h> | |
9 | |
10 #include <map> | |
11 | |
12 #include "base/logging.h" | |
13 #include "base/mac/sdk_forward_declarations.h" | |
14 #include "base/timer/timer.h" | |
15 #include "ui/gfx/display.h" | |
16 #include "ui/gfx/display_change_notifier.h" | |
17 | |
18 namespace { | |
19 | |
20 // The delay to handle the display configuration changes. | |
21 // See comments in ScreenMac::HandleDisplayReconfiguration. | |
22 const int64 kConfigureDelayMs = 500; | |
23 | |
24 gfx::Rect ConvertCoordinateSystem(NSRect ns_rect) { | |
25 // Primary monitor is defined as the monitor with the menubar, | |
26 // which is always at index 0. | |
27 NSScreen* primary_screen = [[NSScreen screens] objectAtIndex:0]; | |
28 float primary_screen_height = [primary_screen frame].size.height; | |
29 gfx::Rect rect(NSRectToCGRect(ns_rect)); | |
30 rect.set_y(primary_screen_height - rect.y() - rect.height()); | |
31 return rect; | |
32 } | |
33 | |
34 NSScreen* GetMatchingScreen(const gfx::Rect& match_rect) { | |
35 // Default to the monitor with the current keyboard focus, in case | |
36 // |match_rect| is not on any screen at all. | |
37 NSScreen* max_screen = [NSScreen mainScreen]; | |
38 int max_area = 0; | |
39 | |
40 for (NSScreen* screen in [NSScreen screens]) { | |
41 gfx::Rect monitor_area = ConvertCoordinateSystem([screen frame]); | |
42 gfx::Rect intersection = gfx::IntersectRects(monitor_area, match_rect); | |
43 int area = intersection.width() * intersection.height(); | |
44 if (area > max_area) { | |
45 max_area = area; | |
46 max_screen = screen; | |
47 } | |
48 } | |
49 | |
50 return max_screen; | |
51 } | |
52 | |
53 gfx::Display GetDisplayForScreen(NSScreen* screen) { | |
54 NSRect frame = [screen frame]; | |
55 | |
56 CGDirectDisplayID display_id = [[[screen deviceDescription] | |
57 objectForKey:@"NSScreenNumber"] unsignedIntValue]; | |
58 | |
59 gfx::Display display(display_id, gfx::Rect(NSRectToCGRect(frame))); | |
60 NSRect visible_frame = [screen visibleFrame]; | |
61 NSScreen* primary = [[NSScreen screens] objectAtIndex:0]; | |
62 | |
63 // Convert work area's coordinate systems. | |
64 if ([screen isEqual:primary]) { | |
65 gfx::Rect work_area = gfx::Rect(NSRectToCGRect(visible_frame)); | |
66 work_area.set_y(frame.size.height - visible_frame.origin.y - | |
67 visible_frame.size.height); | |
68 display.set_work_area(work_area); | |
69 } else { | |
70 display.set_bounds(ConvertCoordinateSystem(frame)); | |
71 display.set_work_area(ConvertCoordinateSystem(visible_frame)); | |
72 } | |
73 CGFloat scale; | |
74 if ([screen respondsToSelector:@selector(backingScaleFactor)]) | |
75 scale = [screen backingScaleFactor]; | |
76 else | |
77 scale = [screen userSpaceScaleFactor]; | |
78 display.set_device_scale_factor(scale); | |
79 // CGDisplayRotation returns a double. Display::SetRotationAsDegree will | |
80 // handle the unexpected situations were the angle is not a multiple of 90. | |
81 display.SetRotationAsDegree(static_cast<int>(CGDisplayRotation(display_id))); | |
82 return display; | |
83 } | |
84 | |
85 class ScreenMac : public gfx::Screen { | |
86 public: | |
87 ScreenMac() { | |
88 displays_ = BuildDisplaysFromQuartz(); | |
89 | |
90 CGDisplayRegisterReconfigurationCallback( | |
91 ScreenMac::DisplayReconfigurationCallBack, this); | |
92 } | |
93 | |
94 bool IsDIPEnabled() override { return true; } | |
95 | |
96 gfx::Point GetCursorScreenPoint() override { | |
97 NSPoint mouseLocation = [NSEvent mouseLocation]; | |
98 // Flip coordinates to gfx (0,0 in top-left corner) using primary screen. | |
99 NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; | |
100 mouseLocation.y = NSMaxY([screen frame]) - mouseLocation.y; | |
101 return gfx::Point(mouseLocation.x, mouseLocation.y); | |
102 } | |
103 | |
104 gfx::NativeWindow GetWindowUnderCursor() override { | |
105 NOTIMPLEMENTED(); | |
106 return gfx::NativeWindow(); | |
107 } | |
108 | |
109 gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override { | |
110 NOTIMPLEMENTED(); | |
111 return gfx::NativeWindow(); | |
112 } | |
113 | |
114 int GetNumDisplays() const override { return GetAllDisplays().size(); } | |
115 | |
116 std::vector<gfx::Display> GetAllDisplays() const override { | |
117 return displays_; | |
118 } | |
119 | |
120 gfx::Display GetDisplayNearestWindow(gfx::NativeView view) const override { | |
121 NSWindow* window = nil; | |
122 #if !defined(USE_AURA) | |
123 if (view) | |
124 window = [view window]; | |
125 #endif | |
126 if (!window) | |
127 return GetPrimaryDisplay(); | |
128 NSScreen* match_screen = [window screen]; | |
129 if (!match_screen) | |
130 return GetPrimaryDisplay(); | |
131 return GetDisplayForScreen(match_screen); | |
132 } | |
133 | |
134 gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const override { | |
135 NSPoint ns_point = NSPointFromCGPoint(point.ToCGPoint()); | |
136 | |
137 NSArray* screens = [NSScreen screens]; | |
138 NSScreen* primary = [screens objectAtIndex:0]; | |
139 ns_point.y = NSMaxY([primary frame]) - ns_point.y; | |
140 for (NSScreen* screen in screens) { | |
141 if (NSMouseInRect(ns_point, [screen frame], NO)) | |
142 return GetDisplayForScreen(screen); | |
143 } | |
144 return GetPrimaryDisplay(); | |
145 } | |
146 | |
147 // Returns the display that most closely intersects the provided bounds. | |
148 gfx::Display GetDisplayMatching(const gfx::Rect& match_rect) const override { | |
149 NSScreen* match_screen = GetMatchingScreen(match_rect); | |
150 return GetDisplayForScreen(match_screen); | |
151 } | |
152 | |
153 // Returns the primary display. | |
154 gfx::Display GetPrimaryDisplay() const override { | |
155 // Primary display is defined as the display with the menubar, | |
156 // which is always at index 0. | |
157 NSScreen* primary = [[NSScreen screens] objectAtIndex:0]; | |
158 gfx::Display display = GetDisplayForScreen(primary); | |
159 return display; | |
160 } | |
161 | |
162 void AddObserver(gfx::DisplayObserver* observer) override { | |
163 change_notifier_.AddObserver(observer); | |
164 } | |
165 | |
166 void RemoveObserver(gfx::DisplayObserver* observer) override { | |
167 change_notifier_.RemoveObserver(observer); | |
168 } | |
169 | |
170 static void DisplayReconfigurationCallBack(CGDirectDisplayID display, | |
171 CGDisplayChangeSummaryFlags flags, | |
172 void* userInfo) { | |
173 if (flags & kCGDisplayBeginConfigurationFlag) | |
174 return; | |
175 | |
176 static_cast<ScreenMac*>(userInfo)->HandleDisplayReconfiguration(); | |
177 } | |
178 | |
179 void HandleDisplayReconfiguration() { | |
180 // Given that we need to rebuild the list of displays, we want to coalesce | |
181 // the events. For that, we use a timer that will be reset every time we get | |
182 // a new event and will be fulfilled kConfigureDelayMs after the latest. | |
183 if (configure_timer_.get() && configure_timer_->IsRunning()) { | |
184 configure_timer_->Reset(); | |
185 return; | |
186 } | |
187 | |
188 configure_timer_.reset(new base::OneShotTimer<ScreenMac>()); | |
189 configure_timer_->Start( | |
190 FROM_HERE, | |
191 base::TimeDelta::FromMilliseconds(kConfigureDelayMs), | |
192 this, | |
193 &ScreenMac::ConfigureTimerFired); | |
194 } | |
195 | |
196 private: | |
197 void ConfigureTimerFired() { | |
198 std::vector<gfx::Display> old_displays = displays_; | |
199 displays_ = BuildDisplaysFromQuartz(); | |
200 | |
201 change_notifier_.NotifyDisplaysChanged(old_displays, displays_); | |
202 } | |
203 | |
204 std::vector<gfx::Display> BuildDisplaysFromQuartz() const { | |
205 // Don't just return all online displays. This would include displays | |
206 // that mirror other displays, which are not desired in this list. It's | |
207 // tempting to use the count returned by CGGetActiveDisplayList, but active | |
208 // displays exclude sleeping displays, and those are desired. | |
209 | |
210 // It would be ridiculous to have this many displays connected, but | |
211 // CGDirectDisplayID is just an integer, so supporting up to this many | |
212 // doesn't hurt. | |
213 CGDirectDisplayID online_displays[128]; | |
214 CGDisplayCount online_display_count = 0; | |
215 if (CGGetOnlineDisplayList(arraysize(online_displays), | |
216 online_displays, | |
217 &online_display_count) != kCGErrorSuccess) { | |
218 return std::vector<gfx::Display>(1, GetPrimaryDisplay()); | |
219 } | |
220 | |
221 typedef std::map<int64, NSScreen*> ScreenIdsToScreensMap; | |
222 ScreenIdsToScreensMap screen_ids_to_screens; | |
223 for (NSScreen* screen in [NSScreen screens]) { | |
224 NSDictionary* screen_device_description = [screen deviceDescription]; | |
225 int64 screen_id = [[screen_device_description | |
226 objectForKey:@"NSScreenNumber"] unsignedIntValue]; | |
227 screen_ids_to_screens[screen_id] = screen; | |
228 } | |
229 | |
230 std::vector<gfx::Display> displays; | |
231 for (CGDisplayCount online_display_index = 0; | |
232 online_display_index < online_display_count; | |
233 ++online_display_index) { | |
234 CGDirectDisplayID online_display = online_displays[online_display_index]; | |
235 if (CGDisplayMirrorsDisplay(online_display) == kCGNullDirectDisplay) { | |
236 // If this display doesn't mirror any other, include it in the list. | |
237 // The primary display in a mirrored set will be counted, but those that | |
238 // mirror it will not be. | |
239 ScreenIdsToScreensMap::iterator foundScreen = | |
240 screen_ids_to_screens.find(online_display); | |
241 if (foundScreen != screen_ids_to_screens.end()) { | |
242 displays.push_back(GetDisplayForScreen(foundScreen->second)); | |
243 } | |
244 } | |
245 } | |
246 | |
247 if (!displays.size()) | |
248 return std::vector<gfx::Display>(1, GetPrimaryDisplay()); | |
249 | |
250 return displays; | |
251 } | |
252 | |
253 // The displays currently attached to the device. | |
254 std::vector<gfx::Display> displays_; | |
255 | |
256 // The timer to delay configuring outputs. See also the comments in | |
257 // HandleDisplayReconfiguration(). | |
258 scoped_ptr<base::OneShotTimer<ScreenMac> > configure_timer_; | |
259 | |
260 gfx::DisplayChangeNotifier change_notifier_; | |
261 | |
262 DISALLOW_COPY_AND_ASSIGN(ScreenMac); | |
263 }; | |
264 | |
265 } // namespace | |
266 | |
267 namespace gfx { | |
268 | |
269 #if !defined(USE_AURA) | |
270 Screen* CreateNativeScreen() { | |
271 return new ScreenMac; | |
272 } | |
273 #endif | |
274 | |
275 } | |
OLD | NEW |