1 |
package Xconfig::monitor; #- $Id: monitor.pm 261588 2009-10-08 17:02:40Z blino $ |
2 |
|
3 |
use diagnostics; |
4 |
use strict; |
5 |
|
6 |
use Xconfig::xfree; |
7 |
use detect_devices; |
8 |
use common; |
9 |
use any; |
10 |
use log; |
11 |
|
12 |
|
13 |
sub good_default_monitor() { |
14 |
detect_devices::is_xbox() ? 'Generic|640x480 @ 60 Hz' : |
15 |
arch() =~ /ppc/ ? |
16 |
(detect_devices::get_mac_model() =~ /^iBook/ ? 'Apple|iBook 800x600' : 'Apple|iMac/PowerBook 1024x768') : |
17 |
(detect_devices::isLaptop() ? 'Generic|Flat Panel 1024x768' : 'Generic|1024x768 @ 60 Hz'); |
18 |
} |
19 |
|
20 |
sub default_monitor { |
21 |
my ($card_Driver) = @_; |
22 |
if (detect_devices::is_virtualbox() || ($card_Driver eq 'siliconmotion' && arch() =~ /mips/)) { |
23 |
# HACK: since there is no way to get the EDID on gdium, the resolution is passed to the kernel |
24 |
# so we can rely on it |
25 |
# in vbox, we return Plug'n'Play because the vbox integration addons |
26 |
# will take care of everything for us |
27 |
{ VendorName => "Plug'n Play" }; |
28 |
} else { |
29 |
good_default_monitor() =~ /(.*)\|(.*)/ or internal_error("bad good_default_monitor"); |
30 |
{ VendorName => $1, ModelName => $2 }; |
31 |
} |
32 |
} |
33 |
|
34 |
my @VertRefresh_ranges = ("50-70", "50-90", "50-100", "40-150"); |
35 |
|
36 |
my @HorizSync_ranges = ( |
37 |
"31.5", |
38 |
"31.5-35.1", |
39 |
"31.5-37.9", |
40 |
"31.5-48.5", |
41 |
"31.5-57.0", |
42 |
"31.5-64.3", |
43 |
"31.5-79.0", |
44 |
"31.5-82.0", |
45 |
"31.5-88.0", |
46 |
"31.5-94.0", |
47 |
); |
48 |
|
49 |
sub configure { |
50 |
my ($in, $raw_X, $nb_monitors, $o_probed_info, $b_auto) = @_; |
51 |
|
52 |
my $monitors = [ $raw_X->get_or_new_monitors($nb_monitors) ]; |
53 |
if ($o_probed_info) { |
54 |
put_in_hash($monitors->[0], $o_probed_info); |
55 |
} |
56 |
my $head_nb = 1; |
57 |
foreach my $monitor (@$monitors) { |
58 |
choose($in, $raw_X, $monitor, @$monitors > 1 ? $head_nb++ : 0, $b_auto) or return; |
59 |
} |
60 |
$raw_X->set_monitors(@$monitors); |
61 |
$monitors; |
62 |
} |
63 |
|
64 |
sub configure_auto_install { |
65 |
my ($raw_X, $old_X) = @_; |
66 |
|
67 |
if ($old_X->{monitor}) { |
68 |
#- keep compatibility |
69 |
$old_X->{monitor}{VertRefresh} = $old_X->{monitor}{vsyncrange}; |
70 |
$old_X->{monitor}{HorizSync} = $old_X->{monitor}{hsyncrange}; |
71 |
|
72 |
#- new name |
73 |
$old_X->{monitors} = [ delete $old_X->{monitor} ]; |
74 |
} |
75 |
|
76 |
my $monitors = [ $raw_X->get_or_new_monitors($old_X->{monitors} ? int @{$old_X->{monitors}} : 1) ]; |
77 |
mapn { |
78 |
my ($monitor, $auto_install_monitor) = @_; |
79 |
put_in_hash($monitor, $auto_install_monitor); |
80 |
configure_automatic($monitor); |
81 |
} $monitors, $old_X->{monitors} if $old_X->{monitors}; |
82 |
|
83 |
my $card_Driver; |
84 |
if (!is_valid($monitors->[0])) { |
85 |
my ($first_card) = Xconfig::card::probe(); |
86 |
$card_Driver = $first_card->{Driver} if $first_card; |
87 |
put_in_hash($monitors->[0], probe($card_Driver)); |
88 |
} |
89 |
|
90 |
foreach my $monitor (@$monitors) { |
91 |
if (!is_valid($monitor)) { |
92 |
put_in_hash($monitor, default_monitor($card_Driver)); |
93 |
configure_automatic($monitor) or internal_error("good_default_monitor (" . good_default_monitor() . ") is unknown in MonitorsDB"); |
94 |
} |
95 |
} |
96 |
$raw_X->set_monitors(@$monitors); |
97 |
$monitors; |
98 |
} |
99 |
|
100 |
sub choose { |
101 |
my ($in, $raw_X, $monitor, $head_nb, $b_auto) = @_; |
102 |
|
103 |
my $ok = is_valid($monitor); |
104 |
if ($b_auto && $ok) { |
105 |
return $ok; |
106 |
} |
107 |
|
108 |
my (@l_monitors, %h_monitors); |
109 |
foreach (monitors_db()) { |
110 |
my $s = "$_->{VendorName}|$_->{ModelName}"; |
111 |
push @l_monitors, $s; |
112 |
$h_monitors{$s} = $_; |
113 |
} |
114 |
$h_monitors{"Plug'n Play"} = {}; |
115 |
|
116 |
ask_monitor: |
117 |
my $merge_name = sub { |
118 |
my ($monitor) = @_; |
119 |
$monitor->{ModelName} ? $monitor->{VendorName} . '|' . $monitor->{ModelName} : $monitor->{VendorName}; |
120 |
}; |
121 |
my $merged_name = do { |
122 |
my $merged_name = $merge_name->($monitor); |
123 |
if (!exists $h_monitors{$merged_name}) { |
124 |
$merged_name = is_valid($monitor) ? 'Custom' : |
125 |
$merge_name->(default_monitor($raw_X->get_Driver)); |
126 |
} |
127 |
$merged_name; |
128 |
}; |
129 |
|
130 |
$in->ask_from_({ title => N("_: This is a display device\nMonitor"), |
131 |
messages => $head_nb ? N("Choose a monitor for head #%d", $head_nb) : N("Choose a monitor"), |
132 |
interactive_help_id => 'configureX_monitor' |
133 |
}, |
134 |
[ { val => \$merged_name, separator => '|', |
135 |
list => ['Custom', "Plug'n Play", uniq(@l_monitors)], |
136 |
format => sub { $_[0] eq 'Custom' ? N("Custom") : |
137 |
$_[0] eq "Plug'n Play" ? N("Plug'n Play") . ($monitor->{VendorName} eq "Plug'n Play" ? " ($monitor->{ModelName})" : '') : |
138 |
$_[0] =~ /^Generic\|(.*)/ ? N("Generic") . "|$1" : |
139 |
N("Vendor") . "|$_[0]" }, |
140 |
sort => !$in->isa('interactive::gtk') } ]) or return; |
141 |
|
142 |
if ($merged_name eq "Plug'n Play") { |
143 |
local $::noauto = 0; #- hey, you asked for plug'n play, so i do probe! |
144 |
delete @$monitor{'VendorName', 'ModelName', 'EISA_ID', 'HorizSync', 'VertRefresh'}; |
145 |
if ($head_nb <= 1) { |
146 |
if (my $probed_info = probe($raw_X->get_Driver)) { |
147 |
put_in_hash($monitor, $probed_info); |
148 |
} else { |
149 |
log::l("Plug'n Play probing failed, but Xorg may do better"); |
150 |
$monitor->{VendorName} = "Plug'n Play"; |
151 |
} |
152 |
} else { |
153 |
$monitor->{VendorName} = "Plug'n Play"; |
154 |
} |
155 |
} elsif ($merged_name eq 'Custom') { |
156 |
$in->ask_from('', |
157 |
N("The two critical parameters are the vertical refresh rate, which is the rate |
158 |
at which the whole screen is refreshed, and most importantly the horizontal |
159 |
sync rate, which is the rate at which scanlines are displayed. |
160 |
|
161 |
It is VERY IMPORTANT that you do not specify a monitor type with a sync range |
162 |
that is beyond the capabilities of your monitor: you may damage your monitor. |
163 |
If in doubt, choose a conservative setting."), |
164 |
[ { val => \$monitor->{HorizSync}, list => \@HorizSync_ranges, label => N("Horizontal refresh rate"), not_edit => 0 }, |
165 |
{ val => \$monitor->{VertRefresh}, list => \@VertRefresh_ranges, label => N("Vertical refresh rate"), not_edit => 0 } ]) or goto &choose; |
166 |
delete @$monitor{'VendorName', 'ModelName', 'EISA_ID'}; |
167 |
} else { |
168 |
put_in_hash($monitor, $h_monitors{$merged_name}); |
169 |
} |
170 |
$monitor->{manually_chosen} = 1; |
171 |
1; |
172 |
} |
173 |
|
174 |
sub _configure_automatic_LCD { |
175 |
my ($monitor) = @_; |
176 |
|
177 |
$monitor->{HorizSync} && $monitor->{VertRefresh} and return; |
178 |
|
179 |
$monitor->{preferred_resolution} |
180 |
&& Xconfig::xfree::resolution2ratio($monitor->{preferred_resolution}) eq '16/10' or return; |
181 |
|
182 |
log::l("no HorizSync nor VertRefresh, using preferred resolution (hopefully this is a flat panel)"); |
183 |
add2hash($monitor, generic_flat_panel($monitor->{preferred_resolution})); |
184 |
1; |
185 |
} |
186 |
|
187 |
sub configure_automatic { |
188 |
my ($monitor) = @_; |
189 |
|
190 |
if ($monitor->{EISA_ID}) { |
191 |
log::l("EISA_ID: $monitor->{EISA_ID}"); |
192 |
if (my $mon = find { lc($_->{EISA_ID}) eq $monitor->{EISA_ID} } monitors_db()) { |
193 |
add2hash($monitor, $mon); |
194 |
log::l("EISA_ID corresponds to: $monitor->{ModelName}"); |
195 |
} elsif (!$monitor->{HorizSync} || !$monitor->{VertRefresh}) { |
196 |
log::l("unknown EISA_ID and partial DDC probe, so unknown monitor"); |
197 |
delete @$monitor{'VendorName', 'ModelName', 'EISA_ID'}; |
198 |
} |
199 |
} elsif ($monitor->{VendorName}) { |
200 |
if (my $mon = find { $_->{VendorName} eq $monitor->{VendorName} && $_->{ModelName} eq $monitor->{ModelName} } monitors_db()) { |
201 |
put_in_hash($monitor, $mon); |
202 |
} |
203 |
} |
204 |
|
205 |
_configure_automatic_LCD($monitor); |
206 |
|
207 |
is_valid($monitor); |
208 |
} |
209 |
|
210 |
sub is_valid { |
211 |
my ($monitor) = @_; |
212 |
$monitor->{HorizSync} && $monitor->{VertRefresh} || $monitor->{VendorName} eq "Plug'n Play"; |
213 |
} |
214 |
|
215 |
sub probe { |
216 |
my ($o_card_Driver) = @_; |
217 |
probe_DDC() || probe_DMI() || probe_using_X($o_card_Driver); |
218 |
} |
219 |
|
220 |
#- some EDID are much too strict: |
221 |
#- the HorizSync range is too small to allow smaller resolutions |
222 |
sub adjust_HorizSync_from_edid { |
223 |
my ($monitor) = @_; |
224 |
|
225 |
my ($hmin, $hmax) = $monitor->{HorizSync} =~ /(\d+)-(\d+)/ or return; |
226 |
if ($hmin > 45) { |
227 |
log::l("replacing HorizSync $hmin-$hmax with 28.8-$hmax (allow 800x480)"); |
228 |
$monitor->{HorizSync} = "28.8-$hmax"; |
229 |
} |
230 |
} |
231 |
#- the VertRefresh range is too weird |
232 |
sub adjust_VertRefresh_from_edid { |
233 |
my ($monitor) = @_; |
234 |
|
235 |
my ($vmin, $vmax) = $monitor->{VertRefresh} =~ /(\d+)-(\d+)/ or return; |
236 |
if ($vmin > 60) { |
237 |
log::l("replacing VertRefresh $vmin-$vmax with 60-$vmax"); |
238 |
$monitor->{VertRefresh} = "60-$vmax"; |
239 |
} |
240 |
} |
241 |
|
242 |
sub probe_DDC() { |
243 |
my ($edid, $vbe) = any::monitor_full_edid() or return; |
244 |
my $monitor = eval($edid); |
245 |
|
246 |
if ($vbe =~ /Memory: (\d+)k/) { |
247 |
$monitor->{VideoRam_probed} = $1; |
248 |
} |
249 |
use_EDID($monitor); |
250 |
} |
251 |
|
252 |
sub use_EDID { |
253 |
my ($monitor) = @_; |
254 |
|
255 |
adjust_HorizSync_from_edid($monitor); |
256 |
adjust_VertRefresh_from_edid($monitor); |
257 |
|
258 |
$monitor->{ModeLine} = Xconfig::xfree::default_ModeLine(); |
259 |
my $detailed_timings = $monitor->{detailed_timings} || []; |
260 |
my @different_timings = uniq_ { $_->{horizontal_active} . 'x' . $_->{vertical_active} } @$detailed_timings; |
261 |
foreach (grep { !$_->{bad_ratio} } @$detailed_timings) { |
262 |
if (Xconfig::xfree::xorg_builtin_resolution($_->{horizontal_active}, $_->{vertical_active})) { |
263 |
#- we don't want the 4/3 modelines otherwise they conflict with the Xorg builtin vesamodes |
264 |
} else { |
265 |
unshift @{$monitor->{ModeLine}}, |
266 |
{ val => $_->{ModeLine}, pre_comment => $_->{ModeLine_comment} . "\n" }; |
267 |
} |
268 |
|
269 |
if (@different_timings == 1 && $_->{horizontal_active} >= 1024) { |
270 |
#- we don't use detailed_timing when it is 640x480 or 800x600, |
271 |
#- since 14" CRTs often give this even when they handle 1024x768 correctly (and desktop is no good in poor resolutions) |
272 |
|
273 |
#- should we care about {has_preferred_timing} ? |
274 |
$monitor->{preferred_resolution} = { X => $_->{horizontal_active}, Y => $_->{vertical_active} }; |
275 |
} |
276 |
} |
277 |
|
278 |
if ($monitor->{EISA_ID}) { |
279 |
$monitor->{VendorName} = "Plug'n Play"; |
280 |
$monitor->{ModelName} = $monitor->{monitor_name}; |
281 |
$monitor->{ModelName} =~ s/"/''/g; |
282 |
$monitor->{ModelName} =~ s/[\0-\x20]/ /g; |
283 |
} |
284 |
configure_automatic($monitor) or return; |
285 |
$monitor; |
286 |
} |
287 |
|
288 |
sub probe_using_X { |
289 |
my ($card_Driver) = @_; |
290 |
|
291 |
detect_devices::isLaptop() or return; |
292 |
|
293 |
$card_Driver ||= do { |
294 |
require Xconfig::card; |
295 |
my @cards = Xconfig::card::probe(); |
296 |
$cards[0]{Driver}; |
297 |
} or return; |
298 |
|
299 |
require modules; |
300 |
my @old_modules = modules::loaded_modules(); |
301 |
my $resolution = run_program::rooted_get_stdout($::prefix, 'monitor-probe-using-X', '--perl', $card_Driver); |
302 |
modules::unload(difference2([ modules::loaded_modules() ], \@old_modules)); |
303 |
|
304 |
$resolution = eval($resolution) or return; |
305 |
|
306 |
if (my $res = $resolution->[0]{preferred_resolution}) { |
307 |
generic_flat_panel($res); |
308 |
} else { |
309 |
log::l("at least one EDID was found in Xorg.log, so let Xorg autodetect the monitor"); |
310 |
{ VendorName => "Plug'n Play" }; |
311 |
} |
312 |
} |
313 |
|
314 |
sub probe_DMI() { |
315 |
my $res = detect_devices::probe_unique_name('Resolution'); |
316 |
$res && generic_flat_panel_txt($res); |
317 |
} |
318 |
|
319 |
sub generic_flat_panel { |
320 |
my ($resolution) = @_; |
321 |
generic_flat_panel_($resolution->{X}, $resolution->{Y}); |
322 |
} |
323 |
sub generic_flat_panel_txt { |
324 |
my ($resolution) = @_; |
325 |
my ($X, $Y) = $resolution =~ /(\d+)x(\d+)/ or log::l("bad resolution $resolution"), return; |
326 |
generic_flat_panel_($X, $Y); |
327 |
} |
328 |
sub generic_flat_panel_ { |
329 |
my ($X, $Y) = @_; |
330 |
{ |
331 |
VendorName => 'Generic', |
332 |
ModelName => "Flat Panel ${X}x${Y}", |
333 |
HorizSync => '28.8-' . ($X > 1920 ? '100' : '90'), VertRefresh => '60', |
334 |
preferred_resolution => { X => $X, Y => $Y }, |
335 |
}; |
336 |
} |
337 |
|
338 |
my $monitors_db; |
339 |
sub monitors_db() { |
340 |
$monitors_db ||= readMonitorsDB("$ENV{SHARE_PATH}/ldetect-lst/MonitorsDB"); |
341 |
@$monitors_db; |
342 |
} |
343 |
sub readMonitorsDB { |
344 |
my ($file) = @_; |
345 |
|
346 |
my @monitors_db; |
347 |
my $F = openFileMaybeCompressed($file); |
348 |
local $_; |
349 |
my $lineno = 0; while (<$F>) { |
350 |
$lineno++; |
351 |
s/\s+$//; |
352 |
/^#/ and next; |
353 |
/^$/ and next; |
354 |
|
355 |
my @fields = qw(VendorName ModelName EISA_ID HorizSync VertRefresh dpms); |
356 |
my %l; @l{@fields} = split /\s*;\s*/; |
357 |
push @monitors_db, \%l; |
358 |
} |
359 |
\@monitors_db; |
360 |
} |
361 |
|
362 |
|
363 |
1; |
364 |
|