xref: /haiku/src/preferences/screen/ScreenMode.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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/&amp;/\&/' -e "s/&#039;/'/" 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;
50 	bool operator==(const pnp_id& a) const {
51 		return ::strcasecmp(a.id, id) == 0;
52 	};
53 	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
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
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
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
130 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
144 screen_mode::operator==(const screen_mode &other) const
145 {
146 	return !(*this != other);
147 }
148 
149 
150 bool
151 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
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 
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 
202 ScreenMode::~ScreenMode()
203 {
204 	free(fModeList);
205 }
206 
207 
208 status_t
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
233 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
259 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
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
292 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
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
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
352 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
353 {
354 	return true;
355 }
356 
357 
358 status_t
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*
379 ScreenMode::GetManufacturerFromID(const char* id) const
380 {
381 	// We assume the array is sorted
382 	const size_t numElements = sizeof(kPNPIDs) / sizeof(kPNPIDs[0]);
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
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
436 ScreenMode::GetDeviceInfo(accelerant_device_info& info)
437 {
438 	BScreen screen(fWindow);
439 	return screen.GetDeviceInfo(&info);
440 }
441 
442 
443 screen_mode
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&
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
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
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