xref: /haiku/src/preferences/screen/ScreenMode.cpp (revision 6f80a9801fedbe7355c4360bd204ba746ec3ec2d)
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 <InterfaceDefs.h>
17 #include <String.h>
18 
19 #include <compute_display_timing.h>
20 
21 
22 /* Note, this headers defines a *private* interface to the Radeon accelerant.
23  * It's a solution that works with the current BeOS interface that Haiku
24  * adopted.
25  * However, it's not a nice and clean solution. Don't use this header in any
26  * application if you can avoid it. No other driver is using this, or should
27  * be using this.
28  * It will be replaced as soon as we introduce an updated accelerant interface
29  * which may even happen before R1 hits the streets.
30  */
31 
32 #include "multimon.h"	// the usual: DANGER WILL, ROBINSON!
33 
34 
35 static combine_mode
36 get_combine_mode(display_mode& mode)
37 {
38 	if ((mode.flags & B_SCROLL) == 0)
39 		return kCombineDisable;
40 
41 	if (mode.virtual_width == mode.timing.h_display * 2)
42 		return kCombineHorizontally;
43 
44 	if (mode.virtual_height == mode.timing.v_display * 2)
45 		return kCombineVertically;
46 
47 	return kCombineDisable;
48 }
49 
50 
51 static float
52 get_refresh_rate(display_mode& mode)
53 {
54 	// we have to be catious as refresh rate cannot be controlled directly,
55 	// so it suffers under rounding errors and hardware restrictions
56 	return rint(10 * float(mode.timing.pixel_clock * 1000)
57 		/ float(mode.timing.h_total * mode.timing.v_total)) / 10.0;
58 }
59 
60 
61 /*!	Helper to sort modes by resolution */
62 static int
63 compare_mode(const void* _mode1, const void* _mode2)
64 {
65 	display_mode *mode1 = (display_mode *)_mode1;
66 	display_mode *mode2 = (display_mode *)_mode2;
67 	combine_mode combine1, combine2;
68 	uint16 width1, width2, height1, height2;
69 
70 	combine1 = get_combine_mode(*mode1);
71 	combine2 = get_combine_mode(*mode2);
72 
73 	width1 = mode1->virtual_width;
74 	height1 = mode1->virtual_height;
75 	width2 = mode2->virtual_width;
76 	height2 = mode2->virtual_height;
77 
78 	if (combine1 == kCombineHorizontally)
79 		width1 /= 2;
80 	if (combine1 == kCombineVertically)
81 		height1 /= 2;
82 	if (combine2 == kCombineHorizontally)
83 		width2 /= 2;
84 	if (combine2 == kCombineVertically)
85 		height2 /= 2;
86 
87 	if (width1 != width2)
88 		return width1 - width2;
89 
90 	if (height1 != height2)
91 		return height1 - height2;
92 
93 	return (int)(10 * get_refresh_rate(*mode1)
94 		-  10 * get_refresh_rate(*mode2));
95 }
96 
97 
98 //	#pragma mark -
99 
100 
101 int32
102 screen_mode::BitsPerPixel() const
103 {
104 	switch (space) {
105 		case B_RGB32:	return 32;
106 		case B_RGB24:	return 24;
107 		case B_RGB16:	return 16;
108 		case B_RGB15:	return 15;
109 		case B_CMAP8:	return 8;
110 		default:		return 0;
111 	}
112 }
113 
114 
115 bool
116 screen_mode::operator==(const screen_mode &other) const
117 {
118 	return !(*this != other);
119 }
120 
121 
122 bool
123 screen_mode::operator!=(const screen_mode &other) const
124 {
125 	return width != other.width || height != other.height
126 		|| space != other.space || refresh != other.refresh
127 		|| combine != other.combine
128 		|| swap_displays != other.swap_displays
129 		|| use_laptop_panel != other.use_laptop_panel
130 		|| tv_standard != other.tv_standard;
131 }
132 
133 
134 void
135 screen_mode::SetTo(display_mode& mode)
136 {
137 	width = mode.virtual_width;
138 	height = mode.virtual_height;
139 	space = (color_space)mode.space;
140 	combine = get_combine_mode(mode);
141 	refresh = get_refresh_rate(mode);
142 
143 	if (combine == kCombineHorizontally)
144 		width /= 2;
145 	else if (combine == kCombineVertically)
146 		height /= 2;
147 
148 	swap_displays = false;
149 	use_laptop_panel = false;
150 	tv_standard = 0;
151 }
152 
153 
154 //	#pragma mark -
155 
156 
157 ScreenMode::ScreenMode(BWindow* window)
158 	:
159 	fWindow(window),
160 	fUpdatedModes(false)
161 {
162 	BScreen screen(window);
163 	if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) {
164 		// sort modes by resolution and refresh to make
165 		// the resolution and refresh menu look nicer
166 		qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode);
167 	} else {
168 		fModeList = NULL;
169 		fModeCount = 0;
170 	}
171 }
172 
173 
174 ScreenMode::~ScreenMode()
175 {
176 	free(fModeList);
177 }
178 
179 
180 status_t
181 ScreenMode::Set(const screen_mode& mode, int32 workspace)
182 {
183 	if (!fUpdatedModes)
184 		UpdateOriginalModes();
185 
186 	BScreen screen(fWindow);
187 
188 	if (workspace == ~0)
189 		workspace = current_workspace();
190 
191 	// TODO: our app_server doesn't fully support workspaces, yet
192 	SetSwapDisplays(&screen, mode.swap_displays);
193 	SetUseLaptopPanel(&screen, mode.use_laptop_panel);
194 	SetTVStandard(&screen, mode.tv_standard);
195 
196 	display_mode displayMode;
197 	if (!_GetDisplayMode(mode, displayMode))
198 		return B_ENTRY_NOT_FOUND;
199 
200 	return screen.SetMode(workspace, &displayMode, true);
201 }
202 
203 
204 status_t
205 ScreenMode::Get(screen_mode& mode, int32 workspace) const
206 {
207 	display_mode displayMode;
208 	BScreen screen(fWindow);
209 
210 	if (workspace == ~0)
211 		workspace = current_workspace();
212 
213 	if (screen.GetMode(workspace, &displayMode) != B_OK)
214 		return B_ERROR;
215 
216 	mode.SetTo(displayMode);
217 
218 	// TODO: our app_server doesn't fully support workspaces, yet
219 	if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK)
220 		mode.swap_displays = false;
221 	if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK)
222 		mode.use_laptop_panel = false;
223 	if (GetTVStandard(&screen, &mode.tv_standard) != B_OK)
224 		mode.tv_standard = 0;
225 
226 	return B_OK;
227 }
228 
229 
230 status_t
231 ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const
232 {
233 	if (workspace == ~0)
234 		workspace = current_workspace();
235 		// TODO this should use kMaxWorkspaces
236 	else if (workspace > 31)
237 		return B_BAD_INDEX;
238 
239 	mode = fOriginal[workspace];
240 
241 	return B_OK;
242 }
243 
244 
245 status_t
246 ScreenMode::Set(const display_mode& mode, int32 workspace)
247 {
248 	if (!fUpdatedModes)
249 		UpdateOriginalModes();
250 
251 	BScreen screen(fWindow);
252 
253 	if (workspace == ~0)
254 		workspace = current_workspace();
255 
256 	// BScreen::SetMode() needs a non-const display_mode
257 	display_mode nonConstMode;
258 	memcpy(&nonConstMode, &mode, sizeof(display_mode));
259 	return screen.SetMode(workspace, &nonConstMode, true);
260 }
261 
262 
263 status_t
264 ScreenMode::Get(display_mode& mode, int32 workspace) const
265 {
266 	BScreen screen(fWindow);
267 
268 	if (workspace == ~0)
269 		workspace = current_workspace();
270 
271 	return screen.GetMode(workspace, &mode);
272 }
273 
274 
275 /*!	This method assumes that you already reverted to the correct number
276 	of workspaces.
277 */
278 status_t
279 ScreenMode::Revert()
280 {
281 	if (!fUpdatedModes)
282 		return B_ERROR;
283 
284 	status_t result = B_OK;
285 	screen_mode current;
286 	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
287 		if (Get(current, workspace) == B_OK && fOriginal[workspace] == current)
288 			continue;
289 
290 		BScreen screen(fWindow);
291 
292 		// TODO: our app_server doesn't fully support workspaces, yet
293 		if (workspace == current_workspace()) {
294 			SetSwapDisplays(&screen, fOriginal[workspace].swap_displays);
295 			SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel);
296 			SetTVStandard(&screen, fOriginal[workspace].tv_standard);
297 		}
298 
299 		result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace],
300 			true);
301 		if (result != B_OK)
302 			break;
303 	}
304 
305 	return result;
306 }
307 
308 
309 void
310 ScreenMode::UpdateOriginalModes()
311 {
312 	BScreen screen(fWindow);
313 	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
314 		if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace])
315 				== B_OK) {
316 			Get(fOriginal[workspace], workspace);
317 			fUpdatedModes = true;
318 		}
319 	}
320 }
321 
322 
323 bool
324 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
325 {
326 	return true;
327 }
328 
329 
330 status_t
331 ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max)
332 {
333 	uint32 minClock, maxClock;
334 	display_mode displayMode;
335 	if (!_GetDisplayMode(mode, displayMode))
336 		return B_ERROR;
337 
338 	BScreen screen(fWindow);
339 	if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK)
340 		return B_ERROR;
341 
342 	uint32 total = displayMode.timing.h_total * displayMode.timing.v_total;
343 	min = minClock * 1000.0 / total;
344 	max = maxClock * 1000.0 / total;
345 
346 	return B_OK;
347 }
348 
349 
350 status_t
351 ScreenMode::GetMonitorInfo(monitor_info& info, float* _diagonalInches)
352 {
353 	BScreen screen(fWindow);
354 	status_t status = screen.GetMonitorInfo(&info);
355 	if (status != B_OK)
356 		return status;
357 
358 	if (_diagonalInches != NULL) {
359 		*_diagonalInches = round(sqrt(info.width * info.width
360 			+ info.height * info.height) / 0.254) / 10.0;
361 	}
362 
363 	// Some older CRT monitors do not contain the monitor range information
364 	// (EDID1_MONITOR_RANGES) in their EDID info resulting in the min/max
365 	// horizontal/vertical frequencies being zero.  In this case, set the
366 	// vertical frequency range to 60..85 Hz.
367 	if (info.min_vertical_frequency == 0) {
368 		info.min_vertical_frequency = 60;
369 		info.max_vertical_frequency = 85;
370 	}
371 
372 	// TODO: If the names aren't sound, we could see if we find/create a
373 	// database for the entries with user presentable names; they are fine
374 	// for the models I could test with so far.
375 
376 	uint32 id = (info.vendor[0] << 24) | (info.vendor[1] << 16)
377 		| (info.vendor[2] << 8) | (info.vendor[3]);
378 
379 	switch (id) {
380 		case 'ACI\0':
381 			strcpy(info.vendor, "Ancor Communications");
382 			break;
383 		case 'ADI\0':
384 			strcpy(info.vendor, "ADI MicroScan");
385 			break;
386 		case 'AAC\0':
387 		case 'ACR\0':
388 		case 'API\0':
389 			strcpy(info.vendor, "Acer");
390 			break;
391 		case 'ACT\0':
392 			strcpy(info.vendor, "Targa");
393 			break;
394 		case 'APP\0':
395 			strcpy(info.vendor, "Apple");
396 			break;
397 		case 'AUO\0':
398 			strcpy(info.vendor, "AU Optronics");
399 			break;
400 		case 'BNQ\0':
401 			strcpy(info.vendor, "BenQ");
402 			break;
403 		case 'CMN\0':
404 			strcpy(info.vendor, "Chimei Innolux");
405 			break;
406 		case 'CMO\0':
407 			strcpy(info.vendor, "Chi Mei Optoelectronics");
408 			break;
409 		case 'COR\0':
410 			strcpy(info.vendor, "CPT");
411 			break;
412 		case 'CPL\0':
413 			strcpy(info.vendor, "ALFA");
414 			break;
415 		case 'CPQ\0':
416 			strcpy(info.vendor, "Compaq");
417 			break;
418 		case 'DEL\0':
419 			strcpy(info.vendor, "Dell");
420 			break;
421 		case 'DPC\0':
422 			strcpy(info.vendor, "Delta Electronics");
423 			break;
424 		case 'DWE\0':
425 			strcpy(info.vendor, "Daewoo");
426 			break;
427 		case 'ECS\0':
428 			strcpy(info.vendor, "Elitegroup");
429 			break;
430 		case 'ELS\0':
431 			strcpy(info.vendor, "ELSA");
432 			break;
433 		case 'EMA\0':
434 			strcpy(info.vendor, "eMachines");
435 			break;
436 		case 'EIZ\0':
437 		case 'ENC\0':
438 			strcpy(info.vendor, "Eizo");
439 			break;
440 		case 'EPI\0':
441 			strcpy(info.vendor, "Envision");
442 			break;
443 		case 'FCM\0':
444 			strcpy(info.vendor, "Funai Electronics");
445 			break;
446 		case 'FUS\0':
447 			strcpy(info.vendor, "Fujitsu-Siemens");
448 			break;
449 		case 'GSM\0':
450 			strcpy(info.vendor, "LG");
451 			break;
452 		case 'GWY\0':
453 			strcpy(info.vendor, "Gateway");
454 			break;
455 		case 'HIQ\0':
456 		case 'HEI\0':
457 			strcpy(info.vendor, "Hyundai");
458 			break;
459 		case 'HIT\0':
460 		case 'HTC\0':
461 			strcpy(info.vendor, "Hitachi");
462 			break;
463 		case 'HSL\0':
464 			strcpy(info.vendor, "Hansol");
465 			break;
466 		case 'HWP\0':
467 			strcpy(info.vendor, "Hewlett Packard");
468 			break;
469 		case 'ICL\0':
470 			strcpy(info.vendor, "Fujitsu");
471 			break;
472 		case 'IVM\0':
473 			strcpy(info.vendor, "Iiyama");
474 			break;
475 		case 'LEN\0':
476 			strcpy(info.vendor, "Lenovo");
477 			break;
478 		case 'LGD\0':
479 			strcpy(info.vendor, "LG Display");
480 			break;
481 		case 'LPL\0':
482 			strcpy(info.vendor, "LG Philips");
483 			break;
484 		case 'LTN\0':
485 			strcpy(info.vendor, "Lite-On");
486 			break;
487 		case 'MAX\0':
488 			strcpy(info.vendor, "Maxdata");
489 			break;
490 		case 'MED\0':
491 			strcpy(info.vendor, "Medion");
492 			break;
493 		case 'MEI\0':
494 			strcpy(info.vendor, "Panasonic");
495 			break;
496 		case 'MEL\0':
497 			strcpy(info.vendor, "Mitsubishi");
498 			break;
499 		case 'MIR\0':
500 			strcpy(info.vendor, "miro");
501 			break;
502 		case 'MTC\0':
503 			strcpy(info.vendor, "Mitac");
504 			break;
505 		case 'NAN\0':
506 			strcpy(info.vendor, "Nanao");
507 			break;
508 		case 'NOK\0':
509 			strcpy(info.vendor, "Nokia");
510 			break;
511 		case 'OQI\0':
512 			strcpy(info.vendor, "Optiquest");
513 			break;
514 		case 'PHL\0':
515 			strcpy(info.vendor, "Philips");
516 			break;
517 		case 'PTS\0':
518 			strcpy(info.vendor, "Proview");
519 			break;
520 		case 'QDS\0':
521 			strcpy(info.vendor, "Quanta Display");
522 			break;
523 		case 'REL\0':
524 			strcpy(info.vendor, "Relisys");
525 			break;
526 		case 'SAM\0':
527 			strcpy(info.vendor, "Samsung");
528 			break;
529 		case 'SDI\0':
530 			strcpy(info.vendor, "Samtron");
531 			break;
532 		case 'SEC\0':
533 			strcpy(info.vendor, "Seiko Epson");
534 			break;
535 		case 'SHP\0':
536 			strcpy(info.vendor, "Sharp");
537 			break;
538 		case 'SNI\0':
539 			strcpy(info.vendor, "Siemens");
540 			break;
541 		case 'SNY\0':
542 			strcpy(info.vendor, "Sony");
543 			break;
544 		case 'SPT\0':
545 			strcpy(info.vendor, "Sceptre");
546 			break;
547 		case 'SRC\0':
548 			strcpy(info.vendor, "Shamrock");
549 			break;
550 		case 'SUN\0':
551 			strcpy(info.vendor, "Sun Microsystems");
552 			break;
553 		case 'TAT\0':
554 			strcpy(info.vendor, "Tatung");
555 			break;
556 		case 'TOS\0':
557 		case 'TSB\0':
558 			strcpy(info.vendor, "Toshiba");
559 			break;
560 		case 'UNK\0':
561 			strcpy(info.vendor, "(unknown)");
562 			break;
563 		case 'UNM\0':
564 			strcpy(info.vendor, "Unisys");
565 			break;
566 		case 'VES\0':
567 			strcpy(info.vendor, "Vestel");
568 			break;
569 		case 'VIZ\0':
570 			strcpy(info.vendor, "Vizio");
571 			break;
572 		case 'VSC\0':
573 			strcpy(info.vendor, "ViewSonic");
574 			break;
575 		case 'ZCM\0':
576 			strcpy(info.vendor, "Zenith");
577 			break;
578 	}
579 
580 	// Remove extraneous vendor strings and whitespace
581 
582 	BString name(info.name);
583 	name.IReplaceAll(info.vendor, "");
584 	name.Trim();
585 
586 	strcpy(info.name, name.String());
587 
588 	return B_OK;
589 }
590 
591 
592 status_t
593 ScreenMode::GetDeviceInfo(accelerant_device_info& info)
594 {
595 	BScreen screen(fWindow);
596 	return screen.GetDeviceInfo(&info);
597 }
598 
599 
600 screen_mode
601 ScreenMode::ModeAt(int32 index)
602 {
603 	if (index < 0)
604 		index = 0;
605 	else if (index >= (int32)fModeCount)
606 		index = fModeCount - 1;
607 
608 	screen_mode mode;
609 	mode.SetTo(fModeList[index]);
610 
611 	return mode;
612 }
613 
614 
615 const display_mode&
616 ScreenMode::DisplayModeAt(int32 index)
617 {
618 	if (index < 0)
619 		index = 0;
620 	else if (index >= (int32)fModeCount)
621 		index = fModeCount - 1;
622 
623 	return fModeList[index];
624 }
625 
626 
627 int32
628 ScreenMode::CountModes()
629 {
630 	return fModeCount;
631 }
632 
633 
634 /*!	Searches for a similar mode in the reported mode list, and if that does not
635 	find a matching mode, it will compute the mode manually using the GTF.
636 */
637 bool
638 ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode)
639 {
640 	uint16 virtualWidth, virtualHeight;
641 	int32 bestIndex = -1;
642 	float bestDiff = 999;
643 
644 	virtualWidth = mode.combine == kCombineHorizontally
645 		? mode.width * 2 : mode.width;
646 	virtualHeight = mode.combine == kCombineVertically
647 		? mode.height * 2 : mode.height;
648 
649 	// try to find mode in list provided by driver
650 	for (uint32 i = 0; i < fModeCount; i++) {
651 		if (fModeList[i].virtual_width != virtualWidth
652 			|| fModeList[i].virtual_height != virtualHeight
653 			|| (color_space)fModeList[i].space != mode.space)
654 			continue;
655 
656 		// Accept the mode if the computed refresh rate of the mode is within
657 		// 0.6 percent of the refresh rate specified by the caller.  Note that
658 		// refresh rates computed from mode parameters is not exact; especially
659 		// some of the older modes such as 640x480, 800x600, and 1024x768.
660 		// The tolerance of 0.6% was obtained by examining the various possible
661 		// modes.
662 
663 		float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh);
664 		if (refreshDiff < 0.006 * mode.refresh) {
665 			// Accept this mode.
666 			displayMode = fModeList[i];
667 			displayMode.h_display_start = 0;
668 			displayMode.v_display_start = 0;
669 
670 			// Since the computed refresh rate of the selected mode might differ
671 			// from selected refresh rate by a few tenths (e.g. 60.2 instead of
672 			// 60.0), tweak the pixel clock so the the refresh rate of the mode
673 			// matches the selected refresh rate.
674 
675 			displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total
676 				* displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5);
677 			return true;
678 		}
679 
680 		// Mode not acceptable.
681 
682 		if (refreshDiff < bestDiff) {
683 			bestDiff = refreshDiff;
684 			bestIndex = i;
685 		}
686 	}
687 
688 	// we didn't find the exact mode, but something very similar?
689 	if (bestIndex == -1)
690 		return false;
691 
692 	displayMode = fModeList[bestIndex];
693 	displayMode.h_display_start = 0;
694 	displayMode.v_display_start = 0;
695 
696 	// For the mode selected by the width, height, and refresh rate, compute
697 	// the video timing parameters for the mode by using the VESA Generalized
698 	// Timing Formula (GTF).
699 	compute_display_timing(mode.width, mode.height, mode.refresh, false,
700 		&displayMode.timing);
701 
702 	return true;
703 }
704