1#!/usr/bin/perl -w 2# Copyright © 2008-2018 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 19require 5; 20use diagnostics; 21use strict; 22 23my $progname = $0; $progname =~ s@.*/@@g; 24my ($version) = ('$Revision: 1.28 $' =~ m/\s(\d[.\d]+)\s/s); 25 26my $verbose = 0; 27my $debug_p = 0; 28 29 30my $text_default_opts = ''; 31foreach (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} 35my $image_default_opts = ''; 36foreach (qw(choose-random-images grab-desktop-images)) { 37 my $s = $_; $s =~ s/-(.)/\U$1/g; 38 $image_default_opts .= "{\"-$_\", \".$s\", XrmoptionSepArg, 0},\n"; 39} 40my $xlockmore_default_opts = ''; 41foreach (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 49my $thread_default_opts = 50 "{\"-threads\", \".useThreads\", XrmoptionNoArg, \"True\"},\n" . 51 "{\"-no-threads\", \".useThreads\", XrmoptionNoArg, \"False\"},\n"; 52 53my $analogtv_default_opts = ''; 54foreach (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# 66sub 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 207my %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# 215sub 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 443sub 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# 499sub 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 517sub 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# 543sub 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# 576sub 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# 589sub 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# 616sub 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 695sub build_android(@) { 696 my (@savers) = @_; 697 698 my $package = "org.jwz.xscreensaver"; 699 my $project_dir = "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+\.[0-9a-z]+) @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 ][/gs; # "Apple ][" 745 $saver_title =~ s/(m)oe(bius)/$1ö$2/gsi; # ö 746 $saver_title =~ s/(moir)e/$1é/gsi; # é 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/&/&/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 $package.Daydream {\n" . 1001 " }\n" . 1002 "\n"); 1003 1004 $wallpaper_java .= 1005 (" public static class $saver_class extends $package.Wallpaper {\n" . 1006 " }\n" . 1007 "\n"); 1008 1009 $settings_java .= 1010 (" public static class $saver_class extends $package.Settings\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 } 1021 1022 $arrays =~ s/^/ /gm; 1023 $arrays = ($xml_header . 1024 "<resources xmlns:xliff=\"" . 1025 "urn:oasis:names:tc:xliff:document:1.2\">\n" . 1026 $arrays . 1027 "</resources>\n"); 1028 1029 $strings =~ s/^/ /gm; 1030 $strings = ($xml_header . 1031 "<resources>\n" . 1032 $strings . 1033 "</resources>\n"); 1034 1035 $manifest .= "<activity android:name=\"$package.Settings\" />\n"; 1036 1037 $manifest .= ("<activity android:name=\"" . 1038 "$package.Activity\"\n" . 1039 " android:theme=\"\@android:style/Theme.Holo\"\n" . 1040 " android:label=\"\@string/app_name\">\n" . 1041 " <intent-filter>\n" . 1042 " <action android:name=\"android.intent.action" . 1043 ".MAIN\" />\n" . 1044 " <category android:name=\"android.intent.category" . 1045 ".LAUNCHER\" />\n" . 1046 " </intent-filter>\n" . 1047 " <intent-filter>\n" . 1048 " <action android:name=\"android.intent.action" . 1049 ".VIEW\" />\n" . 1050 " <category android:name=\"android.intent.category" . 1051 ".DEFAULT\" />\n" . 1052 " <category android:name=\"android.intent.category" . 1053 ".BROWSABLE\" />\n" . 1054 " </intent-filter>\n" . 1055 "</activity>\n"); 1056 1057 1058 $manifest .= ("<activity android:name=\"" . 1059 "$package.TVActivity\"\n" . 1060 " android:theme=\"\@android:style/Theme.Holo\"\n" . 1061 " android:label=\"\@string/app_name\">\n" . 1062 " <intent-filter>\n" . 1063 " <action android:name=\"android.intent.action" . 1064 ".MAIN\" />\n" . 1065 " <category android:name=\"android.intent.category" . 1066 ".LEANBACK_LAUNCHER\" />\n" . 1067 " </intent-filter>\n" . 1068 " <intent-filter>\n" . 1069 " <action android:name=\"android.intent.action" . 1070 ".VIEW\" />\n" . 1071 " <category android:name=\"android.intent.category" . 1072 ".DEFAULT\" />\n" . 1073 " <category android:name=\"android.intent.category" . 1074 ".BROWSABLE\" />\n" . 1075 " </intent-filter>\n" . 1076 "</activity>\n"); 1077 1078 1079 1080 1081 # Android wants this to be an int 1082 my $versb = $vers; 1083 $versb =~ s/^(\d+)\.(\d+).*$/{ $1 * 10000 + $2 * 100 }/sex; 1084 $versb++ if ($versb == 53500); # Herp derp 1085 1086 $manifest =~ s/^/ /gm; 1087 $manifest = ($xml_header . 1088 "<manifest xmlns:android=\"" . 1089 "http://schemas.android.com/apk/res/android\"\n" . 1090 " package=\"$package\"\n" . 1091 " android:versionCode=\"$versb\"\n" . 1092 " android:versionName=\"$vers\">\n" . 1093 1094 " <uses-sdk android:minSdkVersion=\"16\"" . 1095 " android:targetSdkVersion=\"19\" />\n" . 1096 1097 " <uses-feature android:glEsVersion=\"0x00010001\"\n" . 1098 " android:required=\"true\" />\n" . 1099 1100 " <uses-feature android:name=\"android.software.leanback\"\n" . 1101 " android:required=\"false\" />\n" . 1102 1103 " <uses-feature" . 1104 " android:name=\"android.hardware.touchscreen\"\n" . 1105 " android:required=\"false\" />\n" . 1106 1107 " <uses-permission android:name=\"" . 1108 "android.permission.INTERNET\" />\n" . 1109 " <uses-permission android:name=\"" . 1110 "android.permission.READ_EXTERNAL_STORAGE\" />\n" . 1111 1112 " <application android:icon=\"\@drawable/thumbnail\"\n" . 1113 " android:banner=\"\@drawable/thumbnail\"\n" . 1114 " android:label=\"\@string/app_name\"\n" . 1115 " android:name=\".App\">\n" . 1116 $manifest . 1117 " </application>\n" . 1118 "</manifest>\n"); 1119 1120 $daydream_java = ("package $package.gen;\n" . 1121 "\n" . 1122 "import $package.jwxyz;\n" . 1123 "\n" . 1124 "public class Daydream {\n" . 1125 $daydream_java . 1126 "}\n"); 1127 1128 $wallpaper_java = ("package $package.gen;\n" . 1129 "\n" . 1130 "import $package.jwxyz;\n" . 1131 "\n" . 1132 "public class Wallpaper {\n" . 1133 $wallpaper_java . 1134 "}\n"); 1135 1136 $settings_java = ("package $package.gen;\n" . 1137 "\n" . 1138 "import android.content.SharedPreferences;\n" . 1139 "\n" . 1140 "public class Settings {\n" . 1141 $settings_java . 1142 "}\n"); 1143 1144 $write_files{"$project_dir/AndroidManifest.xml"} = $manifest; 1145 $write_files{"$values_dir/settings.xml"} = $arrays; 1146 $write_files{"$values_dir/strings.xml"} = $strings; 1147 $write_files{"$java_dir/Daydream.java"} = $daydream_java; 1148 $write_files{"$java_dir/Wallpaper.java"} = $wallpaper_java; 1149 $write_files{"$java_dir/Settings.java"} = $settings_java; 1150 1151 my $fntable_h = ("extern struct xscreensaver_function_table\n" . 1152 " " . $fntable_h2 . ";\n" . 1153 "\n" . 1154 "static const struct function_table_entry" . 1155 " function_table[] = {\n" . 1156 " " . $fntable_h3 . "\n" . 1157 "};\n"); 1158 $write_files{"$gen_dir/function-table.h"} = $fntable_h; 1159 1160 1161 $write_files{"$values_dir/attrs.xml"} = 1162 # This file doesn't actually have any substitutions in it, so it could 1163 # just be static, somewhere... 1164 # SliderPreference.java refers to this via "R.styleable.SliderPreference". 1165 ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . 1166 "<resources>\n" . 1167 " <declare-styleable name=\"SliderPreference\">\n" . 1168 " <attr name=\"android:summary\" />\n" . 1169 " </declare-styleable>\n" . 1170 "</resources>\n"); 1171 1172 1173 foreach my $file (sort keys %write_files) { 1174 my ($dir) = ($file =~ m@^(.*)/[^/]*$@s); 1175 system ("mkdir", "-p", $dir) if (! -d $dir && !$debug_p); 1176 my $body = $write_files{$file}; 1177 $body = "// Generated by $progname\n$body" 1178 if ($file =~ m/\.(java|[chm])$/s); 1179 write_file_if_changed ($file, $body); 1180 } 1181 1182 # Unlink any .xml files from a previous run that shouldn't be there: 1183 # if a hack is removed from $ANDROID_HACKS in android/Makefile but 1184 # the old XML files remain behind, the build blows up. 1185 # 1186 foreach my $dd ($xml_dir, $gen_dir, $java_dir) { 1187 opendir (my $dirp, $dd) || error ("$dd: $!"); 1188 my @files = readdir ($dirp); 1189 closedir $dirp; 1190 foreach my $f (sort @files) { 1191 next if ($f eq '.' || $f eq '..'); 1192 $f = "$dd/$f"; 1193 next if (defined ($write_files{$f})); 1194 if ($f =~ m/_(settings|wallpaper|dream)\.xml$/s || 1195 $f =~ m/(Settings|Daydream)\.java$/s) { 1196 print STDERR "$progname: rm $f\n"; 1197 unlink ($f) unless ($debug_p); 1198 } else { 1199 print STDERR "$progname: warning: unrecognised file: $f\n"; 1200 } 1201 } 1202 } 1203} 1204 1205 1206sub error($) { 1207 my ($err) = @_; 1208 print STDERR "$progname: $err\n"; 1209 exit 1; 1210} 1211 1212sub usage() { 1213 print STDERR "usage: $progname [--verbose] [--debug]" . 1214 " [--build-android] files ...\n"; 1215 exit 1; 1216} 1217 1218sub main() { 1219 my $android_p = 0; 1220 my @files = (); 1221 while ($#ARGV >= 0) { 1222 $_ = shift @ARGV; 1223 if (m/^--?verbose$/) { $verbose++; } 1224 elsif (m/^-v+$/) { $verbose += length($_)-1; } 1225 elsif (m/^--?debug$/s) { $debug_p++; } 1226 elsif (m/^--?build-android$/s) { $android_p++; } 1227 elsif (m/^-./) { usage; } 1228 else { push @files, $_; } 1229# else { usage; } 1230 } 1231 1232 usage unless ($#files >= 0); 1233 my $failures = 0; 1234 foreach my $file (@files) { 1235 $failures += check_config ($file); 1236 } 1237 1238 build_android (@files) if ($android_p); 1239 1240 exit ($failures); 1241} 1242 1243main(); 1244