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