1 /*
2 * Copyright 2005-2011, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 */
8
9
10 #include "ScreenMode.h"
11
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <string.h>
15
16 #include <algorithm>
17
18 #include <InterfaceDefs.h>
19 #include <String.h>
20
21 #include <compute_display_timing.h>
22
23
24 /* Note, this headers defines a *private* interface to the Radeon accelerant.
25 * It's a solution that works with the current BeOS interface that Haiku
26 * adopted.
27 * However, it's not a nice and clean solution. Don't use this header in any
28 * application if you can avoid it. No other driver is using this, or should
29 * be using this.
30 * It will be replaced as soon as we introduce an updated accelerant interface
31 * which may even happen before R1 hits the streets.
32 */
33
34 #include "multimon.h" // the usual: DANGER WILL, ROBINSON!
35
36
37
38 // Vendors.h is generated using these commands (plus a bit of manual editing):
39 /*
40 * wget https://uefi.org/uefi-pnp-export -O Vendors.h.tmp
41 * if [ $? -eq 0 ]; then
42 * sed -E -e 's:<thead>::g' -e 's:<tr.*="(.+)"><td>:{ ":g' -e 's:<td>[[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}</td>::g' -e 's: *(<\/td><td>):\", \":g' -e 's: *<\/td>.<\/tr>:\" },:g' -e 's/"(.*?)", "(.*?)"/\"\2\", \"\1\"/' -e 's/&/\&/' -e "s/'/'/" Vendors.h.tmp | grep -v '<' | sort > Vendors.h
43 * fi
44 * rm Vendors.h.tmp
45 */
46
47 struct pnp_id {
48 const char* id;
49 const char* manufacturer;
operator ==pnp_id50 bool operator==(const pnp_id& a) const {
51 return ::strcasecmp(a.id, id) == 0;
52 };
operator ()pnp_id53 bool operator()(const pnp_id& a, const pnp_id& b) const {
54 return ::strcasecmp(a.id, b.id) < 0;
55 };
56 };
57
58 static const struct pnp_id kPNPIDs[] = {
59 #include "Vendors.h"
60 };
61
62
63 static combine_mode
get_combine_mode(display_mode & mode)64 get_combine_mode(display_mode& mode)
65 {
66 if ((mode.flags & B_SCROLL) == 0)
67 return kCombineDisable;
68
69 if (mode.virtual_width == mode.timing.h_display * 2)
70 return kCombineHorizontally;
71
72 if (mode.virtual_height == mode.timing.v_display * 2)
73 return kCombineVertically;
74
75 return kCombineDisable;
76 }
77
78
79 static float
get_refresh_rate(display_mode & mode)80 get_refresh_rate(display_mode& mode)
81 {
82 // we have to be catious as refresh rate cannot be controlled directly,
83 // so it suffers under rounding errors and hardware restrictions
84 return rint(10 * float(mode.timing.pixel_clock * 1000)
85 / float(mode.timing.h_total * mode.timing.v_total)) / 10.0;
86 }
87
88
89 /*! Helper to sort modes by resolution */
90 static int
compare_mode(const void * _mode1,const void * _mode2)91 compare_mode(const void* _mode1, const void* _mode2)
92 {
93 display_mode *mode1 = (display_mode *)_mode1;
94 display_mode *mode2 = (display_mode *)_mode2;
95 combine_mode combine1, combine2;
96 uint16 width1, width2, height1, height2;
97
98 combine1 = get_combine_mode(*mode1);
99 combine2 = get_combine_mode(*mode2);
100
101 width1 = mode1->virtual_width;
102 height1 = mode1->virtual_height;
103 width2 = mode2->virtual_width;
104 height2 = mode2->virtual_height;
105
106 if (combine1 == kCombineHorizontally)
107 width1 /= 2;
108 if (combine1 == kCombineVertically)
109 height1 /= 2;
110 if (combine2 == kCombineHorizontally)
111 width2 /= 2;
112 if (combine2 == kCombineVertically)
113 height2 /= 2;
114
115 if (width1 != width2)
116 return width1 - width2;
117
118 if (height1 != height2)
119 return height1 - height2;
120
121 return (int)(10 * get_refresh_rate(*mode1)
122 - 10 * get_refresh_rate(*mode2));
123 }
124
125
126 // #pragma mark -
127
128
129 int32
BitsPerPixel() const130 screen_mode::BitsPerPixel() const
131 {
132 switch (space) {
133 case B_RGB32: return 32;
134 case B_RGB24: return 24;
135 case B_RGB16: return 16;
136 case B_RGB15: return 15;
137 case B_CMAP8: return 8;
138 default: return 0;
139 }
140 }
141
142
143 bool
operator ==(const screen_mode & other) const144 screen_mode::operator==(const screen_mode &other) const
145 {
146 return !(*this != other);
147 }
148
149
150 bool
operator !=(const screen_mode & other) const151 screen_mode::operator!=(const screen_mode &other) const
152 {
153 return width != other.width || height != other.height
154 || space != other.space || refresh != other.refresh
155 || combine != other.combine
156 || swap_displays != other.swap_displays
157 || use_laptop_panel != other.use_laptop_panel
158 || tv_standard != other.tv_standard;
159 }
160
161
162 void
SetTo(display_mode & mode)163 screen_mode::SetTo(display_mode& mode)
164 {
165 width = mode.virtual_width;
166 height = mode.virtual_height;
167 space = (color_space)mode.space;
168 combine = get_combine_mode(mode);
169 refresh = get_refresh_rate(mode);
170
171 if (combine == kCombineHorizontally)
172 width /= 2;
173 else if (combine == kCombineVertically)
174 height /= 2;
175
176 swap_displays = false;
177 use_laptop_panel = false;
178 tv_standard = 0;
179 }
180
181
182 // #pragma mark -
183
184
ScreenMode(BWindow * window)185 ScreenMode::ScreenMode(BWindow* window)
186 :
187 fWindow(window),
188 fUpdatedModes(false)
189 {
190 BScreen screen(window);
191 if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) {
192 // sort modes by resolution and refresh to make
193 // the resolution and refresh menu look nicer
194 qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode);
195 } else {
196 fModeList = NULL;
197 fModeCount = 0;
198 }
199 }
200
201
~ScreenMode()202 ScreenMode::~ScreenMode()
203 {
204 free(fModeList);
205 }
206
207
208 status_t
Set(const screen_mode & mode,int32 workspace)209 ScreenMode::Set(const screen_mode& mode, int32 workspace)
210 {
211 if (!fUpdatedModes)
212 UpdateOriginalModes();
213
214 BScreen screen(fWindow);
215
216 if (workspace == ~0)
217 workspace = current_workspace();
218
219 // TODO: our app_server doesn't fully support workspaces, yet
220 SetSwapDisplays(&screen, mode.swap_displays);
221 SetUseLaptopPanel(&screen, mode.use_laptop_panel);
222 SetTVStandard(&screen, mode.tv_standard);
223
224 display_mode displayMode;
225 if (!_GetDisplayMode(mode, displayMode))
226 return B_ENTRY_NOT_FOUND;
227
228 return screen.SetMode(workspace, &displayMode, true);
229 }
230
231
232 status_t
Get(screen_mode & mode,int32 workspace) const233 ScreenMode::Get(screen_mode& mode, int32 workspace) const
234 {
235 display_mode displayMode;
236 BScreen screen(fWindow);
237
238 if (workspace == ~0)
239 workspace = current_workspace();
240
241 if (screen.GetMode(workspace, &displayMode) != B_OK)
242 return B_ERROR;
243
244 mode.SetTo(displayMode);
245
246 // TODO: our app_server doesn't fully support workspaces, yet
247 if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK)
248 mode.swap_displays = false;
249 if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK)
250 mode.use_laptop_panel = false;
251 if (GetTVStandard(&screen, &mode.tv_standard) != B_OK)
252 mode.tv_standard = 0;
253
254 return B_OK;
255 }
256
257
258 status_t
GetOriginalMode(screen_mode & mode,int32 workspace) const259 ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const
260 {
261 if (workspace == ~0)
262 workspace = current_workspace();
263 // TODO this should use kMaxWorkspaces
264 else if (workspace > 31)
265 return B_BAD_INDEX;
266
267 mode = fOriginal[workspace];
268
269 return B_OK;
270 }
271
272
273 status_t
Set(const display_mode & mode,int32 workspace)274 ScreenMode::Set(const display_mode& mode, int32 workspace)
275 {
276 if (!fUpdatedModes)
277 UpdateOriginalModes();
278
279 BScreen screen(fWindow);
280
281 if (workspace == ~0)
282 workspace = current_workspace();
283
284 // BScreen::SetMode() needs a non-const display_mode
285 display_mode nonConstMode;
286 memcpy(&nonConstMode, &mode, sizeof(display_mode));
287 return screen.SetMode(workspace, &nonConstMode, true);
288 }
289
290
291 status_t
Get(display_mode & mode,int32 workspace) const292 ScreenMode::Get(display_mode& mode, int32 workspace) const
293 {
294 BScreen screen(fWindow);
295
296 if (workspace == ~0)
297 workspace = current_workspace();
298
299 return screen.GetMode(workspace, &mode);
300 }
301
302
303 /*! This method assumes that you already reverted to the correct number
304 of workspaces.
305 */
306 status_t
Revert()307 ScreenMode::Revert()
308 {
309 if (!fUpdatedModes)
310 return B_ERROR;
311
312 status_t result = B_OK;
313 screen_mode current;
314 for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
315 if (Get(current, workspace) == B_OK && fOriginal[workspace] == current)
316 continue;
317
318 BScreen screen(fWindow);
319
320 // TODO: our app_server doesn't fully support workspaces, yet
321 if (workspace == current_workspace()) {
322 SetSwapDisplays(&screen, fOriginal[workspace].swap_displays);
323 SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel);
324 SetTVStandard(&screen, fOriginal[workspace].tv_standard);
325 }
326
327 result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace],
328 true);
329 if (result != B_OK)
330 break;
331 }
332
333 return result;
334 }
335
336
337 void
UpdateOriginalModes()338 ScreenMode::UpdateOriginalModes()
339 {
340 BScreen screen(fWindow);
341 for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
342 if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace])
343 == B_OK) {
344 Get(fOriginal[workspace], workspace);
345 fUpdatedModes = true;
346 }
347 }
348 }
349
350
351 bool
SupportsColorSpace(const screen_mode & mode,color_space space)352 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
353 {
354 return true;
355 }
356
357
358 status_t
GetRefreshLimits(const screen_mode & mode,float & min,float & max)359 ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max)
360 {
361 uint32 minClock, maxClock;
362 display_mode displayMode;
363 if (!_GetDisplayMode(mode, displayMode))
364 return B_ERROR;
365
366 BScreen screen(fWindow);
367 if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK)
368 return B_ERROR;
369
370 uint32 total = displayMode.timing.h_total * displayMode.timing.v_total;
371 min = minClock * 1000.0 / total;
372 max = maxClock * 1000.0 / total;
373
374 return B_OK;
375 }
376
377
378 const char*
GetManufacturerFromID(const char * id) const379 ScreenMode::GetManufacturerFromID(const char* id) const
380 {
381 // We assume the array is sorted
382 const size_t numElements = B_COUNT_OF(kPNPIDs);
383 const struct pnp_id key = { id, "dummy" };
384 const pnp_id* lastElement = kPNPIDs + numElements;
385 const pnp_id* element = std::find(kPNPIDs, lastElement, key);
386 if (element == lastElement) {
387 // can't find the vendor code
388 return NULL;
389 }
390
391 return element->manufacturer;
392 }
393
394
395 status_t
GetMonitorInfo(monitor_info & info,float * _diagonalInches)396 ScreenMode::GetMonitorInfo(monitor_info& info, float* _diagonalInches)
397 {
398 BScreen screen(fWindow);
399 status_t status = screen.GetMonitorInfo(&info);
400 if (status != B_OK)
401 return status;
402
403 if (_diagonalInches != NULL) {
404 *_diagonalInches = round(sqrt(info.width * info.width
405 + info.height * info.height) / 0.254) / 10.0;
406 }
407
408 // Some older CRT monitors do not contain the monitor range information
409 // (EDID1_MONITOR_RANGES) in their EDID info resulting in the min/max
410 // horizontal/vertical frequencies being zero. In this case, set the
411 // vertical frequency range to 60..85 Hz.
412 if (info.min_vertical_frequency == 0) {
413 info.min_vertical_frequency = 60;
414 info.max_vertical_frequency = 85;
415 }
416
417 char vendor[4];
418 strlcpy(vendor, info.vendor, sizeof(vendor));
419 const char* vendorString = GetManufacturerFromID(vendor);
420 if (vendorString != NULL)
421 strlcpy(info.vendor, vendorString, sizeof(info.vendor));
422
423 // Remove extraneous vendor strings and whitespace
424
425 BString name(info.name);
426 name.IReplaceAll(info.vendor, "");
427 name.Trim();
428
429 strcpy(info.name, name.String());
430
431 return B_OK;
432 }
433
434
435 status_t
GetDeviceInfo(accelerant_device_info & info)436 ScreenMode::GetDeviceInfo(accelerant_device_info& info)
437 {
438 BScreen screen(fWindow);
439 return screen.GetDeviceInfo(&info);
440 }
441
442
443 screen_mode
ModeAt(int32 index)444 ScreenMode::ModeAt(int32 index)
445 {
446 if (index < 0)
447 index = 0;
448 else if (index >= (int32)fModeCount)
449 index = fModeCount - 1;
450
451 screen_mode mode;
452 mode.SetTo(fModeList[index]);
453
454 return mode;
455 }
456
457
458 const display_mode&
DisplayModeAt(int32 index)459 ScreenMode::DisplayModeAt(int32 index)
460 {
461 if (index < 0)
462 index = 0;
463 else if (index >= (int32)fModeCount)
464 index = fModeCount - 1;
465
466 return fModeList[index];
467 }
468
469
470 int32
CountModes()471 ScreenMode::CountModes()
472 {
473 return fModeCount;
474 }
475
476
477 /*! Searches for a similar mode in the reported mode list, and if that does not
478 find a matching mode, it will compute the mode manually using the GTF.
479 */
480 bool
_GetDisplayMode(const screen_mode & mode,display_mode & displayMode)481 ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode)
482 {
483 uint16 virtualWidth, virtualHeight;
484 int32 bestIndex = -1;
485 float bestDiff = 999;
486
487 virtualWidth = mode.combine == kCombineHorizontally
488 ? mode.width * 2 : mode.width;
489 virtualHeight = mode.combine == kCombineVertically
490 ? mode.height * 2 : mode.height;
491
492 // try to find mode in list provided by driver
493 for (uint32 i = 0; i < fModeCount; i++) {
494 if (fModeList[i].virtual_width != virtualWidth
495 || fModeList[i].virtual_height != virtualHeight
496 || (color_space)fModeList[i].space != mode.space)
497 continue;
498
499 // Accept the mode if the computed refresh rate of the mode is within
500 // 0.6 percent of the refresh rate specified by the caller. Note that
501 // refresh rates computed from mode parameters is not exact; especially
502 // some of the older modes such as 640x480, 800x600, and 1024x768.
503 // The tolerance of 0.6% was obtained by examining the various possible
504 // modes.
505
506 float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh);
507 if (refreshDiff < 0.006 * mode.refresh) {
508 // Accept this mode.
509 displayMode = fModeList[i];
510 displayMode.h_display_start = 0;
511 displayMode.v_display_start = 0;
512
513 // Since the computed refresh rate of the selected mode might differ
514 // from selected refresh rate by a few tenths (e.g. 60.2 instead of
515 // 60.0), tweak the pixel clock so the the refresh rate of the mode
516 // matches the selected refresh rate.
517
518 displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total
519 * displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5);
520 return true;
521 }
522
523 // Mode not acceptable.
524
525 if (refreshDiff < bestDiff) {
526 bestDiff = refreshDiff;
527 bestIndex = i;
528 }
529 }
530
531 // we didn't find the exact mode, but something very similar?
532 if (bestIndex == -1)
533 return false;
534
535 displayMode = fModeList[bestIndex];
536 displayMode.h_display_start = 0;
537 displayMode.v_display_start = 0;
538
539 // For the mode selected by the width, height, and refresh rate, compute
540 // the video timing parameters for the mode by using the VESA Generalized
541 // Timing Formula (GTF).
542 compute_display_timing(mode.width, mode.height, mode.refresh, false,
543 &displayMode.timing);
544
545 return true;
546 }
547