From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / check-configs.pl
1 #!/usr/bin/perl -w
2 # Copyright © 2008-2017 Jamie Zawinski <jwz@jwz.org>
3 #
4 # Permission to use, copy, modify, distribute, and sell this software and its
5 # documentation for any purpose is hereby granted without fee, provided that
6 # the above copyright notice appear in all copies and that both that
7 # copyright notice and this permission notice appear in supporting
8 # documentation.  No representations are made about the suitability of this
9 # software for any purpose.  It is provided "as is" without express or 
10 # implied warranty.
11 #
12 # This parses the .c and .xml files and makes sure they are in sync: that
13 # options are spelled the same, and that all the numbers are in sync.
14 #
15 # It also converts the hacks/config/ XML files into the Android XML files.
16 #
17 # Created:  1-Aug-2008.
18
19 require 5;
20 use diagnostics;
21 use strict;
22
23 my $progname = $0; $progname =~ s@.*/@@g;
24 my ($version) = ('$Revision: 1.24 $' =~ m/\s(\d[.\d]+)\s/s);
25
26 my $verbose = 0;
27 my $debug_p = 0;
28
29
30 my $text_default_opts = '';
31 foreach (qw(text-mode text-literal text-file text-url text-program)) {
32   my $s = $_; $s =~ s/-(.)/\U$1/g; $s =~ s/url/URL/si;
33   $text_default_opts .= "{\"-$_\", \".$s\", XrmoptionSepArg, 0},\n";
34 }
35 my $image_default_opts = '';
36 foreach (qw(choose-random-images grab-desktop-images)) {
37   my $s = $_; $s =~ s/-(.)/\U$1/g;
38   $image_default_opts .= "{\"-$_\", \".$s\", XrmoptionSepArg, 0},\n";
39 }
40 my $xlockmore_default_opts = '';
41 foreach (qw(count cycles delay ncolors size font)) {
42   $xlockmore_default_opts .= "{\"-$_\", \".$_\", XrmoptionSepArg, 0},\n";
43 }
44 $xlockmore_default_opts .= 
45  "{\"-wireframe\", \".wireframe\", XrmoptionNoArg, \"true\"},\n" .
46  "{\"-3d\", \".use3d\", XrmoptionNoArg, \"true\"},\n" .
47  "{\"-no-3d\", \".use3d\", XrmoptionNoArg, \"false\"},\n";
48
49 my $thread_default_opts = 
50   "{\"-threads\",    \".useThreads\", XrmoptionNoArg, \"True\"},\n" .
51   "{\"-no-threads\", \".useThreads\", XrmoptionNoArg, \"False\"},\n";
52
53 my $analogtv_default_opts = '';
54 foreach (qw(color tint brightness contrast)) {
55   $analogtv_default_opts .= "{\"-tv-$_\", \".TV$_\", XrmoptionSepArg, 0},\n";
56 }
57
58 $analogtv_default_opts .= $thread_default_opts;
59
60
61
62 # Returns two tables:
63 # - A table of the default resource values.
64 # - A table of "-switch" => "resource: value", or "-switch" => "resource: %"
65 #
66 sub parse_src($) {
67   my ($saver) = @_;
68   my $file = lc($saver) . ".c";
69
70   # kludge...
71   $file = 'apple2-main.c' if ($file eq 'apple2.c');
72   $file = 'sproingiewrap.c' if ($file eq 'sproingies.c');
73   $file = 'b_lockglue.c' if ($file eq 'bubble3d.c');
74   $file = 'polyhedra-gl.c' if ($file eq 'polyhedra.c');
75   $file = 'companion.c' if ($file eq 'companioncube.c');
76   $file = 'rd-bomb.c' if ($file eq 'rdbomb.c');
77
78   my $ofile = $file;
79   $file = "glx/$ofile"          unless (-f $file);
80   $file = "../hacks/$ofile"     unless (-f $file);
81   $file = "../hacks/glx/$ofile" unless (-f $file);
82   my $body = '';
83   open (my $in, '<', $file) || error ("$ofile: $!");
84   while (<$in>) { $body .= $_; }
85   close $in;
86   $file =~ s@^.*/@@;
87
88   my $xlockmore_p = 0;
89   my $thread_p = ($body =~ m/THREAD_DEFAULTS/);
90   my $analogtv_p = ($body =~ m/ANALOGTV_DEFAULTS/);
91   my $text_p = ($body =~ m/"textclient\.h"/);
92   my $grab_p = ($body =~ m/load_image_async/);
93
94   $body =~ s@/\*.*?\*/@@gs;
95   $body =~ s@^#\s*(if|ifdef|ifndef|elif|else|endif).*$@@gm;
96   $body =~ s/(THREAD|ANALOGTV)_(DEFAULTS|OPTIONS)(_XLOCK)?//gs;
97   $body =~ s/__extension__//gs;
98
99   print STDERR "$progname: $file: defaults:\n" if ($verbose > 2);
100   my %res_to_val;
101   if ($body =~ m/_defaults\s*\[\]\s*=\s*{(.*?)}\s*;/s) {
102     foreach (split (/,\s*\n/, $1)) {
103       s/^\s*//s;
104       s/\s*$//s;
105       next if m/^0?$/s;
106       my ($key, $val) = m@^\"([^:\s]+)\s*:\s*(.*?)\s*\"$@;
107       print STDERR "$progname: $file: unparsable: $_\n" unless $key;
108       $key =~ s/^[.*]//s;
109       $res_to_val{$key} = $val;
110       print STDERR "$progname: $file:   $key = $val\n" if ($verbose > 2);
111     }
112   } elsif ($body =~ m/\#\s*define\s*DEFAULTS\s*\\?\s*(.*?)\n[\n#]/s) {
113     $xlockmore_p = 1;
114     my $str = $1;
115     $str =~ s/\"\s*\\\n\s*\"//gs;
116     $str =~ m/^\s*\"(.*?)\"\s*\\?\s*$/ || 
117       error ("$file: unparsable defaults: $str");
118     $str = $1;
119     $str =~ s/\s*\\n\s*/\n/gs;
120     foreach (split (/\n/, $str)) {
121       my ($key, $val) = m@^([^:\s]+)\s*:\s*(.*?)\s*$@;
122       print STDERR "$progname: $file: unparsable: $_\n" unless $key;
123       $key =~ s/^[.*]//s;
124       $val =~ s/"\s*"\s*$//s;
125       $res_to_val{$key} = $val;
126       print STDERR "$progname: $file:   $key = $val\n" if ($verbose > 2);
127     }
128
129     while ($body =~ s/^#\s*define\s+(DEF_([A-Z\d_]+))\s+\"([^\"]+)\"//m) {
130       my ($key1, $key2, $val) = ($1, lc($2), $3);
131       $key2 =~ s/_(.)/\U$1/gs;  # "foo_bar" -> "fooBar"
132       $key2 =~ s/Rpm/RPM/;      # kludge
133       $res_to_val{$key2} = $val;
134       print STDERR "$progname: $file:   $key1 ($key2) = $val\n" 
135         if ($verbose > 2);
136     }
137
138   } else {
139     error ("$file: no defaults");
140   }
141
142   $body =~ m/XSCREENSAVER_MODULE(_2)?\s*\(\s*\"([^\"]+)\"/ ||
143     error ("$file: no module name");
144   $res_to_val{progclass} = $2;
145   $res_to_val{doFPS} = 'false';
146   $res_to_val{textMode} = 'date';
147   $res_to_val{textLiteral} = '';
148   $res_to_val{textURL} =
149     'https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss';
150   $res_to_val{grabDesktopImages} = 'true';
151   $res_to_val{chooseRandomImages} = 'true';
152
153   print STDERR "$progname: $file:   progclass = $2\n" if ($verbose > 2);
154
155   print STDERR "$progname: $file: switches to resources:\n"
156     if ($verbose > 2);
157   my %switch_to_res;
158   $switch_to_res{'-fps'} = 'doFPS: true';
159   $switch_to_res{'-fg'}  = 'foreground: %';
160   $switch_to_res{'-bg'}  = 'background: %';
161   $switch_to_res{'-no-grab-desktop-images'}  = 'grabDesktopImages: false';
162   $switch_to_res{'-no-choose-random-images'}  = 'chooseRandomImages: false';
163
164   my ($ign, $opts) = ($body =~ m/(_options|\bopts)\s*\[\]\s*=\s*{(.*?)}\s*;/s);
165   if  ($xlockmore_p || $thread_p || $analogtv_p || $opts) {
166     $opts = '' unless $opts;
167     $opts .= ",\n$text_default_opts" if ($text_p);
168     $opts .= ",\n$image_default_opts" if ($grab_p);
169     $opts .= ",\n$xlockmore_default_opts" if ($xlockmore_p);
170     $opts .= ",\n$thread_default_opts" if ($thread_p);
171     $opts .= ",\n$analogtv_default_opts" if ($analogtv_p);
172
173     foreach (split (/,\s*\n/, $opts)) {
174       s/^\s*//s;
175       s/\s*$//s;
176       next if m/^$/s;
177       next if m/^\{\s*0\s*,/s;
178       my ($switch, $res, $type, $v0, $v1, $v2) =
179         m@^ \s* { \s * \"([^\"]+)\" \s* ,
180                   \s * \"([^\"]+)\" \s* ,
181                   \s * ([^\s]+)     \s* ,
182                   \s * (\"([^\"]*)\"|([a-zA-Z\d_]+)) \s* }@xi;
183       print STDERR "$progname: $file: unparsable: $_\n" unless $switch;
184       my $val = defined($v1) ? $v1 : $v2;
185       $val = '%' if ($type eq 'XrmoptionSepArg');
186       $res =~ s/^[.*]//s;
187       $res =~ s/^[a-z\d]+\.//si;
188       $switch =~ s/^\+/-no-/s;
189
190       $val = "$res: $val";
191       if (defined ($switch_to_res{$switch})) {
192         print STDERR "$progname: $file:   DUP! $switch = \"$val\"\n" 
193           if ($verbose > 2);
194       } else {
195         $switch_to_res{$switch} = $val;
196         print STDERR "$progname: $file:   $switch = \"$val\"\n" 
197           if ($verbose > 2);
198       }
199     }
200   } else {
201     error ("$file: no options");
202   }
203
204   return (\%res_to_val, \%switch_to_res);
205 }
206
207 my %video_dups;
208
209 # Returns a list of:
210 #    "resource = default value"
211 # or "resource != non-default value"
212 #
213 # Also a hash of the simplified XML contents.
214 #
215 sub parse_xml($$$) {
216   my ($saver, $switch_to_res, $src_opts) = @_;
217
218   my $saver_title = undef;
219   my $gl_p = 0;
220   my $file = "config/" . lc($saver) . ".xml";
221   my $ofile = $file;
222   $file = "../hacks/$ofile" unless (-f $file);
223   my $body = '';
224   open (my $in, '<', $file) || error ("$ofile: $!");
225   while (<$in>) { $body .= $_; }
226   close $in;
227   $file =~ s@^.*/@@;
228
229   my @result = ();
230
231   $body =~ s@<xscreensaver-text\s*/?>@
232     <select id="textMode">
233       <option id="date"  _label="Display the date and time"/>
234       <option id="text"  _label="Display static text"
235         arg-set="-text-mode literal"/>
236       <option id="url"   _label="Display the contents of a URL"
237         arg-set="-text-mode url"/>
238     </select>
239     <string id="textLiteral" _label="Text to display" arg="-text-literal %"/>
240     <string id="textURL" _label="URL to display" arg="-text-url %"/>
241     @gs;
242
243   $body =~ s@<xscreensaver-image\s*/?>@
244     <boolean id="grabDesktopImages" _label="Grab screenshots"
245        arg-unset="-no-grab-desktop-images"/>
246     <boolean id="chooseRandomImages" _label="Use photo library"
247        arg-unset="-no-choose-random-images"/>
248     @gs;
249
250   $body =~ s/<!--.*?-->/ /gsi;
251
252   $body =~ s@(<(_description)>.*?</\2>)@{ $_ = $1; s/\n/\002/gs; $_; }@gsexi;
253
254   $body =~ s/\s+/ /gs;
255   $body =~ s/</\001</gs;
256   $body =~ s/\001(<option)/$1/gs;
257
258   my $video = undef;
259
260   my @widgets = ();
261
262   print STDERR "$progname: $file: options:\n" if ($verbose > 2);
263   foreach (split (m/\001/, $body)) {
264     next if (m/^\s*$/s);
265     my ($type, $args) = m@^<([?/]?[-_a-z]+)\b\s*(.*)$@si;
266     error ("$progname: $file: unparsable: $_") unless $type;
267     next if ($type =~ m@^/@);
268
269     my $ctrl = { type => $type };
270
271     if ($type =~ m/^( [hv]group |
272                       \?xml |
273                       command |
274                       file |
275                       xscreensaver-image |
276                       xscreensaver-updater
277                     )/sx) {
278       $ctrl = undef;
279
280     } elsif ($type eq '_description') {
281       $args =~ s/\002/\n/gs;
282       $args =~ s@^>\s*@@s;
283       $args =~ s/^\n*|\s*$//gs;
284       $ctrl->{text} = $args;
285
286     } elsif ($type eq 'screensaver') {
287       ($saver_title) = ($args =~ m/\b_label\s*=\s*\"([^\"]+)\"/s);
288       ($gl_p) = ($args =~ m/\bgl="?yes/s);
289       my $s = $saver_title;
290       $s =~ s/\s+//gs;
291       my $val = "progclass = $s";
292       push @result, $val;
293       print STDERR "$progname: $file:   name:    $saver_title\n"
294         if ($verbose > 2);
295       $ctrl = undef;
296
297     } elsif ($type eq 'video') {
298       error ("$file: multiple videos") if $video;
299       ($video) = ($args =~ m/\bhref="(.*?)"/);
300       error ("$file: unparsable video") unless $video;
301       error ("$file: unparsable video URL")
302         unless ($video =~ m@^https?://www\.youtube\.com/watch\?v=[^?&]+$@s);
303       $ctrl = undef;
304
305     } elsif ($type eq 'select') {
306       $args =~ s/</\001</gs;
307       my @opts = split (/\001/, $args);
308       shift @opts;
309       my $unset_p = 0;
310       my $this_res = undef;
311       my @menu = ();
312       foreach (@opts) {
313         error ("$file: unparsable option: $_") unless (m/^<option\s/);
314
315         my %item;
316         my $opt = $_;
317         $opt =~ s@^<option\s+@@s;
318         $opt =~ s@[?/]>\s*$@@s;
319         while ($opt =~ s/^\s*([^\s]+)\s*=\s*"(.*?)"\s*(.*)/$3/s) {
320           my ($k, $v) = ($1, $2);
321           $item{$k} = $v;
322         }
323
324         error ("unparsable XML option line: $_ [$opt]") if ($opt);
325         push @menu, \%item;
326
327         my ($set) = $item{'arg-set'};
328         if ($set) {
329           my ($set2, $val) = ($set =~ m/^(.*?) (.*)$/s);
330           $set = $set2 if ($set2);
331           my ($res) = $switch_to_res->{$set};
332           error ("$file: no resource for select switch \"$set\"") unless $res;
333
334           my ($res2, $val2) = ($res =~ m/^(.*?): (.*)$/s);
335           error ("$file: unparsable select resource: $res") unless $res2;
336           $res = $res2;
337           $val = $val2 unless ($val2 eq '%');
338           $item{value} = $val;
339
340           error ("$file: mismatched resources: $res vs $this_res")
341             if (defined($this_res) && $this_res ne $res);
342           $this_res = $res;
343
344           $val = "$res != $val";
345           push @result, $val;
346           print STDERR "$progname: $file:   select:  $val\n" if ($verbose > 2);
347
348         } else {
349           error ("$file: multiple default options: $set") if ($unset_p);
350           $unset_p++;
351         }
352       }
353       $ctrl->{resource} = $this_res;
354       $ctrl->{default} = $src_opts->{$this_res};
355       $ctrl->{menu} = \@menu;
356
357     } else {
358
359       my $rest = $args;
360       $rest =~ s@[/?]*>\s*$@@s;
361       while ($rest =~ s/^\s*([^\s]+)\s*=\s*"(.*?)"\s*(.*)/$3/s) {
362         my ($k, $v) = ($1, $2);
363         $ctrl->{$k} = $v;
364       }
365       error ("unparsable XML line: $args [$rest]") if ($rest);
366
367       if ($type eq 'number') {
368         my ($arg) = $ctrl->{arg};
369         my ($val) = $ctrl->{default};
370         $val = '' unless defined($val);
371
372         my $switch = $arg;
373         $switch =~ s/\s+.*$//;
374         my ($res) = $switch_to_res->{$switch};
375         error ("$file: no resource for $type switch \"$arg\"") unless $res;
376
377         $res =~ s/: \%$//;
378         error ("$file: unparsable value: $res") if ($res =~ m/:/);
379         $ctrl->{resource} = $res;
380
381         $val = "$res = $val";
382         push @result, $val;
383         print STDERR "$progname: $file:   number:  $val\n" if ($verbose > 2);
384
385       } elsif ($type eq 'boolean') {
386         my ($set)   = $ctrl->{'arg-set'};
387         my ($unset) = $ctrl->{'arg-unset'};
388         my ($arg) = $set || $unset || error ("$file: unparsable: $args");
389         my ($res) = $switch_to_res->{$arg};
390           error ("$file: no resource for boolean switch \"$arg\"") unless $res;
391
392         my ($res2, $val) = ($res =~ m/^(.*?): (.*)$/s);
393         error ("$file: unparsable boolean resource: $res") unless $res2;
394         $res = $res2;
395
396         $ctrl->{resource} = $res;
397         $ctrl->{convert} = 'invert' if ($val =~ m/off|false|no/i);
398         $ctrl->{default} = ($ctrl->{convert} ? 'true' : 'false');
399
400 #       $val = ($set ? "$res != $val" : "$res = $val");
401         $val = "$res != $val";
402         push @result, $val;
403         print STDERR "$progname: $file:   boolean: $val\n" if ($verbose > 2);
404
405       } elsif ($type eq 'string') {
406         my ($arg) = $ctrl->{arg};
407
408         my $switch = $arg;
409         $switch =~ s/\s+.*$//;
410         my ($res) = $switch_to_res->{$switch};
411         error ("$file: no resource for $type switch \"$arg\"") unless $res;
412
413         $res =~ s/: \%$//;
414         error ("$file: unparsable value: $res") if ($res =~ m/:/);
415         $ctrl->{resource} = $res;
416         $ctrl->{default} = $src_opts->{$res};
417         my $val = "$res = %";
418         push @result, $val;
419         print STDERR "$progname: $file:   string:  $val\n" if ($verbose > 2);
420
421       } else {
422         error ("$file: unknown type \"$type\" for no arg");
423       }
424     }
425
426     push @widgets, $ctrl if $ctrl;
427   }
428
429 #  error ("$file: no video") unless $video;
430   print STDERR "\n$file: WARNING: no video\n\n" unless $video;
431
432   if ($video && $video_dups{$video} && 
433       $video_dups{$video} ne $saver_title) {
434     print STDERR "\n$file: WARNING: $saver_title: dup video with " .
435       $video_dups{$video} . "\n";
436   }
437   $video_dups{$video} = $saver_title if ($video);
438
439   return ($saver_title, $gl_p, \@result, \@widgets);
440 }
441
442
443 sub check_config($) {
444   my ($saver) = @_;
445
446   # kludge
447   return 0 if ($saver =~ m/(-helper)$/);
448
449   my ($src_opts, $switchmap) = parse_src ($saver);
450   my ($saver_title, $gl_p, $xml_opts, $widgets) =
451     parse_xml ($saver, $switchmap, $src_opts);
452
453   my $failures = 0;
454   foreach my $claim (@$xml_opts) {
455     my ($res, $compare, $xval) = ($claim =~ m/^(.*) (=|!=) (.*)$/s);
456     error ("$saver: unparsable xml claim: $claim") unless $compare;
457
458     my $sval = $src_opts->{$res};
459     if ($res =~ m/^TV|^text-mode/) {
460       print STDERR "$progname: $saver: OK: skipping \"$res\"\n"
461         if ($verbose > 1);
462     } elsif (!defined($sval)) {
463       print STDERR "$progname: $saver: $res: not in source\n";
464     } elsif ($claim !~ m/ = %$/s &&
465              ($compare eq '!='
466               ? $sval eq $xval
467               : $sval ne $xval)) {
468       print STDERR "$progname: $saver: " .
469         "src has \"$res = $sval\", xml has \"$claim\"\n";
470       $failures++;
471     } elsif ($verbose > 1) {
472       print STDERR "$progname: $saver: OK: \"$res = $sval\" vs \"$claim\"\n";
473     }
474   }
475
476   # Now make sure the progclass in the source and XML also matches
477   # the XCode target name.
478   #
479   my $obd = "../OSX/build/Debug";
480   if (-d $obd) {
481     my $progclass = $src_opts->{progclass};
482     $progclass = 'DNAlogo' if ($progclass eq 'DNALogo');
483     my $f = (glob("$obd/$progclass.saver*"))[0];
484     if (!$f && $progclass ne 'Flurry') {
485       print STDERR "$progname: $progclass.saver does not exist\n";
486       $failures++;
487     }
488   }
489
490   print STDERR "$progname: $saver: OK\n"
491     if ($verbose == 1 && $failures == 0);
492
493   return $failures;
494 }
495
496
497 # Returns true if the two files differ (by running "cmp")
498 #
499 sub cmp_files($$) {
500   my ($file1, $file2) = @_;
501
502   my @cmd = ("cmp", "-s", "$file1", "$file2");
503   print STDERR "$progname: executing \"" . join(" ", @cmd) . "\"\n"
504     if ($verbose > 3);
505
506   system (@cmd);
507   my $exit_value  = $? >> 8;
508   my $signal_num  = $? & 127;
509   my $dumped_core = $? & 128;
510
511   error ("$cmd[0]: core dumped!") if ($dumped_core);
512   error ("$cmd[0]: signal $signal_num!") if ($signal_num);
513   return $exit_value;
514 }
515
516
517 sub diff_files($$) {
518   my ($file1, $file2) = @_;
519
520   my @cmd = ("diff", 
521              "-U1",
522 #            "-w",
523              "--unidirectional-new-file", "$file1", "$file2");
524   print STDERR "$progname: executing \"" . join(" ", @cmd) . "\"\n"
525     if ($verbose > 3);
526
527   system (@cmd);
528   my $exit_value  = $? >> 8;
529   my $signal_num  = $? & 127;
530   my $dumped_core = $? & 128;
531
532   error ("$cmd[0]: core dumped!") if ($dumped_core);
533   error ("$cmd[0]: signal $signal_num!") if ($signal_num);
534   return $exit_value;
535 }
536
537
538 # If the two files differ:
539 #   mv file2 file1
540 # else
541 #   rm file2
542 #
543 sub rename_or_delete($$;$) {
544   my ($file, $file_tmp, $suffix_msg) = @_;
545
546   my $changed_p = cmp_files ($file, $file_tmp);
547
548   if ($changed_p && $debug_p) {
549     print STDOUT "\n" . ('#' x 79) . "\n";
550     diff_files ("$file", "$file_tmp");
551     $changed_p = 0;
552   }
553
554   if ($changed_p) {
555
556     if (!rename ("$file_tmp", "$file")) {
557       unlink "$file_tmp";
558       error ("mv $file_tmp $file: $!");
559     }
560     print STDERR "$progname: wrote $file" .
561       ($suffix_msg ? " $suffix_msg" : "") . "\n";
562
563   } else {
564     unlink "$file_tmp" || error ("rm $file_tmp: $!\n");
565     print STDERR "$file unchanged" .
566                  ($suffix_msg ? " $suffix_msg" : "") . "\n"
567         if ($verbose);
568     print STDERR "$progname: rm $file_tmp\n" if ($verbose > 2);
569   }
570 }
571
572
573 # Write the given body to the file, but don't alter the file's
574 # date if the new content is the same as the existing content.
575 #
576 sub write_file_if_changed($$;$) {
577   my ($outfile, $body, $suffix_msg) = @_;
578
579   my $file_tmp = "$outfile.tmp";
580   open (my $out, '>', $file_tmp) || error ("$file_tmp: $!");
581   (print $out $body) || error ("$file_tmp: $!");
582   close $out || error ("$file_tmp: $!");
583   rename_or_delete ($outfile, $file_tmp, $suffix_msg);
584 }
585
586
587 # Read the template file and splice in the @KEYWORDS@ in the hash.
588 #
589 sub read_template($$) {
590   my ($file, $subs) = @_;
591   my $body = '';
592   open (my $in, '<', $file) || error ("$file: $!");
593   while (<$in>) { $body .= $_; }
594   close $in;
595
596   $body =~ s@/\*.*?\*/@@gs;  # omit comments
597   $body =~ s@//.*$@@gm;
598
599   foreach my $key (keys %$subs) {
600     my $val = $subs->{$key};
601     $body =~ s/@\Q$key\E@/$val/gs;
602   }
603
604   if ($body =~ m/(@[-_A-Z\d]+@)/s) {
605     error ("$file: unmatched: $1 [$body]");
606   }
607
608   $body =~ s/[ \t]+$//gm;
609   $body =~ s/(\n\n)\n+/$1/gs;
610   return $body;
611 }
612
613
614 # This is duplicated in OSX/update-info-plist.pl
615 #
616 sub munge_blurb($$$$) {
617   my ($filename, $name, $vers, $desc) = @_;
618
619   $desc =~ s/^([ \t]*\n)+//s;
620   $desc =~ s/\s*$//s;
621
622   # in case it's done already...
623   $desc =~ s@<!--.*?-->@@gs;
624   $desc =~ s/^.* version \d[^\n]*\n//s;
625   $desc =~ s/^From the XScreenSaver.*\n//m;
626   $desc =~ s@^https://www\.jwz\.org/xscreensaver.*\n@@m;
627   $desc =~
628        s/\nCopyright [^ \r\n\t]+ (\d{4})(-\d{4})? (.*)\.$/\nWritten $3; $1./s;
629   $desc =~ s/^\n+//s;
630
631   error ("$filename: description contains markup: $1")
632     if ($desc =~ m/([<>&][^<>&\s]*)/s);
633   error ("$filename: description contains ctl chars: $1")
634     if ($desc =~ m/([\000-\010\013-\037])/s);
635
636   error ("$filename: can't extract authors")
637     unless ($desc =~ m@^(.*)\nWritten by[ \t]+(.+)$@s);
638   $desc = $1;
639   my $authors = $2;
640   $desc =~ s/\s*$//s;
641
642   my $year = undef;
643   if ($authors =~ m@^(.*?)\s*[,;]\s+(\d\d\d\d)([-\s,;]+\d\d\d\d)*[.]?$@s) {
644     $authors = $1;
645     $year = $2;
646   }
647
648   error ("$filename: can't extract year") unless $year;
649   my $cyear = 1900 + ((localtime())[5]);
650   $year = "$cyear" unless $year;
651   if ($year && ! ($year =~ m/$cyear/)) {
652     $year = "$year-$cyear";
653   }
654
655   $authors =~ s/[.,;\s]+$//s;
656
657   # List me as a co-author on all of them, since I'm the one who
658   # did the OSX port, packaged it up, and built the executables.
659   #
660   my $curator = "Jamie Zawinski";
661   if (! ($authors =~ m/$curator/si)) {
662     if ($authors =~ m@^(.*?),? and (.*)$@s) {
663       $authors = "$1, $2, and $curator";
664     } else {
665       $authors .= " and $curator";
666     }
667   }
668
669   my $desc1 = ("$name, version $vers.\n\n" .            # savername.xml
670                $desc . "\n" .
671                "\n" . 
672                "From the XScreenSaver collection: " .
673                "https://www.jwz.org/xscreensaver/\n" .
674                "Copyright \302\251 $year by $authors.\n");
675
676   my $desc2 = ("$name $vers,\n" .                       # Info.plist
677                "\302\251 $year $authors.\n" .
678                #"From the XScreenSaver collection:\n" .
679                #"https://www.jwz.org/xscreensaver/\n" .
680                "\n" .
681                $desc .
682                "\n");
683
684   # unwrap lines, but only when it's obviously ok: leave blank lines,
685   # and don't unwrap if that would compress leading whitespace on a line.
686   #
687   $desc2 =~ s/^(From |https?:)/\n$1/gm;
688   1 while ($desc2 =~ s/([^\s])[ \t]*\n([^\s])/$1 $2/gs);
689   $desc2 =~ s/\n\n(From |https?:)/\n$1/gs;
690
691   return ($desc1, $desc2);
692 }
693
694
695 sub build_android(@) {
696   my (@savers) = @_;
697
698   my $package     = "org.jwz.xscreensaver";
699   my $project_dir = "project/xscreensaver";
700   my $xml_dir     = "$project_dir/res/xml";
701   my $values_dir  = "$project_dir/res/values";
702   my $java_dir    = "$project_dir/src/org/jwz/xscreensaver/gen";
703   my $gen_dir     = "gen";
704
705   my $xml_header = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
706
707   my $manifest = '';
708   my $daydream_java = '';
709   my $settings_java = '';
710   my $wallpaper_java = '';
711   my $fntable_h2 = '';
712   my $fntable_h3 = '';
713   my $arrays   = '';
714   my $strings  = '';
715   my %write_files;
716   my %string_dups;
717
718   my $vers;
719   {
720     my $file = "../utils/version.h";
721     my $body = '';
722     open (my $in, '<', $file) || error ("$file: $!");
723     while (<$in>) { $body .= $_; }
724     close $in;
725     ($vers) = ($body =~ m@ (\d+\.\d+) @s);
726     error ("$file: no version number") unless $vers;
727   }
728
729
730   foreach my $saver (@savers) {
731     next if ($saver =~ m/(-helper)$/);
732     $saver = 'rdbomb' if ($saver eq 'rd-bomb');
733
734     my ($src_opts, $switchmap) = parse_src ($saver);
735     my ($saver_title, $gl_p, $xml_opts, $widgets) =
736       parse_xml ($saver, $switchmap, $src_opts);
737
738     my $saver_class = "${saver_title}";
739     $saver_class =~ s/\s+//gs;
740     $saver_class =~ s/^([a-z])/\U$1/gs;  # upcase first letter
741
742     $saver_title =~ s/(.[a-z])([A-Z\d])/$1 $2/gs;       # Spaces in InterCaps
743     $saver_title =~ s/^(GL|RD)[- ]?(.)/$1 \U$2/gs;      # Space after "GL"
744     $saver_title =~ s/^Apple ?2$/Apple &#x5D;&#x5B;/gs; # "Apple ]["
745     $saver_title =~ s/(m)oe(bius)/$1&#xF6;$2/gsi;       # &ouml;
746     $saver_title =~ s/(moir)e/$1&#xE9;/gsi;             # &eacute;
747     $saver_title =~ s/^([a-z])/\U$1/s;                  # "M6502" for sorting
748
749     my $settings = '';
750
751     my $localize0 = sub($$) {
752       my ($key, $string) = @_;
753       $string =~ s@([\\\"\'])@\\$1@gs;          # backslashify
754       $string =~ s@\n@\\n@gs;                   # quote newlines
755       $key =~ s@[^a-z\d_]+@_@gsi;               # illegal characters
756
757       my $old = $string_dups{$key};
758       error ("dup string: $key: \"$old\" != \"$string\"")
759         if (defined($old) && $old ne $string);
760       $string_dups{$key} = $string;
761
762       my $fmt = ($string =~ m/%/ ? ' formatted="false"' : '');
763       $strings .= "<string name=\"${key}\"$fmt>$string</string>\n"
764         unless defined($old);
765       return "\@string/$key";
766     };
767
768     $localize0->('app_name', 'XScreenSaver');
769
770     $settings .= ("<Preference\n" .
771                   "  android:key=\"${saver}_reset\"\n" .
772                   "  android:title=\"" .
773                       $localize0->('reset_to_defaults', 'Reset to defaults') .
774                      "\"\n" .
775                   " />\n");
776
777     my $daydream_desc = '';
778     foreach my $widget (@$widgets) {
779       my $type  = $widget->{type};
780       my $rsrc  = $widget->{resource};
781       my $label = $widget->{_label};
782       my $def   = $widget->{default};
783       my $invert_p = (($widget->{convert} || '') eq 'invert');
784
785       my $key   = "${saver}_$rsrc" if $rsrc;
786
787       #### The menus don't actually have titles on X11 or Cocoa...
788       $label = $widget->{resource} unless $label;
789
790       my $localize = sub($;$) {
791         my ($string, $suf) = @_;
792         $suf = 'title' unless $suf;
793         return $localize0->("${saver}_${rsrc}_${suf}", $string);
794       };
795
796       if ($type eq 'slider' || $type eq 'spinbutton') {
797
798         my $low        = $widget->{low};
799         my $high       = $widget->{high};
800         my $float_p    = $low =~ m/[.]/;
801         my $low_label  = $widget->{'_low-label'};
802         my $high_label = $widget->{'_high-label'};
803
804         $low_label  = $low  unless defined($low_label);
805         $high_label = $high unless defined($high_label);
806
807         ($low, $high) = ($high, $low)
808           if (($widget->{convert} || '') eq 'invert');
809
810         $settings .=
811           ("<$package.SliderPreference\n" .
812            "  android:layout=\"\@layout/slider_preference\"\n" .
813            "  android:key=\"${key}\"\n" .
814            "  android:title=\"" . $localize->($label) . "\"\n" .
815            "  android:defaultValue=\"$def\"\n" .
816            "  low=\"$low\"\n" .
817            "  high=\"$high\"\n" .
818            "  lowLabel=\""  . $localize->($low_label,  'low_label')  . "\"\n" .
819            "  highLabel=\"" . $localize->($high_label, 'high_label') . "\"\n" .
820            "  integral=\"" .($float_p ? 'false' : 'true'). "\" />\n");
821
822       } elsif ($type eq 'boolean') {
823
824         my $def = ($invert_p ? 'true' : 'false');
825         $settings .=
826           ("<CheckBoxPreference\n" .
827            "  android:key=\"${key}\"\n" .
828            "  android:title=\"" . $localize->($label) . "\"\n" .
829            "  android:defaultValue=\"$def\" />\n");
830
831       } elsif ($type eq 'select') {
832
833         $label =~ s/^(.)/\U$1/s;  # upcase first letter of menu title
834         $label =~ s/[-_]/ /gs;
835         $label =~ s/([a-z])([A-Z])/$1 $2/gs;
836         $def = '' unless defined ($def);
837         $settings .=
838           ("<ListPreference\n" .
839            "  android:key=\"${key}\"\n" .
840            "  android:title=\"" . $localize->($label, 'menu') . "\"\n" .
841            "  android:entries=\"\@array/${key}_entries\"\n" .
842            "  android:defaultValue=\"$def\"\n" .
843            "  android:entryValues=\"\@array/${key}_values\" />\n");
844
845         my $a1 = '';
846         foreach my $item (@{$widget->{menu}}) {
847           my $val = $item->{value};
848           if (! defined($val)) {
849             $val = $src_opts->{$widget->{resource}};
850             error ("$saver: no default resource in option menu " .
851                    $item->{_label})
852               unless defined($val);
853           }
854           $val =~ s@([\\\"\'])@\\$1@gs;         # backslashify
855           $a1 .= "  <item>$val</item>\n";
856         }
857
858         my $a2 = '';
859         foreach my $item (@{$widget->{menu}}) {
860           my $val = $item->{value};
861           $val = $src_opts->{$widget->{resource}} unless defined($val);
862           $a2 .= ("  <item>" . $localize->($item->{_label}, $val) .
863                   "</item>\n");
864         }
865
866         my $fmt1 = ($a1 =~ m/%/ ? ' formatted="false"' : '');
867         my $fmt2 = ($a2 =~ m/%/ ? ' formatted="false"' : '');
868         $arrays .= ("<string-array name=\"${key}_values\"$fmt1>\n" .
869                     $a1 .
870                     "</string-array>\n" .
871                     "<string-array name=\"${key}_entries\"$fmt2>\n" .
872                     $a2 .
873                     "</string-array>\n");
874
875       } elsif ($type eq 'string') {
876
877         $def =~ s/&/&amp;/gs;
878         $settings .=
879           ("<EditTextPreference\n" .
880            "  android:key=\"${key}\"\n" .
881            "  android:title=\"" . $localize->($label) . "\"\n" .
882            "  android:defaultValue=\"$def\" />\n");
883
884       } elsif ($type eq 'file') {
885
886       } elsif ($type eq '_description') {
887
888         $type = 'description';
889         $rsrc = $type;
890         my $desc = $widget->{text};
891         (undef, $desc) = munge_blurb ($saver, $saver_title, $vers, $desc);
892
893         # Lose the Wikipedia URLs.
894         $desc =~ s@https?:.*?\b(wikipedia|mathworld)\b[^\s]+[ \t]*\n?@@gm;
895         $desc =~ s/(\n\n)\n+/$1/s;
896         $desc =~ s/\s*$/\n\n\n/s;
897
898         $daydream_desc = $desc;
899
900         my ($year) = ($daydream_desc =~ m/\b((19|20)\d\d)\b/s);
901         error ("$saver: no year") unless $year;
902         $daydream_desc =~ s/^.*?\n\n//gs;
903         $daydream_desc =~ s/\n.*$//gs;
904         $daydream_desc = "$year: $daydream_desc";
905         $daydream_desc =~ s/^(.{72}).+$/$1.../s;
906
907         $settings .=
908           ("<Preference\n" .
909            "  android:icon=\"\@drawable/thumbnail\"\n" .
910            "  android:key=\"${saver}_${type}\"\n" .
911 #           "  android:selectable=\"false\"\n" .
912            "  android:persistent=\"false\"\n" .
913            "  android:layout=\"\@layout/preference_blurb\"\n" .
914            "  android:summary=\"" . $localize->($desc) . "\">\n" .
915            "  <intent android:action=\"android.intent.action.VIEW\"\n" .
916            "    android:data=\"https://www.jwz.org/xscreensaver/\" />\n" .
917            "</Preference>\n");
918
919       } else {
920         error ("unhandled type: $type");
921       }
922     }
923
924     my $heading = "XScreenSaver: $saver_title";
925
926     $settings =~ s/^/  /gm;
927     $settings = ($xml_header .
928                  "<PreferenceScreen xmlns:android=\"" .
929                  "http://schemas.android.com/apk/res/android\"\n" .
930                  "  android:title=\"" .
931                  $localize0->("${saver}_settings_title", $heading) . "\">\n" .
932                  $settings .
933                  "</PreferenceScreen>\n");
934
935     my $saver_underscore = $saver;
936     $saver_underscore =~ s/-/_/g;
937     $write_files{"$xml_dir/${saver_underscore}_settings.xml"} = $settings;
938
939     $manifest .= ("<service android:label=\"" .
940                      $localize0->("${saver_underscore}_saver_title",
941                                   $saver_title) .
942                      "\"\n" .
943                   "  android:summary=\"" .
944                        $localize0->("${saver_underscore}_saver_desc",
945                                     $daydream_desc) . "\"\n" .
946                   "  android:name=\".gen.Daydream\$$saver_class\"\n" .
947                   "  android:permission=\"android.permission" .
948                        ".BIND_DREAM_SERVICE\"\n" .
949                   "  android:exported=\"true\"\n" .
950                   "  android:icon=\"\@drawable/${saver_underscore}\">\n" .
951                   "  <intent-filter>\n" .
952                   "    <action android:name=\"android.service.dreams" .
953                         ".DreamService\" />\n" .
954                   "    <category android:name=\"android.intent.category" .
955                         ".DEFAULT\" />\n" .
956                   "  </intent-filter>\n" .
957                   "  <meta-data android:name=\"android.service.dream\"\n" .
958                   "    android:resource=\"\@xml/${saver}_dream\" />\n" .
959                   "</service>\n" .
960                   "<service android:label=\"" .
961                      $localize0->("${saver_underscore}_saver_title",
962                                   $saver_title) .
963                      "\"\n" .
964                   "  android:summary=\"" .
965                        $localize0->("${saver_underscore}_saver_desc",
966                                     $daydream_desc) . "\"\n" .
967                   "  android:name=\".gen.Wallpaper\$$saver_class\"\n" .
968                   "  android:permission=\"android.permission" .
969                        ".BIND_WALLPAPER\">\n" .
970                   "  <intent-filter>\n" .
971                   "    <action android:name=\"android.service.wallpaper" .
972                         ".WallpaperService\" />\n" .
973                   "    <category android:name=\"android.intent.category" .
974                         ".DEFAULT\" />\n" . # TODO: Is the DEFAULT category needed?
975                   "  </intent-filter>\n" .
976                   "  <meta-data android:name=\"android.service.wallpaper\"\n" .
977                   "    android:resource=\"\@xml/${saver}_wallpaper\" />\n" .
978                   "</service>\n" .
979                   "<activity android:label=\"" .
980                    $localize0->("${saver}_settings_title", $heading) . "\"\n" .
981                   "  android:name=\"$package.gen.Settings\$$saver_class\"\n" .
982                   "  android:exported=\"true\">\n" .
983                   "</activity>\n"
984                  );
985
986     my $dream = ("<dream xmlns:android=\"" .
987                    "http://schemas.android.com/apk/res/android\"\n" .
988                  "  android:settingsActivity=\"" .
989                      "$package.gen.Settings\$$saver_class\" />\n");
990     $write_files{"$xml_dir/${saver_underscore}_dream.xml"} = $dream;
991
992     my $wallpaper = ("<wallpaper xmlns:android=\"" .
993                        "http://schemas.android.com/apk/res/android\"\n" .
994                      "  android:settingsActivity=\"" .
995                      "$package.gen.Settings\$$saver_class\"\n" .
996                      "  android:thumbnail=\"\@drawable/${saver_underscore}\" />\n");
997     $write_files{"$xml_dir/${saver_underscore}_wallpaper.xml"} = $wallpaper;
998
999     $daydream_java .=
1000       ("  public static class $saver_class extends XScreenSaverDaydream {\n" .
1001        "  }\n" .
1002        "\n");
1003
1004     $wallpaper_java .=
1005       ("  public static class $saver_class extends XScreenSaverWallpaper {\n" .
1006        "  }\n" .
1007        "\n");
1008
1009     $settings_java .=
1010       ("  public static class $saver_class extends XScreenSaverSettings\n" .
1011        "    implements SharedPreferences.OnSharedPreferenceChangeListener {\n" .
1012        "  }\n" .
1013        "\n");
1014
1015     $fntable_h2 .= ",\n  " if $fntable_h2 ne '';
1016     $fntable_h3 .= ",\n  " if $fntable_h3 ne '';
1017
1018     $fntable_h2 .= "${saver}_xscreensaver_function_table";
1019     $fntable_h3 .= "{\"${saver}\", &${saver}_xscreensaver_function_table, " .
1020                      'API_' . ($gl_p ? 'GL' : 'XLIB') . '}';
1021   }
1022
1023   $arrays =~ s/^/  /gm;
1024   $arrays = ($xml_header .
1025              "<resources xmlns:xliff=\"" .
1026              "urn:oasis:names:tc:xliff:document:1.2\">\n" .
1027              $arrays .
1028              "</resources>\n");
1029
1030   $strings =~ s/^/  /gm;
1031   $strings = ($xml_header .
1032               "<resources>\n" .
1033               $strings .
1034               "</resources>\n");
1035
1036   $manifest .= "<activity android:name=\"$package.XScreenSaverSettings\" />\n";
1037
1038   $manifest .= ("<activity android:name=\"" .
1039                 "org.jwz.xscreensaver.XScreenSaverActivity\"\n" .
1040                 "  android:theme=\"\@android:style/Theme.Holo\"\n" .
1041                 "  android:label=\"\@string/app_name\">\n" .
1042                 "  <intent-filter>\n" .
1043                 "    <action android:name=\"android.intent.action" .
1044                 ".MAIN\" />\n" .
1045                 "    <category android:name=\"android.intent.category" .
1046                 ".LAUNCHER\" />\n" .
1047                 "  </intent-filter>\n" .
1048                 "  <intent-filter>\n" .
1049                 "    <action android:name=\"android.intent.action" .
1050                 ".VIEW\" />\n" .
1051                 "    <category android:name=\"android.intent.category" .
1052                 ".DEFAULT\" />\n" .
1053                 "    <category android:name=\"android.intent.category" .
1054                 ".BROWSABLE\" />\n" .
1055                 "  </intent-filter>\n" .
1056                 "</activity>\n");
1057
1058   # Android wants this to be an int
1059   my $versb = $vers;
1060   $versb =~ s/^(\d+)\.(\d+).*$/{ $1 * 10000 + $2 * 100 }/sex;
1061   $versb++ if ($versb == 53500); # Herp derp
1062
1063   $manifest =~ s/^/   /gm;
1064   $manifest = ($xml_header .
1065                "<manifest xmlns:android=\"" .
1066                "http://schemas.android.com/apk/res/android\"\n" .
1067                "  package=\"$package\"\n" .
1068                "  android:versionCode=\"$versb\"\n" .
1069                "  android:versionName=\"$vers\">\n" .
1070
1071                "  <uses-sdk android:minSdkVersion=\"14\"" .
1072                " android:targetSdkVersion=\"19\" />\n" .
1073
1074                "  <uses-feature android:glEsVersion=\"0x00010001\"\n" .
1075                "    android:required=\"true\" />\n" .
1076
1077                "  <uses-permission android:name=\"" .
1078                    "android.permission.INTERNET\" />\n" .
1079                "  <uses-permission android:name=\"" .
1080                    "android.permission.READ_EXTERNAL_STORAGE\" />\n" .
1081
1082                "  <application android:icon=\"\@drawable/thumbnail\"\n" .
1083                "    android:label=\"\@string/app_name\"\n" .
1084                "    android:name=\".XScreenSaverApp\">\n" .
1085                $manifest .
1086                "  </application>\n" .
1087                "</manifest>\n");
1088
1089   $daydream_java = ("package org.jwz.xscreensaver.gen;\n" .
1090                     "\n" .
1091                     "import org.jwz.xscreensaver.XScreenSaverDaydream;\n" .
1092                     "import org.jwz.xscreensaver.jwxyz;\n" .
1093                     "\n" .
1094                     "public class Daydream {\n" .
1095                     $daydream_java .
1096                     "}\n");
1097
1098   $wallpaper_java = ("package org.jwz.xscreensaver.gen;\n" .
1099                      "\n" .
1100                      "import org.jwz.xscreensaver.XScreenSaverWallpaper;\n" .
1101                      "import org.jwz.xscreensaver.jwxyz;\n" .
1102                      "\n" .
1103                      "public class Wallpaper {\n" .
1104                      $wallpaper_java .
1105                      "}\n");
1106
1107   $settings_java = ("package org.jwz.xscreensaver.gen;\n" .
1108                     "\n" .
1109                     "import android.content.SharedPreferences;\n" .
1110                     "import org.jwz.xscreensaver.XScreenSaverSettings;\n" .
1111                     "\n" .
1112                     "public class Settings {\n" .
1113                     $settings_java .
1114                     "}\n");
1115
1116   $write_files{"$project_dir/AndroidManifest.xml"}     = $manifest;
1117   $write_files{"$values_dir/settings.xml"} = $arrays;
1118   $write_files{"$values_dir/strings.xml"}  = $strings;
1119   $write_files{"$java_dir/Daydream.java"}  = $daydream_java;
1120   $write_files{"$java_dir/Wallpaper.java"} = $wallpaper_java;
1121   $write_files{"$java_dir/Settings.java"}  = $settings_java;
1122
1123   my $fntable_h = ("extern struct xscreensaver_function_table\n" .
1124                    "  " . $fntable_h2 . ";\n" .
1125                    "\n" .
1126                    "static const struct function_table_entry" .
1127                    " function_table[] = {\n" .
1128                    "  " . $fntable_h3 . "\n" .
1129                    "};\n");
1130   $write_files{"$gen_dir/function-table.h"} = $fntable_h;
1131
1132
1133   $write_files{"$values_dir/attrs.xml"} =
1134     # This file doesn't actually have any substitutions in it, so it could
1135     # just be static, somewhere...
1136     # SliderPreference.java refers to this via "R.styleable.SliderPreference".
1137     ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
1138      "<resources>\n" .
1139      "  <declare-styleable name=\"SliderPreference\">\n" .
1140      "    <attr name=\"android:summary\" />\n" .
1141      "  </declare-styleable>\n" .
1142      "</resources>\n");
1143
1144
1145   foreach my $file (sort keys %write_files) {
1146     my ($dir) = ($file =~ m@^(.*)/[^/]*$@s);
1147     system ("mkdir", "-p", $dir) if (! -d $dir && !$debug_p);
1148     my $body = $write_files{$file};
1149     $body = "// Generated by $progname\n$body"
1150       if ($file =~ m/\.(java|[chm])$/s);
1151     write_file_if_changed ($file, $body);
1152   }
1153
1154   # Unlink any .xml files from a previous run that shouldn't be there:
1155   # if a hack is removed from $ANDROID_HACKS in android/Makefile but
1156   # the old XML files remain behind, the build blows up.
1157   #
1158   foreach my $dd ($xml_dir, $gen_dir, $java_dir) {
1159     opendir (my $dirp, $dd) || error ("$dd: $!");
1160     my @files = readdir ($dirp);
1161     closedir $dirp;
1162     foreach my $f (sort @files) {
1163       next if ($f eq '.' || $f eq '..');
1164       $f = "$dd/$f";
1165       next if (defined ($write_files{$f}));
1166       if ($f =~ m/_(settings|wallpaper|dream)\.xml$/s ||
1167           $f =~ m/(Settings|Daydream)\.java$/s) {
1168         print STDERR "$progname: rm $f\n";
1169         unlink ($f) unless ($debug_p);
1170       } else {
1171         print STDERR "$progname: warning: unrecognised file: $f\n";
1172       }
1173     }
1174   }
1175 }
1176
1177
1178 sub error($) {
1179   my ($err) = @_;
1180   print STDERR "$progname: $err\n";
1181   exit 1;
1182 }
1183
1184 sub usage() {
1185   print STDERR "usage: $progname [--verbose] [--debug]" .
1186     " [--build-android] files ...\n";
1187   exit 1;
1188 }
1189
1190 sub main() {
1191   my $android_p = 0;
1192   my @files = ();
1193   while ($#ARGV >= 0) {
1194     $_ = shift @ARGV;
1195     if (m/^--?verbose$/) { $verbose++; }
1196     elsif (m/^-v+$/) { $verbose += length($_)-1; }
1197     elsif (m/^--?debug$/s) { $debug_p++; }
1198     elsif (m/^--?build-android$/s) { $android_p++; }
1199     elsif (m/^-./) { usage; }
1200     else { push @files, $_; }
1201 #    else { usage; }
1202   }
1203
1204   usage unless ($#files >= 0);
1205   my $failures = 0;
1206   foreach my $file (@files) {
1207     $failures += check_config ($file);
1208   }
1209
1210   build_android (@files) if ($android_p);
1211
1212   exit ($failures);
1213 }
1214
1215 main();