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
35static combine_mode
36get_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
51static float
52get_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 */
62static int
63compare_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
101int32
102screen_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
115bool
116screen_mode::operator==(const screen_mode &other) const
117{
118	return !(*this != other);
119}
120
121
122bool
123screen_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
134void
135screen_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
157ScreenMode::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
174ScreenMode::~ScreenMode()
175{
176	free(fModeList);
177}
178
179
180status_t
181ScreenMode::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
204status_t
205ScreenMode::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
230status_t
231ScreenMode::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
245status_t
246ScreenMode::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
263status_t
264ScreenMode::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*/
278status_t
279ScreenMode::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
309void
310ScreenMode::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
323bool
324ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
325{
326	return true;
327}
328
329
330status_t
331ScreenMode::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
350status_t
351ScreenMode::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 'ADI\0':
381			strcpy(info.vendor, "ADI MicroScan");
382			break;
383		case 'AAC\0':
384		case 'ACR\0':
385		case 'API\0':
386			strcpy(info.vendor, "Acer");
387			break;
388		case 'ACT\0':
389			strcpy(info.vendor, "Targa");
390			break;
391		case 'APP\0':
392			strcpy(info.vendor, "Apple");
393			break;
394		case 'AUO\0':
395			strcpy(info.vendor, "AU Optronics");
396			break;
397		case 'BNQ\0':
398			strcpy(info.vendor, "BenQ");
399			break;
400		case 'CPL\0':
401			strcpy(info.vendor, "ALFA");
402			break;
403		case 'CPQ\0':
404			strcpy(info.vendor, "Compaq");
405			break;
406		case 'DEL\0':
407			strcpy(info.vendor, "Dell");
408			break;
409		case 'DPC\0':
410			strcpy(info.vendor, "Delta Electronics");
411			break;
412		case 'DWE\0':
413			strcpy(info.vendor, "Daewoo");
414			break;
415		case 'ECS\0':
416			strcpy(info.vendor, "Elitegroup");
417			break;
418		case 'ELS\0':
419			strcpy(info.vendor, "ELSA");
420			break;
421		case 'EMA\0':
422			strcpy(info.vendor, "eMachines");
423			break;
424		case 'EIZ\0':
425		case 'ENC\0':
426			strcpy(info.vendor, "Eizo");
427			break;
428		case 'EPI\0':
429			strcpy(info.vendor, "Envision");
430			break;
431		case 'FCM\0':
432			strcpy(info.vendor, "Funai Electronics");
433			break;
434		case 'FUS\0':
435			strcpy(info.vendor, "Fujitsu-Siemens");
436			break;
437		case 'GSM\0':
438			strcpy(info.vendor, "LG");
439			break;
440		case 'GWY\0':
441			strcpy(info.vendor, "Gateway");
442			break;
443		case 'HIQ\0':
444		case 'HEI\0':
445			strcpy(info.vendor, "Hyundai");
446			break;
447		case 'HIT\0':
448		case 'HTC\0':
449			strcpy(info.vendor, "Hitachi");
450			break;
451		case 'HSL\0':
452			strcpy(info.vendor, "Hansol");
453			break;
454		case 'HWP\0':
455			strcpy(info.vendor, "Hewlett Packard");
456			break;
457		case 'ICL\0':
458			strcpy(info.vendor, "Fujitsu");
459			break;
460		case 'IVM\0':
461			strcpy(info.vendor, "Iiyama");
462			break;
463		case 'LEN\0':
464			strcpy(info.vendor, "Lenovo");
465			break;
466		case 'LPL\0':
467			strcpy(info.vendor, "LG Phillips");
468			break;
469		case 'LTN\0':
470			strcpy(info.vendor, "Lite-On");
471			break;
472		case 'MAX\0':
473			strcpy(info.vendor, "Maxdata");
474			break;
475		case 'MED\0':
476			strcpy(info.vendor, "Medion");
477			break;
478		case 'MEI\0':
479			strcpy(info.vendor, "Panasonic");
480			break;
481		case 'MEL\0':
482			strcpy(info.vendor, "Mitsubishi");
483			break;
484		case 'MIR\0':
485			strcpy(info.vendor, "miro");
486			break;
487		case 'MTC\0':
488			strcpy(info.vendor, "Mitac");
489			break;
490		case 'NAN\0':
491			strcpy(info.vendor, "Nanao");
492			break;
493		case 'NOK\0':
494			strcpy(info.vendor, "Nokia");
495			break;
496		case 'OQI\0':
497			strcpy(info.vendor, "Optiquest");
498			break;
499		case 'PHL\0':
500			strcpy(info.vendor, "Philips");
501			break;
502		case 'PTS\0':
503			strcpy(info.vendor, "Proview");
504			break;
505		case 'QDS\0':
506			strcpy(info.vendor, "Quanta Display");
507			break;
508		case 'REL\0':
509			strcpy(info.vendor, "Relisys");
510			break;
511		case 'SAM\0':
512			strcpy(info.vendor, "Samsung");
513			break;
514		case 'SDI\0':
515			strcpy(info.vendor, "Samtron");
516			break;
517		case 'SHP\0':
518			strcpy(info.vendor, "Sharp");
519			break;
520		case 'SNI\0':
521			strcpy(info.vendor, "Siemens");
522			break;
523		case 'SNY\0':
524			strcpy(info.vendor, "Sony");
525			break;
526		case 'SPT\0':
527			strcpy(info.vendor, "Sceptre");
528			break;
529		case 'SRC\0':
530			strcpy(info.vendor, "Shamrock");
531			break;
532		case 'SUN\0':
533			strcpy(info.vendor, "Sun Microsystems");
534			break;
535		case 'TAT\0':
536			strcpy(info.vendor, "Tatung");
537			break;
538		case 'TOS\0':
539		case 'TSB\0':
540			strcpy(info.vendor, "Toshiba");
541			break;
542		case 'UNM\0':
543			strcpy(info.vendor, "Unisys");
544			break;
545		case 'VIZ\0':
546			strcpy(info.vendor, "Vizio");
547			break;
548		case 'VSC\0':
549			strcpy(info.vendor, "ViewSonic");
550			break;
551		case 'ZCM\0':
552			strcpy(info.vendor, "Zenith");
553			break;
554	}
555
556	// Remove extraneous vendor strings and whitespace
557
558	BString name(info.name);
559	name.IReplaceAll(info.vendor, "");
560	name.Trim();
561
562	strcpy(info.name, name.String());
563
564	return B_OK;
565}
566
567
568status_t
569ScreenMode::GetDeviceInfo(accelerant_device_info& info)
570{
571	BScreen screen(fWindow);
572	return screen.GetDeviceInfo(&info);
573}
574
575
576screen_mode
577ScreenMode::ModeAt(int32 index)
578{
579	if (index < 0)
580		index = 0;
581	else if (index >= (int32)fModeCount)
582		index = fModeCount - 1;
583
584	screen_mode mode;
585	mode.SetTo(fModeList[index]);
586
587	return mode;
588}
589
590
591const display_mode&
592ScreenMode::DisplayModeAt(int32 index)
593{
594	if (index < 0)
595		index = 0;
596	else if (index >= (int32)fModeCount)
597		index = fModeCount - 1;
598
599	return fModeList[index];
600}
601
602
603int32
604ScreenMode::CountModes()
605{
606	return fModeCount;
607}
608
609
610/*!	Searches for a similar mode in the reported mode list, and if that does not
611	find a matching mode, it will compute the mode manually using the GTF.
612*/
613bool
614ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode)
615{
616	uint16 virtualWidth, virtualHeight;
617	int32 bestIndex = -1;
618	float bestDiff = 999;
619
620	virtualWidth = mode.combine == kCombineHorizontally
621		? mode.width * 2 : mode.width;
622	virtualHeight = mode.combine == kCombineVertically
623		? mode.height * 2 : mode.height;
624
625	// try to find mode in list provided by driver
626	for (uint32 i = 0; i < fModeCount; i++) {
627		if (fModeList[i].virtual_width != virtualWidth
628			|| fModeList[i].virtual_height != virtualHeight
629			|| (color_space)fModeList[i].space != mode.space)
630			continue;
631
632		// Accept the mode if the computed refresh rate of the mode is within
633		// 0.6 percent of the refresh rate specified by the caller.  Note that
634		// refresh rates computed from mode parameters is not exact; especially
635		// some of the older modes such as 640x480, 800x600, and 1024x768.
636		// The tolerance of 0.6% was obtained by examining the various possible
637		// modes.
638
639		float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh);
640		if (refreshDiff < 0.006 * mode.refresh) {
641			// Accept this mode.
642			displayMode = fModeList[i];
643			displayMode.h_display_start = 0;
644			displayMode.v_display_start = 0;
645
646			// Since the computed refresh rate of the selected mode might differ
647			// from selected refresh rate by a few tenths (e.g. 60.2 instead of
648			// 60.0), tweak the pixel clock so the the refresh rate of the mode
649			// matches the selected refresh rate.
650
651			displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total
652				* displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5);
653			return true;
654		}
655
656		// Mode not acceptable.
657
658		if (refreshDiff < bestDiff) {
659			bestDiff = refreshDiff;
660			bestIndex = i;
661		}
662	}
663
664	// we didn't find the exact mode, but something very similar?
665	if (bestIndex == -1)
666		return false;
667
668	displayMode = fModeList[bestIndex];
669	displayMode.h_display_start = 0;
670	displayMode.v_display_start = 0;
671
672	// For the mode selected by the width, height, and refresh rate, compute
673	// the video timing parameters for the mode by using the VESA Generalized
674	// Timing Formula (GTF).
675	compute_display_timing(mode.width, mode.height, mode.refresh, false,
676		&displayMode.timing);
677
678	return true;
679}
680