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 "chromeos/monitor/output_configurator.h" | |
6 | |
7 #include <X11/Xlib.h> | |
8 #include <X11/extensions/dpms.h> | |
9 #include <X11/extensions/Xrandr.h> | |
10 | |
11 // Xlib defines Status as int which causes our include of dbus/bus.h to fail | |
12 // when it tries to name an enum Status. Thus, we need to undefine it (note | |
13 // that this will cause a problem if code needs to use the Status type). | |
14 // RootWindow causes similar problems in that there is a Chromium type with that | |
15 // name. | |
16 #undef Status | |
17 #undef RootWindow | |
18 | |
19 #include "base/chromeos/chromeos_version.h" | |
20 #include "base/logging.h" | |
21 #include "base/message_pump_aurax11.h" | |
22 #include "chromeos/dbus/dbus_thread_manager.h" | |
23 #include "dbus/bus.h" | |
24 #include "dbus/exported_object.h" | |
25 #include "dbus/message.h" | |
26 #include "dbus/object_path.h" | |
27 #include "dbus/object_proxy.h" | |
28 #include "third_party/cros_system_api/dbus/service_constants.h" | |
29 | |
30 namespace chromeos { | |
31 | |
32 namespace { | |
33 // DPI measurements. | |
34 const float kMmInInch = 25.4; | |
35 const float kDpi96 = 96.0; | |
36 const float kPixelsToMmScale = kMmInInch / kDpi96; | |
37 | |
38 // The DPI threshold to detech high density screen. | |
39 // Higher DPI than this will use device_scale_factor=2 | |
40 // Should be kept in sync with monitor_change_observer_x11.cc | |
41 const unsigned int kHighDensityDIPThreshold = 160; | |
42 | |
43 // Prefixes for the built-in displays. | |
44 const char kInternal_LVDS[] = "LVDS"; | |
45 const char kInternal_eDP[] = "eDP"; | |
46 | |
47 // Gap between screens so cursor at bottom of active monitor doesn't partially | |
48 // appear on top of inactive monitor. Higher numbers guard against larger | |
49 // cursors, but also waste more memory. We will double this gap for screens | |
50 // with a device_scale_factor of 2. While this gap will not guard against all | |
51 // possible cursors in X, it should handle the ones we actually use. See | |
52 // crbug.com/130188 | |
53 const int kVerticalGap = 30; | |
54 | |
55 // TODO: Determine if we need to organize modes in a way which provides better | |
56 // than O(n) lookup time. In many call sites, for example, the "next" mode is | |
57 // typically what we are looking for so using this helper might be too | |
58 // expensive. | |
59 static XRRModeInfo* ModeInfoForID(XRRScreenResources* screen, RRMode modeID) { | |
60 XRRModeInfo* result = NULL; | |
61 for (int i = 0; (i < screen->nmode) && (result == NULL); i++) | |
62 if (modeID == screen->modes[i].id) | |
63 result = &screen->modes[i]; | |
64 | |
65 // We can't fail to find a mode referenced from the same screen. | |
66 CHECK(result != NULL); | |
67 return result; | |
68 } | |
69 | |
70 // Identifies the modes which will be used by the respective outputs when in a | |
71 // mirror mode. This means that the two modes will have the same resolution. | |
72 // The RROutput IDs |one| and |two| are used to look up the modes and | |
73 // |out_one_mode| and |out_two_mode| are the out-parameters for the respective | |
74 // modes. | |
75 // Returns false if it fails to find a compatible set of modes. | |
76 static bool FindMirrorModeForOutputs(Display* display, | |
77 XRRScreenResources* screen, | |
78 RROutput one, | |
79 RROutput two, | |
80 RRMode* out_one_mode, | |
81 RRMode* out_two_mode) { | |
82 XRROutputInfo* primary = XRRGetOutputInfo(display, screen, one); | |
83 XRROutputInfo* secondary = XRRGetOutputInfo(display, screen, two); | |
84 | |
85 int one_index = 0; | |
86 int two_index = 0; | |
87 bool found = false; | |
88 while (!found && | |
89 (one_index < primary->nmode) && | |
90 (two_index < secondary->nmode)) { | |
91 RRMode one_id = primary->modes[one_index]; | |
92 RRMode two_id = secondary->modes[two_index]; | |
93 XRRModeInfo* one_mode = ModeInfoForID(screen, one_id); | |
94 XRRModeInfo* two_mode = ModeInfoForID(screen, two_id); | |
95 int one_width = one_mode->width; | |
96 int one_height = one_mode->height; | |
97 int two_width = two_mode->width; | |
98 int two_height = two_mode->height; | |
99 if ((one_width == two_width) && (one_height == two_height)) { | |
100 *out_one_mode = one_id; | |
101 *out_two_mode = two_id; | |
102 found = true; | |
103 } else { | |
104 // The sort order of the modes is NOT by mode area but is sorted by width, | |
105 // then by height within each like width. | |
106 if (one_width > two_width) { | |
107 one_index += 1; | |
108 } else if (one_width < two_width) { | |
109 two_index += 1; | |
110 } else { | |
111 if (one_height > two_height) { | |
112 one_index += 1; | |
113 } else { | |
114 two_index += 1; | |
115 } | |
116 } | |
117 } | |
118 } | |
119 XRRFreeOutputInfo(primary); | |
120 XRRFreeOutputInfo(secondary); | |
121 return found; | |
122 } | |
123 | |
124 // A helper to call XRRSetCrtcConfig with the given options but some of our | |
125 // default output count and rotation arguments. | |
126 static void ConfigureCrtc(Display *display, | |
127 XRRScreenResources* screen, | |
128 RRCrtc crtc, | |
129 int x, | |
130 int y, | |
131 RRMode mode, | |
132 RROutput output) { | |
133 const Rotation kRotate = RR_Rotate_0; | |
134 RROutput* outputs = NULL; | |
135 int num_outputs = 0; | |
136 | |
137 // Check the output and mode argument - if either are None, we should disable. | |
138 if ((output != None) && (mode != None)) { | |
139 outputs = &output; | |
140 num_outputs = 1; | |
141 } | |
142 | |
143 XRRSetCrtcConfig(display, | |
144 screen, | |
145 crtc, | |
146 CurrentTime, | |
147 x, | |
148 y, | |
149 mode, | |
150 kRotate, | |
151 outputs, | |
152 num_outputs); | |
153 if (num_outputs == 1) { | |
154 // We are enabling a display so make sure it is turned on. | |
155 CHECK(DPMSEnable(display)); | |
156 CHECK(DPMSForceLevel(display, DPMSModeOn)); | |
157 } | |
158 } | |
159 | |
160 // Called to set the frame buffer (underling XRR "screen") size. Has a | |
161 // side-effect of disabling all CRTCs. | |
162 static void CreateFrameBuffer(Display* display, | |
163 XRRScreenResources* screen, | |
164 Window window, | |
165 int width, | |
166 int height) { | |
167 // Note that setting the screen size fails if any CRTCs are currently | |
168 // pointing into it so disable them all. | |
169 for (int i = 0; i < screen->ncrtc; ++i) { | |
170 const int x = 0; | |
171 const int y = 0; | |
172 const RRMode kMode = None; | |
173 const RROutput kOutput = None; | |
174 | |
175 ConfigureCrtc(display, | |
176 screen, | |
177 screen->crtcs[i], | |
178 x, | |
179 y, | |
180 kMode, | |
181 kOutput); | |
182 } | |
183 int mm_width = width * kPixelsToMmScale; | |
184 int mm_height = height * kPixelsToMmScale; | |
185 XRRSetScreenSize(display, window, width, height, mm_width, mm_height); | |
186 } | |
187 | |
188 // A helper to get the current CRTC, Mode, and height for a given output. This | |
189 // is read from the XRandR configuration and not any of our caches. | |
190 static void GetOutputConfiguration(Display* display, | |
191 XRRScreenResources* screen, | |
192 RROutput output, | |
193 RRCrtc* crtc, | |
194 RRMode* mode, | |
195 int* height) { | |
196 XRROutputInfo* output_info = XRRGetOutputInfo(display, screen, output); | |
197 CHECK(output_info != NULL); | |
198 *crtc = output_info->crtc; | |
199 XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(display, screen, *crtc); | |
200 if (crtc_info != NULL) { | |
201 *mode = crtc_info->mode; | |
202 *height = crtc_info->height; | |
203 XRRFreeCrtcInfo(crtc_info); | |
204 } | |
205 XRRFreeOutputInfo(output_info); | |
206 } | |
207 | |
208 // A helper to determine the device_scale_factor given pixel width and mm_width. | |
209 // This currently only reports two scale factors (1.0 and 2.0) | |
210 static float ComputeDeviceScaleFactor(unsigned int width, | |
211 unsigned long mm_width) { | |
212 float device_scale_factor = 1.0f; | |
213 if (mm_width > 0 && (kMmInInch * width / mm_width) > kHighDensityDIPThreshold) | |
214 device_scale_factor = 2.0f; | |
215 return device_scale_factor; | |
216 } | |
217 | |
218 } // namespace | |
219 | |
220 bool OutputConfigurator::TryRecacheOutputs(Display* display, | |
221 XRRScreenResources* screen) { | |
222 bool outputs_did_change = false; | |
223 int previous_connected_count = 0; | |
224 int new_connected_count = 0; | |
225 | |
226 if (output_count_ != screen->noutput) { | |
227 outputs_did_change = true; | |
228 } else { | |
229 // The outputs might have changed so compare the connected states in the | |
230 // screen to our existing cache. | |
231 for (int i = 0; (i < output_count_) && !outputs_did_change; ++i) { | |
232 RROutput thisID = screen->outputs[i]; | |
233 XRROutputInfo* output = XRRGetOutputInfo(display, screen, thisID); | |
234 bool now_connected = (RR_Connected == output->connection); | |
235 outputs_did_change = (now_connected != output_cache_[i].is_connected); | |
236 XRRFreeOutputInfo(output); | |
237 | |
238 if (output_cache_[i].is_connected) | |
239 previous_connected_count += 1; | |
240 if (now_connected) | |
241 new_connected_count += 1; | |
242 } | |
243 } | |
244 | |
245 if (outputs_did_change) { | |
246 // We now know that we need to recache so free and re-alloc the buffer. | |
247 output_count_ = screen->noutput; | |
248 if (output_count_ == 0) { | |
249 output_cache_.reset(NULL); | |
250 } else { | |
251 // Ideally, this would be allocated inline in the OutputConfigurator | |
252 // instance since we support at most 2 connected outputs but this dynamic | |
253 // allocation was specifically requested. | |
254 output_cache_.reset(new CachedOutputDescription[output_count_]); | |
255 } | |
256 | |
257 // TODO: This approach to finding CRTCs only supports two. Expand on this. | |
258 RRCrtc used_crtc = None; | |
259 primary_output_index_ = -1; | |
260 secondary_output_index_ = -1; | |
261 | |
262 for (int i = 0; i < output_count_; ++i) { | |
263 RROutput this_id = screen->outputs[i]; | |
264 XRROutputInfo* output = XRRGetOutputInfo(display, screen, this_id); | |
265 bool is_connected = (RR_Connected == output->connection); | |
266 RRCrtc crtc = None; | |
267 RRMode ideal_mode = None; | |
268 int x = 0; | |
269 int y = 0; | |
270 unsigned long mm_width = output->mm_width; | |
271 unsigned long mm_height = output->mm_height; | |
272 bool is_internal = false; | |
273 | |
274 if (is_connected) { | |
275 for (int j = 0; (j < output->ncrtc) && (None == crtc); ++j) { | |
276 RRCrtc possible = output->crtcs[j]; | |
277 if (possible != used_crtc) { | |
278 crtc = possible; | |
279 used_crtc = possible; | |
280 } | |
281 } | |
282 | |
283 const char* name = output->name; | |
284 is_internal = | |
285 (strncmp(kInternal_LVDS, | |
286 name, | |
287 arraysize(kInternal_LVDS) - 1) == 0) || | |
288 (strncmp(kInternal_eDP, | |
289 name, | |
290 arraysize(kInternal_eDP) - 1) == 0); | |
291 if (output->nmode > 0) | |
292 ideal_mode = output->modes[0]; | |
293 | |
294 if (crtc != None) { | |
295 XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screen, crtc); | |
296 x = crtcInfo->x; | |
297 y = crtcInfo->y; | |
298 XRRFreeCrtcInfo(crtcInfo); | |
299 } | |
300 | |
301 // Save this for later mirror mode detection. | |
302 if (primary_output_index_ == -1) | |
303 primary_output_index_ = i; | |
304 else if (secondary_output_index_ == -1) | |
305 secondary_output_index_ = i; | |
306 } | |
307 XRRFreeOutputInfo(output); | |
308 | |
309 // Now save the cached state for this output (we will default to mirror | |
310 // disabled and detect that after we have identified the first two | |
311 // connected outputs). | |
312 VLOG(1) << "Recache output index: " << i | |
313 << ", output id: " << this_id | |
314 << ", crtc id: " << crtc | |
315 << ", ideal mode id: " << ideal_mode | |
316 << ", x: " << x | |
317 << ", y: " << y | |
318 << ", is connected: " << is_connected | |
319 << ", is_internal: " << is_internal | |
320 << ", mm_width: " << mm_width | |
321 << ", mm_height: " << mm_height; | |
322 output_cache_[i].output = this_id; | |
323 output_cache_[i].crtc = crtc; | |
324 output_cache_[i].mirror_mode = None; | |
325 output_cache_[i].ideal_mode = ideal_mode; | |
326 output_cache_[i].x = x; | |
327 output_cache_[i].y = y; | |
328 output_cache_[i].is_connected = is_connected; | |
329 output_cache_[i].is_powered_on = true; | |
330 output_cache_[i].is_internal = is_internal; | |
331 output_cache_[i].mm_width = mm_width; | |
332 output_cache_[i].mm_height = mm_height; | |
333 } | |
334 | |
335 // Now, detect the mirror modes if we have two connected outputs. | |
336 if ((primary_output_index_ != -1) && (secondary_output_index_ != -1)) { | |
337 mirror_supported_ = FindMirrorModeForOutputs( | |
338 display, | |
339 screen, | |
340 output_cache_[primary_output_index_].output, | |
341 output_cache_[secondary_output_index_].output, | |
342 &output_cache_[primary_output_index_].mirror_mode, | |
343 &output_cache_[secondary_output_index_].mirror_mode); | |
344 | |
345 RRMode primary_mode = output_cache_[primary_output_index_].mirror_mode; | |
346 RRMode second_mode = output_cache_[secondary_output_index_].mirror_mode; | |
347 VLOG(1) << "Mirror mode supported " << mirror_supported_ | |
348 << " primary " << primary_mode | |
349 << " secondary " << second_mode; | |
350 } | |
351 } | |
352 return outputs_did_change; | |
353 } | |
354 | |
355 OutputConfigurator::OutputConfigurator() | |
356 : is_running_on_chrome_os_(base::chromeos::IsRunningOnChromeOS()), | |
357 output_count_(0), | |
358 output_cache_(NULL), | |
359 mirror_supported_(false), | |
360 primary_output_index_(-1), | |
361 secondary_output_index_(-1), | |
362 xrandr_event_base_(0), | |
363 output_state_(STATE_INVALID) { | |
364 if (is_running_on_chrome_os_) { | |
365 // Send the signal to powerd to tell it that we will take over output | |
366 // control. | |
367 // Note that this can be removed once the legacy powerd support is removed. | |
368 chromeos::DBusThreadManager* manager = chromeos::DBusThreadManager::Get(); | |
369 dbus::Bus* bus = manager->GetSystemBus(); | |
370 dbus::ExportedObject* remote_object = bus->GetExportedObject( | |
371 dbus::ObjectPath(power_manager::kPowerManagerServicePath)); | |
372 dbus::Signal signal(power_manager::kPowerManagerInterface, | |
373 power_manager::kUseNewMonitorConfigSignal); | |
374 CHECK(signal.raw_message() != NULL); | |
375 remote_object->SendSignal(&signal); | |
376 | |
377 // Cache the initial output state. | |
378 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
379 CHECK(display != NULL); | |
380 XGrabServer(display); | |
381 Window window = DefaultRootWindow(display); | |
382 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
383 CHECK(screen != NULL); | |
384 bool did_detect_outputs = TryRecacheOutputs(display, screen); | |
385 CHECK(did_detect_outputs); | |
386 State current_state = InferCurrentState(display, screen); | |
387 if (current_state == STATE_INVALID) { | |
388 // Unknown state. Transition into the default state. | |
389 State state = GetDefaultState(); | |
390 UpdateCacheAndXrandrToState(display, screen, window, state); | |
391 } else { | |
392 // This is a valid state so just save it to |output_state_|. | |
393 output_state_ = current_state; | |
394 } | |
395 // Find xrandr_event_base_ since we need it to interpret events, later. | |
396 int error_base_ignored = 0; | |
397 XRRQueryExtension(display, &xrandr_event_base_, &error_base_ignored); | |
398 // Relinquish X resources. | |
399 XRRFreeScreenResources(screen); | |
400 XUngrabServer(display); | |
401 CheckIsProjectingAndNotify(); | |
402 } | |
403 } | |
404 | |
405 OutputConfigurator::~OutputConfigurator() { | |
406 } | |
407 | |
408 void OutputConfigurator::UpdateCacheAndXrandrToState( | |
409 Display* display, | |
410 XRRScreenResources* screen, | |
411 Window window, | |
412 State new_state) { | |
413 // Default rules: | |
414 // - single display = rebuild framebuffer and set to ideal_mode. | |
415 // - multi display = rebuild framebuffer and set to mirror_mode. | |
416 | |
417 // First, calculate the width and height of the framebuffer (we could retain | |
418 // the existing buffer, if it isn't resizing, but that causes an odd display | |
419 // state where the CRTCs are repositioned over the root windows before Chrome | |
420 // can move them). It is a feature worth considering, though, and wouldn't | |
421 // be difficult to implement (just check the current framebuffer size before | |
422 // changing it). | |
423 int width = 0; | |
424 int height = 0; | |
425 int primary_height = 0; | |
426 int secondary_height = 0; | |
427 int vertical_gap = 0; | |
428 if (new_state == STATE_SINGLE) { | |
429 CHECK_NE(-1, primary_output_index_); | |
430 | |
431 XRRModeInfo* ideal_mode = ModeInfoForID( | |
432 screen, | |
433 output_cache_[primary_output_index_].ideal_mode); | |
434 width = ideal_mode->width; | |
435 height = ideal_mode->height; | |
436 } else if (new_state == STATE_DUAL_MIRROR) { | |
437 CHECK_NE(-1, primary_output_index_); | |
438 CHECK_NE(-1, secondary_output_index_); | |
439 | |
440 XRRModeInfo* mirror_mode = ModeInfoForID( | |
441 screen, | |
442 output_cache_[primary_output_index_].mirror_mode); | |
443 width = mirror_mode->width; | |
444 height = mirror_mode->height; | |
445 } else if ((new_state == STATE_DUAL_PRIMARY_ONLY) || | |
446 (new_state == STATE_DUAL_SECONDARY_ONLY)) { | |
447 CHECK_NE(-1, primary_output_index_); | |
448 CHECK_NE(-1, secondary_output_index_); | |
449 | |
450 XRRModeInfo* one_ideal = ModeInfoForID( | |
451 screen, | |
452 output_cache_[primary_output_index_].ideal_mode); | |
453 XRRModeInfo* two_ideal = ModeInfoForID( | |
454 screen, | |
455 output_cache_[secondary_output_index_].ideal_mode); | |
456 | |
457 // Compute the device scale factor for the topmost display. We only need | |
458 // to take this device's scale factor into account as we are creating a gap | |
459 // to avoid the cursor drawing onto the second (unused) display when the | |
460 // cursor is near the bottom of the topmost display. | |
461 float top_scale_factor; | |
462 if (new_state == STATE_DUAL_PRIMARY_ONLY) { | |
463 top_scale_factor = ComputeDeviceScaleFactor(one_ideal->width, | |
464 output_cache_[primary_output_index_].mm_width); | |
465 } else { | |
466 top_scale_factor = ComputeDeviceScaleFactor(two_ideal->width, | |
467 output_cache_[secondary_output_index_].mm_width); | |
468 } | |
469 vertical_gap = kVerticalGap * top_scale_factor; | |
470 | |
471 width = std::max<int>(one_ideal->width, two_ideal->width); | |
472 height = one_ideal->height + two_ideal->height + vertical_gap; | |
473 primary_height = one_ideal->height; | |
474 secondary_height = two_ideal->height; | |
475 } | |
476 CreateFrameBuffer(display, screen, window, width, height); | |
477 | |
478 // Now, tile the outputs appropriately. | |
479 const int x = 0; | |
480 const int y = 0; | |
481 switch (new_state) { | |
482 case STATE_SINGLE: | |
483 ConfigureCrtc(display, | |
484 screen, | |
485 output_cache_[primary_output_index_].crtc, | |
486 x, | |
487 y, | |
488 output_cache_[primary_output_index_].ideal_mode, | |
489 output_cache_[primary_output_index_].output); | |
490 break; | |
491 case STATE_DUAL_MIRROR: | |
492 case STATE_DUAL_PRIMARY_ONLY: | |
493 case STATE_DUAL_SECONDARY_ONLY: { | |
494 RRMode primary_mode = output_cache_[primary_output_index_].mirror_mode; | |
495 RRMode secondary_mode = | |
496 output_cache_[secondary_output_index_].mirror_mode; | |
497 int primary_y = y; | |
498 int secondary_y = y; | |
499 | |
500 if (new_state != STATE_DUAL_MIRROR) { | |
501 primary_mode = output_cache_[primary_output_index_].ideal_mode; | |
502 secondary_mode = output_cache_[secondary_output_index_].ideal_mode; | |
503 } | |
504 if (new_state == STATE_DUAL_PRIMARY_ONLY) | |
505 secondary_y = y + primary_height + vertical_gap; | |
506 if (new_state == STATE_DUAL_SECONDARY_ONLY) | |
507 primary_y = y + secondary_height + vertical_gap; | |
508 | |
509 ConfigureCrtc(display, | |
510 screen, | |
511 output_cache_[primary_output_index_].crtc, | |
512 x, | |
513 primary_y, | |
514 primary_mode, | |
515 output_cache_[primary_output_index_].output); | |
516 ConfigureCrtc(display, | |
517 screen, | |
518 output_cache_[secondary_output_index_].crtc, | |
519 x, | |
520 secondary_y, | |
521 secondary_mode, | |
522 output_cache_[secondary_output_index_].output); | |
523 } | |
524 break; | |
525 case STATE_HEADLESS: | |
526 // Do nothing. | |
527 break; | |
528 default: | |
529 NOTREACHED() << "Unhandled state " << new_state; | |
530 } | |
531 output_state_ = new_state; | |
532 } | |
533 | |
534 bool OutputConfigurator::RecacheAndUseDefaultState() { | |
535 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
536 CHECK(display != NULL); | |
537 XGrabServer(display); | |
538 Window window = DefaultRootWindow(display); | |
539 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
540 CHECK(screen != NULL); | |
541 | |
542 bool did_detect_change = TryRecacheOutputs(display, screen); | |
543 if (did_detect_change) { | |
544 State state = GetDefaultState(); | |
545 UpdateCacheAndXrandrToState(display, screen, window, state); | |
546 } | |
547 XRRFreeScreenResources(screen); | |
548 XUngrabServer(display); | |
549 return did_detect_change; | |
550 } | |
551 | |
552 State OutputConfigurator::GetDefaultState() const { | |
553 State state = STATE_HEADLESS; | |
554 if (-1 != primary_output_index_) { | |
555 if (-1 != secondary_output_index_) | |
556 state = mirror_supported_ ? STATE_DUAL_MIRROR : STATE_DUAL_PRIMARY_ONLY; | |
557 else | |
558 state = STATE_SINGLE; | |
559 } | |
560 return state; | |
561 } | |
562 | |
563 State OutputConfigurator::InferCurrentState(Display* display, | |
564 XRRScreenResources* screen) const { | |
565 // STATE_INVALID will be our default or "unknown" state. | |
566 State state = STATE_INVALID; | |
567 // First step: count the number of connected outputs. | |
568 if (secondary_output_index_ == -1) { | |
569 // No secondary display. | |
570 if (primary_output_index_ == -1) { | |
571 // No primary display implies HEADLESS. | |
572 state = STATE_HEADLESS; | |
573 } else { | |
574 // The common case of primary-only. | |
575 // The only sanity check we require in this case is that the current mode | |
576 // of the output's CRTC is the ideal mode we determined for it. | |
577 RRCrtc primary_crtc = None; | |
578 RRMode primary_mode = None; | |
579 int primary_height = 0; | |
580 GetOutputConfiguration(display, | |
581 screen, | |
582 output_cache_[primary_output_index_].output, | |
583 &primary_crtc, | |
584 &primary_mode, | |
585 &primary_height); | |
586 if (primary_mode == output_cache_[primary_output_index_].ideal_mode) | |
587 state = STATE_SINGLE; | |
588 } | |
589 } else { | |
590 // We have two displays attached so we need to look at their configuration. | |
591 // Note that, for simplicity, we will only detect the states that we would | |
592 // have used and will assume anything unexpected is INVALID (which should | |
593 // not happen in any expected usage scenario). | |
594 RRCrtc primary_crtc = None; | |
595 RRMode primary_mode = None; | |
596 int primary_height = 0; | |
597 GetOutputConfiguration(display, | |
598 screen, | |
599 output_cache_[primary_output_index_].output, | |
600 &primary_crtc, | |
601 &primary_mode, | |
602 &primary_height); | |
603 RRCrtc secondary_crtc = None; | |
604 RRMode secondary_mode = None; | |
605 int secondary_height = 0; | |
606 GetOutputConfiguration(display, | |
607 screen, | |
608 output_cache_[secondary_output_index_].output, | |
609 &secondary_crtc, | |
610 &secondary_mode, | |
611 &secondary_height); | |
612 // Make sure the CRTCs are matched to the expected outputs. | |
613 if ((output_cache_[primary_output_index_].crtc == primary_crtc) && | |
614 (output_cache_[secondary_output_index_].crtc == secondary_crtc)) { | |
615 // Check the mode matching: either both mirror or both ideal. | |
616 if ((output_cache_[primary_output_index_].mirror_mode == primary_mode) && | |
617 (output_cache_[secondary_output_index_].mirror_mode == | |
618 secondary_mode)) { | |
619 // We are already in mirror mode. | |
620 state = STATE_DUAL_MIRROR; | |
621 } else if ((output_cache_[primary_output_index_].ideal_mode == | |
622 primary_mode) && | |
623 (output_cache_[secondary_output_index_].ideal_mode == | |
624 secondary_mode)) { | |
625 // Both outputs are in their "ideal" mode so check their Y-offsets to | |
626 // see which "ideal" configuration this is. | |
627 if (primary_height == output_cache_[secondary_output_index_].y) { | |
628 // Secondary is tiled first. | |
629 state = STATE_DUAL_SECONDARY_ONLY; | |
630 } else if (secondary_height == output_cache_[primary_output_index_].y) { | |
631 // Primary is tiled first. | |
632 state = STATE_DUAL_PRIMARY_ONLY; | |
633 } | |
634 } | |
635 } | |
636 } | |
637 | |
638 return state; | |
639 } | |
640 | |
641 bool OutputConfigurator::CycleDisplayMode() { | |
642 VLOG(1) << "CycleDisplayMode"; | |
643 bool did_change = false; | |
644 if (is_running_on_chrome_os_) { | |
645 // Rules: | |
646 // - if there are 0 or 1 displays, do nothing and return false. | |
647 // - use y-coord of CRTCs to determine if we are mirror, primary-first, or | |
648 // secondary-first. The cycle order is: | |
649 // mirror->primary->secondary->mirror. | |
650 State new_state = STATE_INVALID; | |
651 switch (output_state_) { | |
652 case STATE_DUAL_MIRROR: | |
653 new_state = STATE_DUAL_PRIMARY_ONLY; | |
654 break; | |
655 case STATE_DUAL_PRIMARY_ONLY: | |
656 new_state = STATE_DUAL_SECONDARY_ONLY; | |
657 break; | |
658 case STATE_DUAL_SECONDARY_ONLY: | |
659 new_state = mirror_supported_ ? | |
660 STATE_DUAL_MIRROR : | |
661 STATE_DUAL_PRIMARY_ONLY; | |
662 break; | |
663 default: | |
664 // Do nothing - we aren't in a mode which we can rotate. | |
665 break; | |
666 } | |
667 if (STATE_INVALID != new_state) | |
668 did_change = SetDisplayMode(new_state); | |
669 } | |
670 return did_change; | |
671 } | |
672 | |
673 bool OutputConfigurator::ScreenPowerSet(bool power_on, bool all_displays) { | |
674 VLOG(1) << "OutputConfigurator::SetScreensOn " << power_on | |
675 << " all displays " << all_displays; | |
676 bool success = false; | |
677 if (is_running_on_chrome_os_) { | |
678 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
679 CHECK(display != NULL); | |
680 XGrabServer(display); | |
681 Window window = DefaultRootWindow(display); | |
682 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
683 CHECK(screen != NULL); | |
684 | |
685 // Set the CRTCs based on whether we want to turn the power on or off and | |
686 // select the outputs to operate on by name or all_displays. | |
687 for (int i = 0; i < output_count_; ++i) { | |
688 if (all_displays || output_cache_[i].is_internal) { | |
689 const int x = output_cache_[i].x; | |
690 const int y = output_cache_[i].y; | |
691 RROutput output = output_cache_[i].output; | |
692 RRCrtc crtc = output_cache_[i].crtc; | |
693 RRMode mode = None; | |
694 if (power_on) { | |
695 mode = (STATE_DUAL_MIRROR == output_state_) ? | |
696 output_cache_[i].mirror_mode : | |
697 output_cache_[i].ideal_mode; | |
698 } | |
699 | |
700 VLOG(1) << "SET POWER crtc: " << crtc | |
701 << ", mode " << mode | |
702 << ", output " << output | |
703 << ", x " << x | |
704 << ", y " << y; | |
705 ConfigureCrtc(display, | |
706 screen, | |
707 crtc, | |
708 x, | |
709 y, | |
710 mode, | |
711 output); | |
712 output_cache_[i].is_powered_on = power_on; | |
713 success = true; | |
714 } | |
715 } | |
716 | |
717 // Force the DPMS on since the driver doesn't always detect that it should | |
718 // turn on. | |
719 if (power_on) { | |
720 CHECK(DPMSEnable(display)); | |
721 CHECK(DPMSForceLevel(display, DPMSModeOn)); | |
722 } | |
723 | |
724 XRRFreeScreenResources(screen); | |
725 XUngrabServer(display); | |
726 } | |
727 return success; | |
728 } | |
729 | |
730 bool OutputConfigurator::SetDisplayMode(State new_state) { | |
731 if (output_state_ == STATE_INVALID || | |
732 output_state_ == STATE_HEADLESS || | |
733 output_state_ == STATE_SINGLE) | |
734 return false; | |
735 | |
736 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
737 CHECK(display != NULL); | |
738 XGrabServer(display); | |
739 Window window = DefaultRootWindow(display); | |
740 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
741 CHECK(screen != NULL); | |
742 | |
743 UpdateCacheAndXrandrToState(display, | |
744 screen, | |
745 window, | |
746 new_state); | |
747 XRRFreeScreenResources(screen); | |
748 XUngrabServer(display); | |
749 return true; | |
750 } | |
751 | |
752 bool OutputConfigurator::Dispatch(const base::NativeEvent& event) { | |
753 // Ignore this event if the Xrandr extension isn't supported. | |
754 if (is_running_on_chrome_os_ && | |
755 (event->type - xrandr_event_base_ == RRNotify)) { | |
756 XEvent* xevent = static_cast<XEvent*>(event); | |
757 XRRNotifyEvent* notify_event = | |
758 reinterpret_cast<XRRNotifyEvent*>(xevent); | |
759 if (notify_event->subtype == RRNotify_OutputChange) { | |
760 XRROutputChangeNotifyEvent* output_change_event = | |
761 reinterpret_cast<XRROutputChangeNotifyEvent*>(xevent); | |
762 if ((output_change_event->connection == RR_Connected) || | |
763 (output_change_event->connection == RR_Disconnected)) { | |
764 RecacheAndUseDefaultState(); | |
765 CheckIsProjectingAndNotify(); | |
766 } | |
767 // Ignore the case of RR_UnkownConnection. | |
768 } | |
769 } | |
770 return true; | |
771 } | |
772 | |
773 void OutputConfigurator::CheckIsProjectingAndNotify() { | |
774 // Determine if there is an "internal" output and how many outputs are | |
775 // connected. | |
776 bool has_internal_output = false; | |
777 int connected_output_count = 0; | |
778 for (int i = 0; i < output_count_; ++i) { | |
779 if (output_cache_[i].is_connected) { | |
780 connected_output_count += 1; | |
781 has_internal_output |= output_cache_[i].is_internal; | |
782 } | |
783 } | |
784 | |
785 // "Projecting" is defined as having more than 1 output connected while at | |
786 // least one of them is an internal output. | |
787 bool is_projecting = has_internal_output && (connected_output_count > 1); | |
788 chromeos::DBusThreadManager* manager = chromeos::DBusThreadManager::Get(); | |
789 dbus::Bus* bus = manager->GetSystemBus(); | |
790 dbus::ObjectProxy* power_manager_proxy = bus->GetObjectProxy( | |
791 power_manager::kPowerManagerServiceName, | |
792 dbus::ObjectPath(power_manager::kPowerManagerServicePath)); | |
793 dbus::MethodCall method_call( | |
794 power_manager::kPowerManagerInterface, | |
795 power_manager::kSetIsProjectingMethod); | |
796 dbus::MessageWriter writer(&method_call); | |
797 writer.AppendBool(is_projecting); | |
798 power_manager_proxy->CallMethod( | |
799 &method_call, | |
800 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | |
801 dbus::ObjectProxy::EmptyResponseCallback()); | |
802 } | |
803 | |
804 } // namespace chromeos | |
OLD | NEW |