From 99d94b27ea31490fae28ede3fbd40a7cb17003aa Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Mon, 14 Jun 2010 11:16:42 -0400 Subject: [PATCH] http://www.jwz.org/xscreensaver/xscreensaver-5.11.tar.gz -rw-r--r-- 1 zblaxell zblaxell 5585001 Apr 13 16:37 xscreensaver-5.11.tar.gz 8cc46c4e80124fba0890fa9e37e3b4ff87c6a8cf xscreensaver-5.11.tar.gz --- INSTALL | 183 + Makefile.in | 373 + OSX/._InvertedSlider.m | Bin 0 -> 173 bytes OSX/._XScreenSaver.icns | Bin 0 -> 39555 bytes OSX/English.lproj/InfoPlist.strings | Bin 0 -> 92 bytes OSX/English.lproj/SaverTester.nib/classes.nib | 27 + OSX/English.lproj/SaverTester.nib/info.nib | 20 + .../SaverTester.nib/keyedobjects.nib | Bin 0 -> 12749 bytes OSX/InvertedSlider.h | 20 + OSX/InvertedSlider.m | 102 + OSX/Makefile | 141 + OSX/PrefsReader.h | 39 + OSX/PrefsReader.m | 300 + OSX/README | 10 + OSX/SaverTester.h | 21 + OSX/SaverTester.m | 352 + OSX/SaverTester.plist | 32 + OSX/XScreenSaver.icns | Bin 0 -> 39163 bytes OSX/XScreenSaver.plist | 30 + OSX/XScreenSaverConfigSheet.h | 38 + OSX/XScreenSaverConfigSheet.m | 1926 + OSX/XScreenSaverDMG.icns | Bin 0 -> 35120 bytes OSX/XScreenSaverGLView.h | 26 + OSX/XScreenSaverGLView.m | 219 + OSX/XScreenSaverSubclass.m | 33 + OSX/XScreenSaverView.h | 43 + OSX/XScreenSaverView.m | 715 + OSX/bindist-DS_Store | Bin 0 -> 12292 bytes OSX/bindist.rtf | 51 + OSX/jwxyz-timers.h | 29 + OSX/jwxyz-timers.m | 383 + OSX/jwxyz.h | 698 + OSX/jwxyz.m | 2842 ++ OSX/main.m | 17 + OSX/osxgrabscreen.m | 375 + OSX/update-info-plist.pl | 278 + OSX/xscreensaver_Prefix.pch | 15 + README | 1356 + README.VMS | 57 + README.hacking | 167 + aclocal.m4 | 443 + config.guess | 1516 + config.h-vms | 284 + config.h.in | 492 + config.sub | 1626 + configure | 23043 ++++++++++++ configure.in | 4302 +++ driver/.gdbinit | 27 + driver/Makefile.in | 1017 + driver/README | 6 + driver/XScreenSaver-Xm.ad | 126 + driver/XScreenSaver.ad.in | 492 + driver/XScreenSaver_Xm_ad.h | 108 + driver/XScreenSaver_ad.h | 357 + driver/auth.h | 54 + driver/compile_axp.com | 15 + driver/compile_decc.com | 15 + driver/demo-Gtk-conf.c | 1960 + driver/demo-Gtk-conf.h | 31 + driver/demo-Gtk-stubs.h | 90 + driver/demo-Gtk-support.c | 219 + driver/demo-Gtk-support.h | 61 + driver/demo-Gtk-widgets.c | 1764 + driver/demo-Gtk-widgets.h | 6 + driver/demo-Gtk.c | 5261 +++ driver/demo-Xm-widgets.c | 907 + driver/demo-Xm.c | 1875 + driver/dpms.c | 256 + driver/exec.c | 299 + driver/exec.h | 21 + driver/link_axp.com | 15 + driver/link_decc.com | 15 + driver/lock.c | 2118 ++ driver/mlstring.c | 229 + driver/mlstring.h | 57 + driver/passwd-helper.c | 162 + driver/passwd-kerberos.c | 251 + driver/passwd-pam.c | 493 + driver/passwd-pwent.c | 312 + driver/passwd.c | 334 + driver/pdf2jpeg.m | 152 + driver/pdf2jpeg.man | 43 + driver/prefs.c | 1638 + driver/prefs.h | 35 + driver/remote.c | 580 + driver/remote.h | 24 + driver/screens.c | 1077 + driver/screensaver-properties.desktop.in | 8 + driver/setuid.c | 361 + driver/splash.c | 860 + driver/stderr.c | 548 + driver/subprocs.c | 1344 + driver/test-apm.c | 101 + driver/test-fade.c | 123 + driver/test-grab.c | 89 + driver/test-mlstring.c | 312 + driver/test-passwd.c | 291 + driver/test-randr.c | 339 + driver/test-screens.c | 208 + driver/test-uid.c | 209 + driver/test-vp.c | 213 + driver/test-xdpms.c | 160 + driver/test-xinerama.c | 112 + driver/timers.c | 1540 + driver/types.h | 407 + driver/vms-getpwnam.c | 129 + driver/vms-hpwd.c | 75 + driver/vms-pwd.h | 48 + driver/vms-validate.c | 75 + driver/vms_axp.opt | 5 + driver/vms_axp_12.opt | 5 + driver/vms_decc.opt | 5 + driver/vms_decc_12.opt | 5 + driver/windows.c | 2001 + driver/xdpyinfo.c | 1085 + driver/xscreensaver-command.c | 439 + driver/xscreensaver-command.man | 263 + driver/xscreensaver-demo.glade2 | 2874 ++ driver/xscreensaver-demo.glade2p | 19 + driver/xscreensaver-demo.man | 373 + driver/xscreensaver-getimage-desktop | 207 + driver/xscreensaver-getimage-desktop.man | 55 + driver/xscreensaver-getimage-file | 552 + driver/xscreensaver-getimage-file.man | 49 + driver/xscreensaver-getimage-video | 175 + driver/xscreensaver-getimage-video.man | 51 + driver/xscreensaver-getimage.c | 1913 + driver/xscreensaver-getimage.man | 70 + driver/xscreensaver-text | 906 + driver/xscreensaver-text.man | 85 + driver/xscreensaver.c | 2287 ++ driver/xscreensaver.h | 202 + driver/xscreensaver.man | 950 + driver/xscreensaver.pam | 16 + driver/xset.c | 233 + hacks/.gdbinit | 12 + hacks/Makefile.in | 2800 ++ hacks/README | 6 + hacks/abstractile.c | 1592 + hacks/abstractile.man | 52 + hacks/analogtv.c | 2203 ++ hacks/analogtv.h | 300 + hacks/anemone.c | 437 + hacks/anemone.man | 74 + hacks/anemotaxis.c | 752 + hacks/anemotaxis.man | 78 + hacks/ant.c | 1371 + hacks/ant.man | 96 + hacks/apollonian.c | 836 + hacks/apollonian.man | 70 + hacks/apple2-main.c | 2012 + hacks/apple2.c | 859 + hacks/apple2.h | 121 + hacks/apple2.man | 206 + hacks/asm6502.c | 2292 ++ hacks/asm6502.h | 181 + hacks/attraction.c | 1049 + hacks/attraction.man | 227 + hacks/automata.h | 64 + hacks/barcode.c | 1984 + hacks/barcode.man | 61 + hacks/blaster.c | 1187 + hacks/blaster.man | 65 + hacks/blitspin.c | 430 + hacks/blitspin.man | 96 + hacks/bouboule.c | 847 + hacks/bouboule.man | 80 + hacks/boxfit.c | 557 + hacks/boxfit.man | 102 + hacks/braid.c | 467 + hacks/braid.man | 69 + hacks/bsod.c | 4074 +++ hacks/bsod.man | 140 + hacks/bubbles-default.c | 151 + hacks/bubbles.c | 1433 + hacks/bubbles.h | 224 + hacks/bubbles.man | 140 + hacks/bumps.c | 732 + hacks/bumps.man | 80 + hacks/ccurve.c | 847 + hacks/ccurve.man | 60 + hacks/celtic.c | 1118 + hacks/celtic.man | 64 + hacks/check-configs.pl | 340 + hacks/cloudlife.c | 416 + hacks/cloudlife.man | 87 + hacks/compass.c | 973 + hacks/compass.man | 57 + hacks/compile_axp.com | 148 + hacks/compile_decc.com | 148 + hacks/config/._klein.xml | Bin 0 -> 225 bytes hacks/config/README | 249 + hacks/config/abstractile.xml | 32 + hacks/config/anemone.xml | 50 + hacks/config/anemotaxis.xml | 36 + hacks/config/ant.xml | 63 + hacks/config/antinspect.xml | 21 + hacks/config/antmaze.xml | 19 + hacks/config/antspotlight.xml | 22 + hacks/config/apollonian.xml | 38 + hacks/config/apple2.xml | 41 + hacks/config/atlantis.xml | 46 + hacks/config/attraction.xml | 84 + hacks/config/atunnel.xml | 22 + hacks/config/barcode.xml | 32 + hacks/config/blaster.xml | 59 + hacks/config/blinkbox.xml | 31 + hacks/config/blitspin.xml | 38 + hacks/config/blocktube.xml | 30 + hacks/config/boing.xml | 47 + hacks/config/bouboule.xml | 30 + hacks/config/bouncingcow.xml | 28 + hacks/config/boxed.xml | 54 + hacks/config/boxfit.xml | 56 + hacks/config/braid.xml | 35 + hacks/config/bsod.xml | 60 + hacks/config/bubble3d.xml | 21 + hacks/config/bubbles.xml | 38 + hacks/config/bumps.xml | 33 + hacks/config/cage.xml | 23 + hacks/config/carousel.xml | 53 + hacks/config/ccurve.xml | 28 + hacks/config/celtic.xml | 27 + hacks/config/circuit.xml | 33 + hacks/config/cloudlife.xml | 37 + hacks/config/compass.xml | 20 + hacks/config/coral.xml | 32 + hacks/config/crackberg.xml | 48 + hacks/config/critical.xml | 27 + hacks/config/crystal.xml | 44 + hacks/config/cube21.xml | 73 + hacks/config/cubenetic.xml | 57 + hacks/config/cubestorm.xml | 38 + hacks/config/cubicgrid.xml | 28 + hacks/config/cwaves.xml | 27 + hacks/config/cynosure.xml | 28 + hacks/config/dangerball.xml | 33 + hacks/config/decayscreen.xml | 46 + hacks/config/deco.xml | 42 + hacks/config/deluxe.xml | 35 + hacks/config/demon.xml | 37 + hacks/config/discrete.xml | 29 + hacks/config/distort.xml | 50 + hacks/config/dnalogo.xml | 26 + hacks/config/drift.xml | 27 + hacks/config/endgame.xml | 25 + hacks/config/engine.xml | 40 + hacks/config/epicycle.xml | 52 + hacks/config/eruption.xml | 49 + hacks/config/euler2d.xml | 44 + hacks/config/extrusion.xml | 41 + hacks/config/fadeplot.xml | 31 + hacks/config/fiberlamp.xml | 27 + hacks/config/fireworkx.xml | 28 + hacks/config/flag.xml | 37 + hacks/config/flame.xml | 35 + hacks/config/flipflop.xml | 48 + hacks/config/flipscreen3d.xml | 24 + hacks/config/fliptext.xml | 47 + hacks/config/flow.xml | 56 + hacks/config/fluidballs.xml | 49 + hacks/config/flurry.xml | 28 + hacks/config/flyingtoasters.xml | 38 + hacks/config/fontglide.xml | 44 + hacks/config/forest.xml | 26 + hacks/config/fuzzyflakes.xml | 62 + hacks/config/galaxy.xml | 33 + hacks/config/gears.xml | 36 + hacks/config/gflux.xml | 57 + hacks/config/glblur.xml | 43 + hacks/config/glcells.xml | 60 + hacks/config/gleidescope.xml | 38 + hacks/config/glforestfire.xml | 42 + hacks/config/glhanoi.xml | 32 + hacks/config/glknots.xml | 59 + hacks/config/glmatrix.xml | 48 + hacks/config/glplanet.xml | 38 + hacks/config/glschool.xml | 41 + hacks/config/glslideshow.xml | 56 + hacks/config/glsnake.xml | 53 + hacks/config/gltext.xml | 47 + hacks/config/goop.xml | 61 + hacks/config/grav.xml | 32 + hacks/config/greynetic.xml | 21 + hacks/config/halftone.xml | 55 + hacks/config/halo.xml | 38 + hacks/config/helix.xml | 23 + hacks/config/hopalong.xml | 55 + hacks/config/hyperball.xml | 61 + hacks/config/hypercube.xml | 70 + hacks/config/hypertorus.xml | 114 + hacks/config/hypnowheel.xml | 50 + hacks/config/ifs.xml | 42 + hacks/config/imsmap.xml | 42 + hacks/config/interaggregate.xml | 27 + hacks/config/interference.xml | 53 + hacks/config/intermomentary.xml | 31 + hacks/config/jigglypuff.xml | 88 + hacks/config/jigsaw.xml | 48 + hacks/config/juggle.xml | 58 + hacks/config/juggler3d.xml | 50 + hacks/config/julia.xml | 35 + hacks/config/kaleidescope.xml | 38 + hacks/config/klein.xml | 125 + hacks/config/kumppa.xml | 28 + hacks/config/lament.xml | 26 + hacks/config/laser.xml | 34 + hacks/config/lavalite.xml | 85 + hacks/config/lcdscrub.xml | 51 + hacks/config/lightning.xml | 26 + hacks/config/lisa.xml | 40 + hacks/config/lissie.xml | 39 + hacks/config/lmorph.xml | 41 + hacks/config/lockward.xml | 63 + hacks/config/loop.xml | 32 + hacks/config/m6502.xml | 28 + hacks/config/maze.xml | 55 + hacks/config/memscroller.xml | 30 + hacks/config/menger.xml | 48 + hacks/config/metaballs.xml | 51 + hacks/config/mirrorblob.xml | 69 + hacks/config/mismunch.xml | 40 + hacks/config/moebius.xml | 25 + hacks/config/moebiusgears.xml | 43 + hacks/config/moire.xml | 34 + hacks/config/moire2.xml | 30 + hacks/config/molecule.xml | 57 + hacks/config/morph3d.xml | 30 + hacks/config/mountain.xml | 27 + hacks/config/munch.xml | 58 + hacks/config/nerverot.xml | 59 + hacks/config/noof.xml | 19 + hacks/config/noseguy.xml | 15 + hacks/config/pacman.xml | 24 + hacks/config/pedal.xml | 28 + hacks/config/penetrate.xml | 30 + hacks/config/penrose.xml | 51 + hacks/config/petri.xml | 83 + hacks/config/phosphor.xml | 31 + hacks/config/photopile.xml | 62 + hacks/config/piecewise.xml | 42 + hacks/config/pinion.xml | 45 + hacks/config/pipes.xml | 41 + hacks/config/polyhedra.xml | 196 + hacks/config/polyominoes.xml | 32 + hacks/config/polytopes.xml | 107 + hacks/config/pong.xml | 29 + hacks/config/popsquares.xml | 52 + hacks/config/providence.xml | 25 + hacks/config/pulsar.xml | 39 + hacks/config/pyro.xml | 33 + hacks/config/qix.xml | 70 + hacks/config/queens.xml | 26 + hacks/config/rd-bomb.xml | 63 + hacks/config/rdbomb.xml | 63 + hacks/config/ripples.xml | 51 + hacks/config/rocks.xml | 42 + hacks/config/rorschach.xml | 34 + hacks/config/rotor.xml | 36 + hacks/config/rotzoomer.xml | 37 + hacks/config/rubik.xml | 35 + hacks/config/rubikblocks.xml | 63 + hacks/config/sballs.xml | 33 + hacks/config/shadebobs.xml | 35 + hacks/config/sierpinski.xml | 34 + hacks/config/sierpinski3d.xml | 31 + hacks/config/skytentacles.xml | 67 + hacks/config/slidescreen.xml | 51 + hacks/config/slip.xml | 43 + hacks/config/sonar.xml | 76 + hacks/config/speedmine.xml | 58 + hacks/config/sphere.xml | 26 + hacks/config/spheremonics.xml | 56 + hacks/config/spiral.xml | 34 + hacks/config/spotlight.xml | 30 + hacks/config/sproingies.xml | 32 + hacks/config/squiral.xml | 47 + hacks/config/stairs.xml | 21 + hacks/config/starfish.xml | 40 + hacks/config/starwars.xml | 59 + hacks/config/stonerview.xml | 23 + hacks/config/strange.xml | 30 + hacks/config/substrate.xml | 41 + hacks/config/superquadrics.xml | 32 + hacks/config/surfaces.xml | 67 + hacks/config/swirl.xml | 29 + hacks/config/t3d.xml | 49 + hacks/config/tangram.xml | 41 + hacks/config/thornbird.xml | 33 + hacks/config/timetunnel.xml | 39 + hacks/config/topblock.xml | 60 + hacks/config/triangle.xml | 24 + hacks/config/truchet.xml | 37 + hacks/config/twang.xml | 57 + hacks/config/vermiculate.xml | 18 + hacks/config/vidwhacker.xml | 26 + hacks/config/vines.xml | 26 + hacks/config/voronoi.xml | 58 + hacks/config/wander.xml | 45 + hacks/config/webcollage.xml | 49 + hacks/config/whirlwindwarp.xml | 24 + hacks/config/whirlygig.xml | 86 + hacks/config/worm.xml | 35 + hacks/config/wormhole.xml | 27 + hacks/config/xanalogtv.xml | 21 + hacks/config/xflame.xml | 31 + hacks/config/xjack.xml | 21 + hacks/config/xlyap.xml | 48 + hacks/config/xmatrix.xml | 62 + hacks/config/xrayswarm.xml | 20 + hacks/config/xspirograph.xml | 28 + hacks/config/xss.dtd | 105 + hacks/config/xss.xsd | 375 + hacks/config/zoom.xml | 50 + hacks/coral.c | 301 + hacks/coral.man | 64 + hacks/critical.c | 452 + hacks/critical.man | 94 + hacks/crystal.c | 1273 + hacks/crystal.man | 81 + hacks/cwaves.c | 208 + hacks/cwaves.man | 76 + hacks/cynosure.c | 432 + hacks/cynosure.man | 64 + hacks/decayscreen.c | 362 + hacks/decayscreen.man | 92 + hacks/deco.c | 333 + hacks/deco.man | 105 + hacks/deluxe.c | 453 + hacks/deluxe.man | 72 + hacks/demon.c | 978 + hacks/demon.man | 69 + hacks/discrete.c | 453 + hacks/discrete.man | 61 + hacks/distort.c | 856 + hacks/distort.man | 137 + hacks/drift.c | 695 + hacks/drift.man | 79 + hacks/epicycle.c | 789 + hacks/epicycle.man | 204 + hacks/eruption.c | 507 + hacks/eruption.man | 77 + hacks/euler2d.c | 881 + hacks/euler2d.man | 69 + hacks/euler2d.tex | 337 + hacks/fadeplot.c | 236 + hacks/fadeplot.man | 65 + hacks/fiberlamp.c | 473 + hacks/fiberlamp.man | 65 + hacks/fireworkx.c | 499 + hacks/fireworkx.man | 90 + hacks/fireworkx_mmx.S | 226 + hacks/flag.c | 568 + hacks/flag.man | 92 + hacks/flame.c | 453 + hacks/flame.man | 74 + hacks/flow.c | 1225 + hacks/flow.man | 137 + hacks/fluidballs.c | 805 + hacks/fluidballs.man | 90 + hacks/fontglide.c | 1808 + hacks/fontglide.man | 124 + hacks/forest.c | 260 + hacks/forest.man | 62 + hacks/fps.c | 242 + hacks/fps.h | 28 + hacks/fpsI.h | 36 + hacks/fuzzyflakes.c | 641 + hacks/fuzzyflakes.man | 112 + hacks/galaxy.c | 462 + hacks/galaxy.man | 87 + hacks/glx/._grab-ximage.c | Bin 0 -> 170 bytes hacks/glx/._jigsaw.c | Bin 0 -> 170 bytes hacks/glx/._rubikblocks.man | Bin 0 -> 225 bytes hacks/glx/Makefile.in | 2399 ++ hacks/glx/README | 10 + hacks/glx/antinspect.c | 696 + hacks/glx/antinspect.man | 56 + hacks/glx/antmaze.c | 1594 + hacks/glx/antmaze.man | 52 + hacks/glx/ants.h | 45 + hacks/glx/antspotlight.c | 795 + hacks/glx/antspotlight.man | 56 + hacks/glx/atlantis.c | 581 + hacks/glx/atlantis.h | 123 + hacks/glx/atlantis.man | 78 + hacks/glx/atunnel.c | 326 + hacks/glx/atunnel.man | 83 + hacks/glx/b_draw.c | 244 + hacks/glx/b_lockglue.c | 243 + hacks/glx/b_sphere.c | 219 + hacks/glx/blinkbox.c | 598 + hacks/glx/blinkbox.man | 73 + hacks/glx/blocktube.c | 449 + hacks/glx/blocktube.man | 73 + hacks/glx/boing.c | 664 + hacks/glx/boing.man | 105 + hacks/glx/bouncingcow.c | 554 + hacks/glx/bouncingcow.man | 73 + hacks/glx/boxed.c | 1307 + hacks/glx/boxed.h | 4116 +++ hacks/glx/boxed.man | 56 + hacks/glx/bubble3d.c | 280 + hacks/glx/bubble3d.h | 96 + hacks/glx/bubble3d.man | 62 + hacks/glx/buildlwo.c | 96 + hacks/glx/buildlwo.h | 32 + hacks/glx/cage.c | 471 + hacks/glx/cage.man | 61 + hacks/glx/carousel.c | 902 + hacks/glx/carousel.man | 109 + hacks/glx/chessgames.h | 343 + hacks/glx/chessmodels.c | 1708 + hacks/glx/chessmodels.h | 44 + hacks/glx/circuit.c | 2279 ++ hacks/glx/circuit.man | 72 + hacks/glx/cow_face.c | 341 + hacks/glx/cow_hide.c | 13055 +++++++ hacks/glx/cow_hoofs.c | 1037 + hacks/glx/cow_horns.c | 1025 + hacks/glx/cow_tail.c | 464 + hacks/glx/cow_udder.c | 1520 + hacks/glx/crackberg.c | 1419 + hacks/glx/crackberg.man | 123 + hacks/glx/cube21.c | 909 + hacks/glx/cube21.man | 147 + hacks/glx/cubenetic.c | 609 + hacks/glx/cubenetic.man | 89 + hacks/glx/cubestorm.c | 427 + hacks/glx/cubestorm.man | 77 + hacks/glx/cubicgrid.c | 291 + hacks/glx/cubicgrid.man | 83 + hacks/glx/dangerball.c | 386 + hacks/glx/dangerball.man | 72 + hacks/glx/dnalogo.c | 1517 + hacks/glx/dolphin.c | 2061 ++ hacks/glx/dropshadow.c | 180 + hacks/glx/dropshadow.h | 40 + hacks/glx/dxf2gl.pl | 501 + hacks/glx/e_textures.h | 1478 + hacks/glx/endgame.c | 905 + hacks/glx/endgame.man | 72 + hacks/glx/engine.c | 1008 + hacks/glx/engine.man | 80 + hacks/glx/extrusion-helix2.c | 47 + hacks/glx/extrusion-helix3.c | 46 + hacks/glx/extrusion-helix4.c | 63 + hacks/glx/extrusion-joinoffset.c | 148 + hacks/glx/extrusion-screw.c | 114 + hacks/glx/extrusion-taper.c | 218 + hacks/glx/extrusion-twistoid.c | 213 + hacks/glx/extrusion.c | 557 + hacks/glx/extrusion.h | 53 + hacks/glx/extrusion.man | 71 + hacks/glx/flipflop.c | 876 + hacks/glx/flipflop.man | 95 + hacks/glx/flipscreen3d.c | 536 + hacks/glx/flipscreen3d.man | 61 + hacks/glx/fliptext.c | 1115 + hacks/glx/fliptext.man | 114 + hacks/glx/flurry-smoke.c | 1444 + hacks/glx/flurry-spark.c | 285 + hacks/glx/flurry-star.c | 106 + hacks/glx/flurry-texture.c | 224 + hacks/glx/flurry.c | 572 + hacks/glx/flurry.h | 285 + hacks/glx/flurry.man | 73 + hacks/glx/flyingtoasters.c | 860 + hacks/glx/flyingtoasters.man | 86 + hacks/glx/font-ximage.c | 255 + hacks/glx/font-ximage.h | 30 + hacks/glx/fps-gl.c | 98 + hacks/glx/gears.c | 945 + hacks/glx/gears.man | 79 + hacks/glx/gflux.c | 835 + hacks/glx/gflux.man | 111 + hacks/glx/glblur.c | 638 + hacks/glx/glblur.man | 76 + hacks/glx/glcells.c | 1317 + hacks/glx/glcells.man | 97 + hacks/glx/gleidescope.c | 1630 + hacks/glx/gleidescope.man | 77 + hacks/glx/glforestfire.c | 1143 + hacks/glx/glforestfire.man | 130 + hacks/glx/glhanoi.c | 1514 + hacks/glx/glhanoi.man | 72 + hacks/glx/glknots.c | 462 + hacks/glx/glknots.man | 81 + hacks/glx/gllist.c | 9 + hacks/glx/gllist.h | 22 + hacks/glx/glmatrix.c | 1067 + hacks/glx/glmatrix.man | 98 + hacks/glx/glplanet.c | 671 + hacks/glx/glplanet.man | 70 + hacks/glx/glschool.c | 212 + hacks/glx/glschool.h | 17 + hacks/glx/glschool.man | 126 + hacks/glx/glschool_alg.c | 364 + hacks/glx/glschool_alg.h | 126 + hacks/glx/glschool_gl.c | 281 + hacks/glx/glschool_gl.h | 37 + hacks/glx/glslideshow.c | 1231 + hacks/glx/glslideshow.man | 131 + hacks/glx/glsnake.c | 2643 ++ hacks/glx/glsnake.man | 98 + hacks/glx/gltext.c | 649 + hacks/glx/gltext.man | 123 + hacks/glx/gltrackball.c | 152 + hacks/glx/gltrackball.h | 59 + hacks/glx/glut_roman.h | 2454 ++ hacks/glx/glut_stroke.c | 56 + hacks/glx/glut_swidth.c | 72 + hacks/glx/glutstroke.h | 47 + hacks/glx/glxfonts.c | 498 + hacks/glx/glxfonts.h | 48 + hacks/glx/grab-ximage.c | 838 + hacks/glx/grab-ximage.h | 76 + hacks/glx/hypertorus.c | 1033 + hacks/glx/hypertorus.man | 188 + hacks/glx/hypnowheel.c | 298 + hacks/glx/hypnowheel.man | 80 + hacks/glx/involute.c | 967 + hacks/glx/involute.h | 77 + hacks/glx/jigglypuff.c | 1076 + hacks/glx/jigglypuff.man | 121 + hacks/glx/jigsaw.c | 1165 + hacks/glx/jigsaw.man | 90 + hacks/glx/juggler3d.c | 3068 ++ hacks/glx/juggler3d.man | 183 + hacks/glx/klein.c | 2045 ++ hacks/glx/klein.man | 299 + hacks/glx/lament.c | 2045 ++ hacks/glx/lament.man | 68 + hacks/glx/lavalite.c | 1573 + hacks/glx/lavalite.man | 160 + hacks/glx/lockward.c | 938 + hacks/glx/lockward.man | 79 + hacks/glx/marching.c | 639 + hacks/glx/marching.h | 48 + hacks/glx/menger.c | 552 + hacks/glx/menger.man | 78 + hacks/glx/mirrorblob.c | 1851 + hacks/glx/mirrorblob.man | 107 + hacks/glx/moebius.c | 755 + hacks/glx/moebius.man | 65 + hacks/glx/moebiusgears.c | 413 + hacks/glx/moebiusgears.man | 86 + hacks/glx/molecule.c | 1654 + hacks/glx/molecule.man | 160 + hacks/glx/molecules.sh | 17 + hacks/glx/morph3d.c | 834 + hacks/glx/morph3d.man | 57 + hacks/glx/noof.c | 474 + hacks/glx/noof.man | 52 + hacks/glx/normals.c | 52 + hacks/glx/normals.h | 38 + hacks/glx/photopile.c | 769 + hacks/glx/photopile.man | 113 + hacks/glx/pinion.c | 1488 + hacks/glx/pinion.man | 82 + hacks/glx/pipeobjs.c | 3262 ++ hacks/glx/pipes.c | 1081 + hacks/glx/pipes.man | 85 + hacks/glx/polyhedra-gl.c | 665 + hacks/glx/polyhedra.c | 2448 ++ hacks/glx/polyhedra.h | 52 + hacks/glx/polyhedra.man | 124 + hacks/glx/polytopes.c | 3207 ++ hacks/glx/polytopes.man | 207 + hacks/glx/providence.c | 815 + hacks/glx/providence.man | 66 + hacks/glx/pulsar.c | 517 + hacks/glx/pulsar.man | 102 + hacks/glx/queens.c | 536 + hacks/glx/queens.man | 66 + hacks/glx/rotator.c | 247 + hacks/glx/rotator.h | 60 + hacks/glx/rubik.c | 2115 ++ hacks/glx/rubik.man | 69 + hacks/glx/rubikblocks.c | 653 + hacks/glx/rubikblocks.man | 117 + hacks/glx/s1_1.c | 1733 + hacks/glx/s1_2.c | 1733 + hacks/glx/s1_3.c | 1733 + hacks/glx/s1_4.c | 1733 + hacks/glx/s1_5.c | 1733 + hacks/glx/s1_6.c | 1733 + hacks/glx/s1_b.c | 505 + hacks/glx/sballs.c | 863 + hacks/glx/sballs.man | 125 + hacks/glx/shark.c | 1395 + hacks/glx/sierpinski3d.c | 577 + hacks/glx/sierpinski3d.man | 67 + hacks/glx/skytentacles.c | 1093 + hacks/glx/skytentacles.man | 102 + hacks/glx/sonar-icmp.c | 1201 + hacks/glx/sonar-sim.c | 112 + hacks/glx/sonar.c | 1015 + hacks/glx/sonar.h | 68 + hacks/glx/sonar.man | 163 + hacks/glx/sphere.c | 108 + hacks/glx/sphere.h | 23 + hacks/glx/spheremonics.c | 914 + hacks/glx/spheremonics.man | 92 + hacks/glx/sproingies.c | 923 + hacks/glx/sproingies.h | 25 + hacks/glx/sproingies.man | 68 + hacks/glx/sproingiewrap.c | 249 + hacks/glx/stairs.c | 596 + hacks/glx/stairs.man | 56 + hacks/glx/starwars.c | 1107 + hacks/glx/starwars.man | 181 + hacks/glx/starwars.txt | 311 + hacks/glx/stonerview-move.c | 156 + hacks/glx/stonerview-move.h | 31 + hacks/glx/stonerview-osc.c | 341 + hacks/glx/stonerview-osc.h | 175 + hacks/glx/stonerview-view.c | 114 + hacks/glx/stonerview.c | 188 + hacks/glx/stonerview.h | 61 + hacks/glx/stonerview.man | 53 + hacks/glx/superquadrics.c | 797 + hacks/glx/superquadrics.man | 70 + hacks/glx/surfaces.c | 680 + hacks/glx/surfaces.man | 127 + hacks/glx/swim.c | 232 + hacks/glx/tangram.c | 1065 + hacks/glx/tangram.man | 67 + hacks/glx/tangram_shapes.c | 224 + hacks/glx/tangram_shapes.h | 15 + hacks/glx/teapot.c | 214 + hacks/glx/teapot.h | 7 + hacks/glx/texfont.c | 517 + hacks/glx/texfont.h | 37 + hacks/glx/timetunnel.c | 1255 + hacks/glx/timetunnel.man | 105 + hacks/glx/toast.c | 185 + hacks/glx/toast2.c | 209 + hacks/glx/toaster.c | 371 + hacks/glx/toaster_base.c | 125 + hacks/glx/toaster_handle.c | 59 + hacks/glx/toaster_handle2.c | 35 + hacks/glx/toaster_jet.c | 173 + hacks/glx/toaster_knob.c | 71 + hacks/glx/toaster_slots.c | 101 + hacks/glx/toaster_wing.c | 38 + hacks/glx/topblock.c | 897 + hacks/glx/topblock.h | 45 + hacks/glx/topblock.man | 170 + hacks/glx/trackball.c | 330 + hacks/glx/trackball.h | 82 + hacks/glx/tube.c | 236 + hacks/glx/tube.h | 32 + hacks/glx/tunnel_draw.c | 502 + hacks/glx/tunnel_draw.h | 11 + hacks/glx/voronoi.c | 508 + hacks/glx/voronoi.man | 88 + hacks/glx/whale.c | 1887 + hacks/glx/xlock-gl-utils.c | 211 + hacks/glx/xpm-ximage.c | 462 + hacks/glx/xpm-ximage.h | 31 + hacks/glx/xscreensaver-gl-helper.c | 74 + hacks/glx/xscreensaver-gl-helper.man | 33 + hacks/goop.c | 594 + hacks/goop.man | 84 + hacks/grav.c | 355 + hacks/grav.man | 78 + hacks/greynetic.c | 290 + hacks/greynetic.man | 56 + hacks/halftone.c | 394 + hacks/halftone.man | 83 + hacks/halo.c | 436 + hacks/halo.man | 75 + hacks/helix.c | 353 + hacks/helix.man | 62 + hacks/hopalong.c | 579 + hacks/hopalong.man | 82 + hacks/hyperball.c | 2463 ++ hacks/hyperball.man | 88 + hacks/hypercube.c | 571 + hacks/hypercube.man | 97 + hacks/ifs.c | 532 + hacks/ifs.man | 111 + hacks/images/6x10font.xbm | 190 + hacks/images/amiga.xpm | 269 + hacks/images/apple2font.xbm | 41 + hacks/images/atari.xbm | 6 + hacks/images/atm.xbm | 246 + hacks/images/blocktube.xpm | 320 + hacks/images/bob.xbm | 43 + hacks/images/bubbles/blood.pov | 24 + hacks/images/bubbles/blood1.xpm | 75 + hacks/images/bubbles/blood10.xpm | 159 + hacks/images/bubbles/blood11.xpm | 170 + hacks/images/bubbles/blood2.xpm | 101 + hacks/images/bubbles/blood3.xpm | 112 + hacks/images/bubbles/blood4.xpm | 116 + hacks/images/bubbles/blood5.xpm | 121 + hacks/images/bubbles/blood6.xpm | 128 + hacks/images/bubbles/blood7.xpm | 135 + hacks/images/bubbles/blood8.xpm | 143 + hacks/images/bubbles/blood9.xpm | 149 + hacks/images/bubbles/blue.pov | 22 + hacks/images/bubbles/blue1.xpm | 64 + hacks/images/bubbles/blue10.xpm | 157 + hacks/images/bubbles/blue11.xpm | 169 + hacks/images/bubbles/blue2.xpm | 90 + hacks/images/bubbles/blue3.xpm | 104 + hacks/images/bubbles/blue4.xpm | 116 + hacks/images/bubbles/blue5.xpm | 122 + hacks/images/bubbles/blue6.xpm | 128 + hacks/images/bubbles/blue7.xpm | 132 + hacks/images/bubbles/blue8.xpm | 142 + hacks/images/bubbles/blue9.xpm | 149 + hacks/images/bubbles/glass.pov | 27 + hacks/images/bubbles/glass1.xpm | 79 + hacks/images/bubbles/glass10.xpm | 155 + hacks/images/bubbles/glass11.xpm | 167 + hacks/images/bubbles/glass2.xpm | 95 + hacks/images/bubbles/glass3.xpm | 112 + hacks/images/bubbles/glass4.xpm | 118 + hacks/images/bubbles/glass5.xpm | 120 + hacks/images/bubbles/glass6.xpm | 128 + hacks/images/bubbles/glass7.xpm | 133 + hacks/images/bubbles/glass8.xpm | 140 + hacks/images/bubbles/glass9.xpm | 147 + hacks/images/bubbles/jade.pov | 24 + hacks/images/bubbles/jade1.xpm | 76 + hacks/images/bubbles/jade10.xpm | 158 + hacks/images/bubbles/jade11.xpm | 171 + hacks/images/bubbles/jade2.xpm | 96 + hacks/images/bubbles/jade3.xpm | 113 + hacks/images/bubbles/jade4.xpm | 116 + hacks/images/bubbles/jade5.xpm | 123 + hacks/images/bubbles/jade6.xpm | 128 + hacks/images/bubbles/jade7.xpm | 133 + hacks/images/bubbles/jade8.xpm | 143 + hacks/images/bubbles/jade9.xpm | 149 + hacks/images/chromesphere.xpm | 350 + hacks/images/earth.xpm | 303 + hacks/images/ground.xpm | 227 + hacks/images/hmac.xpm | 52 + hacks/images/jigglymap.xpm | 350 + hacks/images/lament.xpm | 791 + hacks/images/m6502/._amiga.asm | Bin 0 -> 225 bytes hacks/images/m6502/._colors.asm | Bin 0 -> 225 bytes hacks/images/m6502/._disco.asm | Bin 0 -> 225 bytes hacks/images/m6502/._softsprite.asm | Bin 0 -> 225 bytes hacks/images/m6502/._starfield2d.asm | Bin 0 -> 225 bytes hacks/images/m6502/amiga.asm | 120 + hacks/images/m6502/breakout.asm | 195 + hacks/images/m6502/byterun.asm | 100 + hacks/images/m6502/cellular-30.asm | 67 + hacks/images/m6502/cellular-600.asm | 209 + hacks/images/m6502/colors.asm | 46 + hacks/images/m6502/crunch6502.asm | 292 + hacks/images/m6502/demoscene.asm | 457 + hacks/images/m6502/disco.asm | 23 + hacks/images/m6502/dmsc.asm | 108 + hacks/images/m6502/dragon-fractal.asm | 49 + hacks/images/m6502/fullscreenlogo.asm | 107 + hacks/images/m6502/keftal.asm | 82 + hacks/images/m6502/matrix.asm | 67 + hacks/images/m6502/noise.asm | 16 + hacks/images/m6502/random-walk.asm | 82 + hacks/images/m6502/random.asm | 11 + hacks/images/m6502/random2.asm | 11 + hacks/images/m6502/rorschach.asm | 124 + hacks/images/m6502/santa.asm | 142 + hacks/images/m6502/selfmodify.asm | 12 + hacks/images/m6502/sierpinsky.asm | 131 + hacks/images/m6502/softsprite.asm | 132 + hacks/images/m6502/spacer.asm | 235 + hacks/images/m6502/starfield2d.asm | 50 + hacks/images/m6502/wave6502.asm | 164 + hacks/images/m6502/zookeeper.asm | 109 + hacks/images/mac.xbm | 14 + hacks/images/macbomb.xbm | 474 + hacks/images/matrix1.xbm | 1260 + hacks/images/matrix1.xpm | 392 + hacks/images/matrix1b.xbm | 307 + hacks/images/matrix1b.xpm | 227 + hacks/images/matrix2.xbm | 1260 + hacks/images/matrix2.xpm | 393 + hacks/images/matrix2b.xbm | 307 + hacks/images/matrix2b.xpm | 233 + hacks/images/matrix3.xpm | 692 + hacks/images/molecules/adenine.pdb | 37 + hacks/images/molecules/adrenochrome.pdb | 55 + hacks/images/molecules/bucky.pdb | 156 + hacks/images/molecules/caffeine.pdb | 54 + hacks/images/molecules/chlordecone.pdb | 49 + hacks/images/molecules/cocaine.pdb | 93 + hacks/images/molecules/codeine.pdb | 93 + hacks/images/molecules/cyclohexane.pdb | 151 + hacks/images/molecules/cytosine.pdb | 33 + hacks/images/molecules/dna.pdb | 972 + hacks/images/molecules/dodecahedrane.pdb | 87 + hacks/images/molecules/dthc.pdb | 107 + hacks/images/molecules/dynamite.pdb | 47 + hacks/images/molecules/glycol.pdb | 27 + hacks/images/molecules/guanine.pdb | 39 + hacks/images/molecules/heroin.pdb | 107 + hacks/images/molecules/hexahelicene.pdb | 90 + hacks/images/molecules/ibuprofen.pdb | 72 + hacks/images/molecules/lsd.pdb | 104 + hacks/images/molecules/menthol.pdb | 69 + hacks/images/molecules/mescaline.pdb | 71 + hacks/images/molecules/methamphetamine.pdb | 88 + hacks/images/molecules/morphine.pdb | 87 + hacks/images/molecules/nicotine.pdb | 59 + hacks/images/molecules/novocaine.pdb | 81 + hacks/images/molecules/olestra.pdb | 913 + hacks/images/molecules/penicillin.pdb | 89 + hacks/images/molecules/sarin.pdb | 43 + hacks/images/molecules/strychnine.pdb | 101 + hacks/images/molecules/sucrose.pdb | 97 + hacks/images/molecules/thalidomide.pdb | 65 + hacks/images/molecules/thymine.pdb | 37 + hacks/images/molecules/viagra.pdb | 133 + hacks/images/molecules/vitaminb6.pdb | 56 + hacks/images/molecules/vitaminc.pdb | 47 + hacks/images/molecules/vx.pdb | 92 + hacks/images/noseguy/nose-f1.xbm | 46 + hacks/images/noseguy/nose-f1.xpm | 74 + hacks/images/noseguy/nose-f2.xbm | 46 + hacks/images/noseguy/nose-f2.xpm | 74 + hacks/images/noseguy/nose-f3.xbm | 46 + hacks/images/noseguy/nose-f3.xpm | 74 + hacks/images/noseguy/nose-f4.xbm | 46 + hacks/images/noseguy/nose-f4.xpm | 73 + hacks/images/noseguy/nose-l1.xbm | 46 + hacks/images/noseguy/nose-l1.xpm | 74 + hacks/images/noseguy/nose-l2.xbm | 46 + hacks/images/noseguy/nose-l2.xpm | 74 + hacks/images/noseguy/nose-r1.xbm | 46 + hacks/images/noseguy/nose-r1.xpm | 74 + hacks/images/noseguy/nose-r2.xbm | 46 + hacks/images/noseguy/nose-r2.xpm | 74 + hacks/images/osx_10_2.xpm | 298 + hacks/images/osx_10_3.xpm | 279 + hacks/images/pacman/eyes-d.xpm | 70 + hacks/images/pacman/eyes-l.xpm | 70 + hacks/images/pacman/eyes-r.xpm | 70 + hacks/images/pacman/eyes-u.xpm | 70 + hacks/images/pacman/ghost-d1.xpm | 72 + hacks/images/pacman/ghost-d2.xpm | 72 + hacks/images/pacman/ghost-l1.xpm | 72 + hacks/images/pacman/ghost-l2.xpm | 73 + hacks/images/pacman/ghost-mask.xpm | 69 + hacks/images/pacman/ghost-r1.xpm | 72 + hacks/images/pacman/ghost-r2.xpm | 72 + hacks/images/pacman/ghost-s1.xpm | 71 + hacks/images/pacman/ghost-s2.xpm | 71 + hacks/images/pacman/ghost-sf1.xpm | 71 + hacks/images/pacman/ghost-sf2.xpm | 71 + hacks/images/pacman/ghost-u1.xpm | 72 + hacks/images/pacman/ghost-u2.xpm | 72 + hacks/images/pacman/pacman-0.xpm | 69 + hacks/images/pacman/pacman-d1.xpm | 69 + hacks/images/pacman/pacman-d2.xpm | 69 + hacks/images/pacman/pacman-ds1.xpm | 69 + hacks/images/pacman/pacman-ds2.xpm | 69 + hacks/images/pacman/pacman-ds3.xpm | 69 + hacks/images/pacman/pacman-ds4.xpm | 69 + hacks/images/pacman/pacman-ds5.xpm | 69 + hacks/images/pacman/pacman-ds6.xpm | 69 + hacks/images/pacman/pacman-ds7.xpm | 69 + hacks/images/pacman/pacman-ds8.xpm | 69 + hacks/images/pacman/pacman-l1.xpm | 69 + hacks/images/pacman/pacman-l2.xpm | 69 + hacks/images/pacman/pacman-r1.xpm | 69 + hacks/images/pacman/pacman-r2.xpm | 69 + hacks/images/pacman/pacman-u1.xpm | 69 + hacks/images/pacman/pacman-u2.xpm | 69 + hacks/images/sball-bg.xpm | 355 + hacks/images/sball.xpm | 131 + hacks/images/scales.xpm | 204 + hacks/images/sea-texture.xpm | 199 + hacks/images/som.xbm | 1685 + hacks/images/timetunnel0.xpm | 343 + hacks/images/timetunnel1.xpm | 1111 + hacks/images/timetunnel2.xpm | 599 + hacks/images/toast.xpm | 222 + hacks/images/tree.xpm | 226 + hacks/images/tunnel0.xpm | 198 + hacks/images/tunnel1.xpm | 134 + hacks/images/tunnel2.xpm | 206 + hacks/images/tunnel3.xpm | 150 + hacks/images/tunnel4.xpm | 134 + hacks/images/tunnel5.xpm | 134 + hacks/images/tunnelstar.xpm | 323 + hacks/imsmap.c | 416 + hacks/imsmap.man | 64 + hacks/interaggregate.c | 972 + hacks/interaggregate.man | 72 + hacks/interference.c | 473 + hacks/interference.man | 86 + hacks/intermomentary.c | 572 + hacks/intermomentary.man | 82 + hacks/juggle.c | 2823 ++ hacks/juggle.man | 181 + hacks/julia.c | 442 + hacks/julia.man | 87 + hacks/kaleidescope.c | 490 + hacks/kaleidescope.man | 89 + hacks/kumppa.c | 533 + hacks/kumppa.man | 65 + hacks/laser.c | 366 + hacks/laser.man | 68 + hacks/lcdscrub.c | 204 + hacks/lcdscrub.man | 73 + hacks/lightning.c | 614 + hacks/lightning.man | 62 + hacks/link_axp.com | 107 + hacks/link_decc.com | 107 + hacks/lisa.c | 751 + hacks/lisa.man | 71 + hacks/lissie.c | 329 + hacks/lissie.man | 69 + hacks/ljlatest | 18 + hacks/ljlatest.man | 61 + hacks/lmorph.c | 568 + hacks/lmorph.man | 65 + hacks/loop.c | 1697 + hacks/loop.man | 65 + hacks/m6502.c | 282 + hacks/m6502.sh | 20 + hacks/maze.c | 1650 + hacks/maze.man | 150 + hacks/memscroller.c | 620 + hacks/memscroller.man | 78 + hacks/metaballs.c | 427 + hacks/metaballs.man | 73 + hacks/moire.c | 294 + hacks/moire.man | 68 + hacks/moire2.c | 349 + hacks/moire2.man | 63 + hacks/mountain.c | 291 + hacks/mountain.man | 60 + hacks/munch.c | 456 + hacks/munch.man | 153 + hacks/munge-ad.pl | 242 + hacks/nerverot.c | 1350 + hacks/nerverot.man | 132 + hacks/noseguy.c | 674 + hacks/noseguy.man | 84 + hacks/pacman.c | 1800 + hacks/pacman.h | 231 + hacks/pacman.man | 67 + hacks/pacman_ai.c | 879 + hacks/pacman_ai.h | 32 + hacks/pacman_level.c | 772 + hacks/pacman_level.h | 32 + hacks/pedal.c | 326 + hacks/pedal.man | 60 + hacks/penetrate.c | 987 + hacks/penetrate.man | 98 + hacks/penrose.c | 1348 + hacks/penrose.man | 106 + hacks/petri.c | 761 + hacks/petri.man | 129 + hacks/phosphor.c | 1558 + hacks/phosphor.man | 173 + hacks/piecewise.c | 995 + hacks/piecewise.man | 77 + hacks/polyominoes.c | 2375 ++ hacks/polyominoes.man | 65 + hacks/pong.c | 791 + hacks/pong.man | 82 + hacks/popsquares.c | 237 + hacks/pyro.c | 368 + hacks/pyro.man | 64 + hacks/qix.c | 621 + hacks/qix.man | 132 + hacks/rd-bomb.c | 590 + hacks/rd-bomb.man | 100 + hacks/ripples.c | 1130 + hacks/ripples.man | 96 + hacks/rocks.c | 545 + hacks/rocks.man | 90 + hacks/rorschach.c | 205 + hacks/rorschach.man | 75 + hacks/rotor.c | 401 + hacks/rotor.man | 68 + hacks/rotzoomer.c | 483 + hacks/rotzoomer.man | 88 + hacks/screenhack.c | 930 + hacks/screenhack.h | 61 + hacks/screenhackI.h | 138 + hacks/shadebobs.c | 463 + hacks/shadebobs.man | 65 + hacks/sierpinski.c | 224 + hacks/sierpinski.man | 69 + hacks/slidescreen.c | 477 + hacks/slidescreen.man | 97 + hacks/slip.c | 366 + hacks/slip.man | 86 + hacks/speedmine.c | 1634 + hacks/speedmine.man | 246 + hacks/sphere.c | 308 + hacks/sphere.man | 62 + hacks/spiral.c | 335 + hacks/spiral.man | 71 + hacks/spotlight.c | 326 + hacks/spotlight.man | 80 + hacks/squiral.c | 264 + hacks/squiral.man | 80 + hacks/starfish.c | 541 + hacks/starfish.man | 84 + hacks/strange.c | 694 + hacks/strange.man | 62 + hacks/substrate.c | 744 + hacks/substrate.man | 73 + hacks/swirl.c | 1482 + hacks/swirl.man | 70 + hacks/t3d.c | 969 + hacks/t3d.man | 131 + hacks/thornbird.c | 276 + hacks/thornbird.man | 64 + hacks/triangle.c | 361 + hacks/triangle.man | 56 + hacks/truchet.c | 526 + hacks/truchet.man | 139 + hacks/twang.c | 791 + hacks/twang.man | 132 + hacks/vermiculate.c | 1214 + hacks/vermiculate.man | 48 + hacks/vidwhacker | 505 + hacks/vidwhacker.man | 89 + hacks/vines.c | 203 + hacks/vines.man | 66 + hacks/vms_axp.opt | 4 + hacks/vms_axp_12.opt | 4 + hacks/vms_decc.opt | 4 + hacks/vms_decc_12.opt | 4 + hacks/wander.c | 255 + hacks/wander.man | 76 + hacks/webcollage | 3772 ++ hacks/webcollage-cocoa.m | 420 + hacks/webcollage-helper-cocoa.m | 400 + hacks/webcollage-helper.c | 504 + hacks/webcollage.man | 247 + hacks/whirlwindwarp.c | 499 + hacks/whirlwindwarp.man | 64 + hacks/whirlygig.c | 731 + hacks/whirlygig.man | 137 + hacks/worm.c | 442 + hacks/worm.man | 65 + hacks/wormhole.c | 726 + hacks/wormhole.man | 69 + hacks/xanalogtv.c | 617 + hacks/xanalogtv.man | 84 + hacks/xflame.c | 816 + hacks/xflame.man | 72 + hacks/xjack.c | 471 + hacks/xjack.man | 52 + hacks/xlockmore.c | 549 + hacks/xlockmore.h | 207 + hacks/xlockmoreI.h | 158 + hacks/xlyap.c | 1919 + hacks/xlyap.man | 223 + hacks/xmatrix.c | 1895 + hacks/xmatrix.man | 147 + hacks/xml2man.pl | 250 + hacks/xpm-pixmap.c | 363 + hacks/xpm-pixmap.h | 26 + hacks/xrayswarm.c | 1214 + hacks/xrayswarm.man | 50 + hacks/xscreensaver-sgigl.c | 266 + hacks/xspirograph.c | 330 + hacks/xspirograph.man | 71 + hacks/xsublim.c | 798 + hacks/xsublim.man | 91 + hacks/zoom.c | 270 + hacks/zoom.man | 111 + install-sh | 250 + intltool-extract.in | 309 + intltool-merge.in | 567 + intltool-update.in | 613 + makevms.com | 57 + po/._ja.po | Bin 0 -> 225 bytes po/._nb.po | Bin 0 -> 536 bytes po/ChangeLog | 300 + po/Makefile.in.in | 384 + po/POTFILES.in | 227 + po/ca.po | 8905 +++++ po/da.po | 9938 +++++ po/de.po | 9603 +++++ po/es.po | 10021 +++++ po/et.po | 8995 +++++ po/fi.po | 8939 +++++ po/fr.po | 9248 +++++ po/hu.po | 9760 +++++ po/it.po | 8967 +++++ po/ja.po | 9039 +++++ po/ko.po | 9340 +++++ po/nb.po | 9071 +++++ po/nl.po | 9542 +++++ po/pl.po | 9387 +++++ po/pt.po | 9995 +++++ po/pt_BR.po | 8902 +++++ po/ru.po | 9132 +++++ po/sk.po | 9242 +++++ po/sv.po | 11236 ++++++ po/update.sh | 24 + po/vi.po | 9347 +++++ po/wa.po | 9138 +++++ po/zh_CN.po | 9513 +++++ po/zh_TW.po | 9358 +++++ setup.com | 124 + utils/._colors.c | Bin 0 -> 170 bytes utils/Makefile.in | 301 + utils/README | 6 + utils/ad2c | 42 + utils/alpha.c | 215 + utils/alpha.h | 22 + utils/colorbars.c | 144 + utils/colorbars.h | 24 + utils/colors.c | 706 + utils/colors.h | 140 + utils/compile_axp.com | 25 + utils/compile_decc.com | 25 + utils/erase.c | 763 + utils/erase.h | 20 + utils/fade.c | 954 + utils/fade.h | 21 + utils/grabclient.c | 861 + utils/grabscreen.c | 898 + utils/grabscreen.h | 94 + utils/hsv.c | 81 + utils/hsv.h | 27 + utils/images/logo-180.gif | Bin 0 -> 3328 bytes utils/images/logo-180.xpm | 207 + utils/images/logo-50.gif | Bin 0 -> 857 bytes utils/images/logo-50.xpm | 77 + utils/images/logo-big.gif | Bin 0 -> 17019 bytes utils/images/logo.eps | 8058 ++++ utils/images/screensaver-cmndln.png | Bin 0 -> 1040 bytes utils/images/screensaver-colorselector.png | Bin 0 -> 1104 bytes utils/images/screensaver-diagnostic.png | Bin 0 -> 1307 bytes utils/images/screensaver-locking.png | Bin 0 -> 944 bytes utils/images/screensaver-power.png | Bin 0 -> 973 bytes utils/images/screensaver-snap.png | Bin 0 -> 1272 bytes utils/logo.c | 75 + utils/minixpm.c | 233 + utils/minixpm.h | 28 + utils/overlay.c | 158 + utils/resources.c | 265 + utils/resources.h | 33 + utils/spline.c | 330 + utils/spline.h | 51 + utils/usleep.c | 64 + utils/usleep.h | 20 + utils/utils.h | 27 + utils/version.h | 2 + utils/visual-gl.c | 278 + utils/visual.c | 544 + utils/visual.h | 32 + utils/vms-gtod.c | 31 + utils/vms-gtod.h | 85 + utils/vms-strdup.c | 25 + utils/vroot.h | 156 + utils/xdbe.c | 75 + utils/xdbe.h | 27 + utils/xmu.c | 173 + utils/xmu.h | 14 + utils/xscreensaver-intl.h | 36 + utils/xshm.c | 229 + utils/xshm.h | 37 + utils/yarandom.c | 139 + utils/yarandom.h | 65 + xscreensaver.spec | 204 + xscreensaver.xcodeproj/project.pbxproj | 30390 ++++++++++++++++ 1274 files changed, 693856 insertions(+) create mode 100644 INSTALL create mode 100644 Makefile.in create mode 100644 OSX/._InvertedSlider.m create mode 100644 OSX/._XScreenSaver.icns create mode 100644 OSX/English.lproj/InfoPlist.strings create mode 100644 OSX/English.lproj/SaverTester.nib/classes.nib create mode 100644 OSX/English.lproj/SaverTester.nib/info.nib create mode 100644 OSX/English.lproj/SaverTester.nib/keyedobjects.nib create mode 100644 OSX/InvertedSlider.h create mode 100644 OSX/InvertedSlider.m create mode 100644 OSX/Makefile create mode 100644 OSX/PrefsReader.h create mode 100644 OSX/PrefsReader.m create mode 100644 OSX/README create mode 100644 OSX/SaverTester.h create mode 100644 OSX/SaverTester.m create mode 100644 OSX/SaverTester.plist create mode 100644 OSX/XScreenSaver.icns create mode 100644 OSX/XScreenSaver.plist create mode 100644 OSX/XScreenSaverConfigSheet.h create mode 100644 OSX/XScreenSaverConfigSheet.m create mode 100644 OSX/XScreenSaverDMG.icns create mode 100644 OSX/XScreenSaverGLView.h create mode 100644 OSX/XScreenSaverGLView.m create mode 100644 OSX/XScreenSaverSubclass.m create mode 100644 OSX/XScreenSaverView.h create mode 100644 OSX/XScreenSaverView.m create mode 100644 OSX/bindist-DS_Store create mode 100644 OSX/bindist.rtf create mode 100644 OSX/jwxyz-timers.h create mode 100644 OSX/jwxyz-timers.m create mode 100644 OSX/jwxyz.h create mode 100644 OSX/jwxyz.m create mode 100644 OSX/main.m create mode 100644 OSX/osxgrabscreen.m create mode 100755 OSX/update-info-plist.pl create mode 100644 OSX/xscreensaver_Prefix.pch create mode 100644 README create mode 100644 README.VMS create mode 100644 README.hacking create mode 100644 aclocal.m4 create mode 100755 config.guess create mode 100644 config.h-vms create mode 100644 config.h.in create mode 100755 config.sub create mode 100755 configure create mode 100644 configure.in create mode 100644 driver/.gdbinit create mode 100644 driver/Makefile.in create mode 100644 driver/README create mode 100644 driver/XScreenSaver-Xm.ad create mode 100644 driver/XScreenSaver.ad.in create mode 100644 driver/XScreenSaver_Xm_ad.h create mode 100644 driver/XScreenSaver_ad.h create mode 100644 driver/auth.h create mode 100644 driver/compile_axp.com create mode 100644 driver/compile_decc.com create mode 100644 driver/demo-Gtk-conf.c create mode 100644 driver/demo-Gtk-conf.h create mode 100644 driver/demo-Gtk-stubs.h create mode 100644 driver/demo-Gtk-support.c create mode 100644 driver/demo-Gtk-support.h create mode 100644 driver/demo-Gtk-widgets.c create mode 100644 driver/demo-Gtk-widgets.h create mode 100644 driver/demo-Gtk.c create mode 100644 driver/demo-Xm-widgets.c create mode 100644 driver/demo-Xm.c create mode 100644 driver/dpms.c create mode 100644 driver/exec.c create mode 100644 driver/exec.h create mode 100644 driver/link_axp.com create mode 100644 driver/link_decc.com create mode 100644 driver/lock.c create mode 100644 driver/mlstring.c create mode 100644 driver/mlstring.h create mode 100644 driver/passwd-helper.c create mode 100644 driver/passwd-kerberos.c create mode 100644 driver/passwd-pam.c create mode 100644 driver/passwd-pwent.c create mode 100644 driver/passwd.c create mode 100644 driver/pdf2jpeg.m create mode 100644 driver/pdf2jpeg.man create mode 100644 driver/prefs.c create mode 100644 driver/prefs.h create mode 100644 driver/remote.c create mode 100644 driver/remote.h create mode 100644 driver/screens.c create mode 100644 driver/screensaver-properties.desktop.in create mode 100644 driver/setuid.c create mode 100644 driver/splash.c create mode 100644 driver/stderr.c create mode 100644 driver/subprocs.c create mode 100644 driver/test-apm.c create mode 100644 driver/test-fade.c create mode 100644 driver/test-grab.c create mode 100644 driver/test-mlstring.c create mode 100644 driver/test-passwd.c create mode 100644 driver/test-randr.c create mode 100644 driver/test-screens.c create mode 100644 driver/test-uid.c create mode 100644 driver/test-vp.c create mode 100644 driver/test-xdpms.c create mode 100644 driver/test-xinerama.c create mode 100644 driver/timers.c create mode 100644 driver/types.h create mode 100644 driver/vms-getpwnam.c create mode 100644 driver/vms-hpwd.c create mode 100644 driver/vms-pwd.h create mode 100644 driver/vms-validate.c create mode 100644 driver/vms_axp.opt create mode 100644 driver/vms_axp_12.opt create mode 100644 driver/vms_decc.opt create mode 100644 driver/vms_decc_12.opt create mode 100644 driver/windows.c create mode 100644 driver/xdpyinfo.c create mode 100644 driver/xscreensaver-command.c create mode 100644 driver/xscreensaver-command.man create mode 100644 driver/xscreensaver-demo.glade2 create mode 100644 driver/xscreensaver-demo.glade2p create mode 100644 driver/xscreensaver-demo.man create mode 100755 driver/xscreensaver-getimage-desktop create mode 100644 driver/xscreensaver-getimage-desktop.man create mode 100755 driver/xscreensaver-getimage-file create mode 100644 driver/xscreensaver-getimage-file.man create mode 100755 driver/xscreensaver-getimage-video create mode 100644 driver/xscreensaver-getimage-video.man create mode 100644 driver/xscreensaver-getimage.c create mode 100644 driver/xscreensaver-getimage.man create mode 100755 driver/xscreensaver-text create mode 100644 driver/xscreensaver-text.man create mode 100644 driver/xscreensaver.c create mode 100644 driver/xscreensaver.h create mode 100644 driver/xscreensaver.man create mode 100644 driver/xscreensaver.pam create mode 100644 driver/xset.c create mode 100644 hacks/.gdbinit create mode 100644 hacks/Makefile.in create mode 100644 hacks/README create mode 100644 hacks/abstractile.c create mode 100644 hacks/abstractile.man create mode 100644 hacks/analogtv.c create mode 100644 hacks/analogtv.h create mode 100644 hacks/anemone.c create mode 100644 hacks/anemone.man create mode 100644 hacks/anemotaxis.c create mode 100644 hacks/anemotaxis.man create mode 100644 hacks/ant.c create mode 100644 hacks/ant.man create mode 100644 hacks/apollonian.c create mode 100644 hacks/apollonian.man create mode 100644 hacks/apple2-main.c create mode 100644 hacks/apple2.c create mode 100644 hacks/apple2.h create mode 100644 hacks/apple2.man create mode 100644 hacks/asm6502.c create mode 100644 hacks/asm6502.h create mode 100644 hacks/attraction.c create mode 100644 hacks/attraction.man create mode 100644 hacks/automata.h create mode 100644 hacks/barcode.c create mode 100644 hacks/barcode.man create mode 100644 hacks/blaster.c create mode 100644 hacks/blaster.man create mode 100644 hacks/blitspin.c create mode 100644 hacks/blitspin.man create mode 100644 hacks/bouboule.c create mode 100644 hacks/bouboule.man create mode 100644 hacks/boxfit.c create mode 100644 hacks/boxfit.man create mode 100644 hacks/braid.c create mode 100644 hacks/braid.man create mode 100644 hacks/bsod.c create mode 100644 hacks/bsod.man create mode 100644 hacks/bubbles-default.c create mode 100644 hacks/bubbles.c create mode 100644 hacks/bubbles.h create mode 100644 hacks/bubbles.man create mode 100644 hacks/bumps.c create mode 100644 hacks/bumps.man create mode 100644 hacks/ccurve.c create mode 100644 hacks/ccurve.man create mode 100644 hacks/celtic.c create mode 100644 hacks/celtic.man create mode 100755 hacks/check-configs.pl create mode 100644 hacks/cloudlife.c create mode 100644 hacks/cloudlife.man create mode 100644 hacks/compass.c create mode 100644 hacks/compass.man create mode 100644 hacks/compile_axp.com create mode 100644 hacks/compile_decc.com create mode 100644 hacks/config/._klein.xml create mode 100644 hacks/config/README create mode 100644 hacks/config/abstractile.xml create mode 100644 hacks/config/anemone.xml create mode 100644 hacks/config/anemotaxis.xml create mode 100644 hacks/config/ant.xml create mode 100644 hacks/config/antinspect.xml create mode 100644 hacks/config/antmaze.xml create mode 100644 hacks/config/antspotlight.xml create mode 100644 hacks/config/apollonian.xml create mode 100644 hacks/config/apple2.xml create mode 100644 hacks/config/atlantis.xml create mode 100644 hacks/config/attraction.xml create mode 100644 hacks/config/atunnel.xml create mode 100644 hacks/config/barcode.xml create mode 100644 hacks/config/blaster.xml create mode 100644 hacks/config/blinkbox.xml create mode 100644 hacks/config/blitspin.xml create mode 100644 hacks/config/blocktube.xml create mode 100644 hacks/config/boing.xml create mode 100644 hacks/config/bouboule.xml create mode 100644 hacks/config/bouncingcow.xml create mode 100644 hacks/config/boxed.xml create mode 100644 hacks/config/boxfit.xml create mode 100644 hacks/config/braid.xml create mode 100644 hacks/config/bsod.xml create mode 100644 hacks/config/bubble3d.xml create mode 100644 hacks/config/bubbles.xml create mode 100644 hacks/config/bumps.xml create mode 100644 hacks/config/cage.xml create mode 100644 hacks/config/carousel.xml create mode 100644 hacks/config/ccurve.xml create mode 100644 hacks/config/celtic.xml create mode 100644 hacks/config/circuit.xml create mode 100644 hacks/config/cloudlife.xml create mode 100644 hacks/config/compass.xml create mode 100644 hacks/config/coral.xml create mode 100644 hacks/config/crackberg.xml create mode 100644 hacks/config/critical.xml create mode 100644 hacks/config/crystal.xml create mode 100644 hacks/config/cube21.xml create mode 100644 hacks/config/cubenetic.xml create mode 100644 hacks/config/cubestorm.xml create mode 100644 hacks/config/cubicgrid.xml create mode 100644 hacks/config/cwaves.xml create mode 100644 hacks/config/cynosure.xml create mode 100644 hacks/config/dangerball.xml create mode 100644 hacks/config/decayscreen.xml create mode 100644 hacks/config/deco.xml create mode 100644 hacks/config/deluxe.xml create mode 100644 hacks/config/demon.xml create mode 100644 hacks/config/discrete.xml create mode 100644 hacks/config/distort.xml create mode 100644 hacks/config/dnalogo.xml create mode 100644 hacks/config/drift.xml create mode 100644 hacks/config/endgame.xml create mode 100644 hacks/config/engine.xml create mode 100644 hacks/config/epicycle.xml create mode 100644 hacks/config/eruption.xml create mode 100644 hacks/config/euler2d.xml create mode 100644 hacks/config/extrusion.xml create mode 100644 hacks/config/fadeplot.xml create mode 100644 hacks/config/fiberlamp.xml create mode 100644 hacks/config/fireworkx.xml create mode 100644 hacks/config/flag.xml create mode 100644 hacks/config/flame.xml create mode 100644 hacks/config/flipflop.xml create mode 100644 hacks/config/flipscreen3d.xml create mode 100644 hacks/config/fliptext.xml create mode 100644 hacks/config/flow.xml create mode 100644 hacks/config/fluidballs.xml create mode 100644 hacks/config/flurry.xml create mode 100644 hacks/config/flyingtoasters.xml create mode 100644 hacks/config/fontglide.xml create mode 100644 hacks/config/forest.xml create mode 100644 hacks/config/fuzzyflakes.xml create mode 100644 hacks/config/galaxy.xml create mode 100644 hacks/config/gears.xml create mode 100644 hacks/config/gflux.xml create mode 100644 hacks/config/glblur.xml create mode 100644 hacks/config/glcells.xml create mode 100644 hacks/config/gleidescope.xml create mode 100644 hacks/config/glforestfire.xml create mode 100644 hacks/config/glhanoi.xml create mode 100644 hacks/config/glknots.xml create mode 100644 hacks/config/glmatrix.xml create mode 100644 hacks/config/glplanet.xml create mode 100644 hacks/config/glschool.xml create mode 100644 hacks/config/glslideshow.xml create mode 100644 hacks/config/glsnake.xml create mode 100644 hacks/config/gltext.xml create mode 100644 hacks/config/goop.xml create mode 100644 hacks/config/grav.xml create mode 100644 hacks/config/greynetic.xml create mode 100644 hacks/config/halftone.xml create mode 100644 hacks/config/halo.xml create mode 100644 hacks/config/helix.xml create mode 100644 hacks/config/hopalong.xml create mode 100644 hacks/config/hyperball.xml create mode 100644 hacks/config/hypercube.xml create mode 100644 hacks/config/hypertorus.xml create mode 100644 hacks/config/hypnowheel.xml create mode 100644 hacks/config/ifs.xml create mode 100644 hacks/config/imsmap.xml create mode 100644 hacks/config/interaggregate.xml create mode 100644 hacks/config/interference.xml create mode 100644 hacks/config/intermomentary.xml create mode 100644 hacks/config/jigglypuff.xml create mode 100644 hacks/config/jigsaw.xml create mode 100644 hacks/config/juggle.xml create mode 100644 hacks/config/juggler3d.xml create mode 100644 hacks/config/julia.xml create mode 100644 hacks/config/kaleidescope.xml create mode 100644 hacks/config/klein.xml create mode 100644 hacks/config/kumppa.xml create mode 100644 hacks/config/lament.xml create mode 100644 hacks/config/laser.xml create mode 100644 hacks/config/lavalite.xml create mode 100644 hacks/config/lcdscrub.xml create mode 100644 hacks/config/lightning.xml create mode 100644 hacks/config/lisa.xml create mode 100644 hacks/config/lissie.xml create mode 100644 hacks/config/lmorph.xml create mode 100644 hacks/config/lockward.xml create mode 100644 hacks/config/loop.xml create mode 100644 hacks/config/m6502.xml create mode 100644 hacks/config/maze.xml create mode 100644 hacks/config/memscroller.xml create mode 100644 hacks/config/menger.xml create mode 100644 hacks/config/metaballs.xml create mode 100644 hacks/config/mirrorblob.xml create mode 100644 hacks/config/mismunch.xml create mode 100644 hacks/config/moebius.xml create mode 100644 hacks/config/moebiusgears.xml create mode 100644 hacks/config/moire.xml create mode 100644 hacks/config/moire2.xml create mode 100644 hacks/config/molecule.xml create mode 100644 hacks/config/morph3d.xml create mode 100644 hacks/config/mountain.xml create mode 100644 hacks/config/munch.xml create mode 100644 hacks/config/nerverot.xml create mode 100644 hacks/config/noof.xml create mode 100644 hacks/config/noseguy.xml create mode 100644 hacks/config/pacman.xml create mode 100644 hacks/config/pedal.xml create mode 100644 hacks/config/penetrate.xml create mode 100644 hacks/config/penrose.xml create mode 100644 hacks/config/petri.xml create mode 100644 hacks/config/phosphor.xml create mode 100644 hacks/config/photopile.xml create mode 100644 hacks/config/piecewise.xml create mode 100644 hacks/config/pinion.xml create mode 100644 hacks/config/pipes.xml create mode 100644 hacks/config/polyhedra.xml create mode 100644 hacks/config/polyominoes.xml create mode 100644 hacks/config/polytopes.xml create mode 100644 hacks/config/pong.xml create mode 100644 hacks/config/popsquares.xml create mode 100644 hacks/config/providence.xml create mode 100644 hacks/config/pulsar.xml create mode 100644 hacks/config/pyro.xml create mode 100644 hacks/config/qix.xml create mode 100644 hacks/config/queens.xml create mode 100644 hacks/config/rd-bomb.xml create mode 100644 hacks/config/rdbomb.xml create mode 100644 hacks/config/ripples.xml create mode 100644 hacks/config/rocks.xml create mode 100644 hacks/config/rorschach.xml create mode 100644 hacks/config/rotor.xml create mode 100644 hacks/config/rotzoomer.xml create mode 100644 hacks/config/rubik.xml create mode 100644 hacks/config/rubikblocks.xml create mode 100644 hacks/config/sballs.xml create mode 100644 hacks/config/shadebobs.xml create mode 100644 hacks/config/sierpinski.xml create mode 100644 hacks/config/sierpinski3d.xml create mode 100644 hacks/config/skytentacles.xml create mode 100644 hacks/config/slidescreen.xml create mode 100644 hacks/config/slip.xml create mode 100644 hacks/config/sonar.xml create mode 100644 hacks/config/speedmine.xml create mode 100644 hacks/config/sphere.xml create mode 100644 hacks/config/spheremonics.xml create mode 100644 hacks/config/spiral.xml create mode 100644 hacks/config/spotlight.xml create mode 100644 hacks/config/sproingies.xml create mode 100644 hacks/config/squiral.xml create mode 100644 hacks/config/stairs.xml create mode 100644 hacks/config/starfish.xml create mode 100644 hacks/config/starwars.xml create mode 100644 hacks/config/stonerview.xml create mode 100644 hacks/config/strange.xml create mode 100644 hacks/config/substrate.xml create mode 100644 hacks/config/superquadrics.xml create mode 100644 hacks/config/surfaces.xml create mode 100644 hacks/config/swirl.xml create mode 100644 hacks/config/t3d.xml create mode 100644 hacks/config/tangram.xml create mode 100644 hacks/config/thornbird.xml create mode 100644 hacks/config/timetunnel.xml create mode 100644 hacks/config/topblock.xml create mode 100644 hacks/config/triangle.xml create mode 100644 hacks/config/truchet.xml create mode 100644 hacks/config/twang.xml create mode 100644 hacks/config/vermiculate.xml create mode 100644 hacks/config/vidwhacker.xml create mode 100644 hacks/config/vines.xml create mode 100644 hacks/config/voronoi.xml create mode 100644 hacks/config/wander.xml create mode 100644 hacks/config/webcollage.xml create mode 100644 hacks/config/whirlwindwarp.xml create mode 100644 hacks/config/whirlygig.xml create mode 100644 hacks/config/worm.xml create mode 100644 hacks/config/wormhole.xml create mode 100644 hacks/config/xanalogtv.xml create mode 100644 hacks/config/xflame.xml create mode 100644 hacks/config/xjack.xml create mode 100644 hacks/config/xlyap.xml create mode 100644 hacks/config/xmatrix.xml create mode 100644 hacks/config/xrayswarm.xml create mode 100644 hacks/config/xspirograph.xml create mode 100644 hacks/config/xss.dtd create mode 100644 hacks/config/xss.xsd create mode 100644 hacks/config/zoom.xml create mode 100644 hacks/coral.c create mode 100644 hacks/coral.man create mode 100644 hacks/critical.c create mode 100644 hacks/critical.man create mode 100644 hacks/crystal.c create mode 100644 hacks/crystal.man create mode 100644 hacks/cwaves.c create mode 100644 hacks/cwaves.man create mode 100644 hacks/cynosure.c create mode 100644 hacks/cynosure.man create mode 100644 hacks/decayscreen.c create mode 100644 hacks/decayscreen.man create mode 100644 hacks/deco.c create mode 100644 hacks/deco.man create mode 100644 hacks/deluxe.c create mode 100644 hacks/deluxe.man create mode 100644 hacks/demon.c create mode 100644 hacks/demon.man create mode 100644 hacks/discrete.c create mode 100644 hacks/discrete.man create mode 100644 hacks/distort.c create mode 100644 hacks/distort.man create mode 100644 hacks/drift.c create mode 100644 hacks/drift.man create mode 100644 hacks/epicycle.c create mode 100644 hacks/epicycle.man create mode 100644 hacks/eruption.c create mode 100644 hacks/eruption.man create mode 100644 hacks/euler2d.c create mode 100644 hacks/euler2d.man create mode 100644 hacks/euler2d.tex create mode 100644 hacks/fadeplot.c create mode 100644 hacks/fadeplot.man create mode 100644 hacks/fiberlamp.c create mode 100644 hacks/fiberlamp.man create mode 100644 hacks/fireworkx.c create mode 100644 hacks/fireworkx.man create mode 100644 hacks/fireworkx_mmx.S create mode 100644 hacks/flag.c create mode 100644 hacks/flag.man create mode 100644 hacks/flame.c create mode 100644 hacks/flame.man create mode 100644 hacks/flow.c create mode 100644 hacks/flow.man create mode 100644 hacks/fluidballs.c create mode 100644 hacks/fluidballs.man create mode 100644 hacks/fontglide.c create mode 100644 hacks/fontglide.man create mode 100644 hacks/forest.c create mode 100644 hacks/forest.man create mode 100644 hacks/fps.c create mode 100644 hacks/fps.h create mode 100644 hacks/fpsI.h create mode 100644 hacks/fuzzyflakes.c create mode 100644 hacks/fuzzyflakes.man create mode 100644 hacks/galaxy.c create mode 100644 hacks/galaxy.man create mode 100644 hacks/glx/._grab-ximage.c create mode 100644 hacks/glx/._jigsaw.c create mode 100644 hacks/glx/._rubikblocks.man create mode 100644 hacks/glx/Makefile.in create mode 100644 hacks/glx/README create mode 100644 hacks/glx/antinspect.c create mode 100644 hacks/glx/antinspect.man create mode 100644 hacks/glx/antmaze.c create mode 100644 hacks/glx/antmaze.man create mode 100644 hacks/glx/ants.h create mode 100644 hacks/glx/antspotlight.c create mode 100644 hacks/glx/antspotlight.man create mode 100644 hacks/glx/atlantis.c create mode 100644 hacks/glx/atlantis.h create mode 100644 hacks/glx/atlantis.man create mode 100644 hacks/glx/atunnel.c create mode 100644 hacks/glx/atunnel.man create mode 100644 hacks/glx/b_draw.c create mode 100644 hacks/glx/b_lockglue.c create mode 100644 hacks/glx/b_sphere.c create mode 100644 hacks/glx/blinkbox.c create mode 100644 hacks/glx/blinkbox.man create mode 100644 hacks/glx/blocktube.c create mode 100644 hacks/glx/blocktube.man create mode 100644 hacks/glx/boing.c create mode 100644 hacks/glx/boing.man create mode 100644 hacks/glx/bouncingcow.c create mode 100644 hacks/glx/bouncingcow.man create mode 100644 hacks/glx/boxed.c create mode 100644 hacks/glx/boxed.h create mode 100644 hacks/glx/boxed.man create mode 100644 hacks/glx/bubble3d.c create mode 100644 hacks/glx/bubble3d.h create mode 100644 hacks/glx/bubble3d.man create mode 100644 hacks/glx/buildlwo.c create mode 100644 hacks/glx/buildlwo.h create mode 100644 hacks/glx/cage.c create mode 100644 hacks/glx/cage.man create mode 100644 hacks/glx/carousel.c create mode 100644 hacks/glx/carousel.man create mode 100644 hacks/glx/chessgames.h create mode 100644 hacks/glx/chessmodels.c create mode 100644 hacks/glx/chessmodels.h create mode 100644 hacks/glx/circuit.c create mode 100644 hacks/glx/circuit.man create mode 100644 hacks/glx/cow_face.c create mode 100644 hacks/glx/cow_hide.c create mode 100644 hacks/glx/cow_hoofs.c create mode 100644 hacks/glx/cow_horns.c create mode 100644 hacks/glx/cow_tail.c create mode 100644 hacks/glx/cow_udder.c create mode 100644 hacks/glx/crackberg.c create mode 100644 hacks/glx/crackberg.man create mode 100644 hacks/glx/cube21.c create mode 100644 hacks/glx/cube21.man create mode 100644 hacks/glx/cubenetic.c create mode 100644 hacks/glx/cubenetic.man create mode 100644 hacks/glx/cubestorm.c create mode 100644 hacks/glx/cubestorm.man create mode 100644 hacks/glx/cubicgrid.c create mode 100644 hacks/glx/cubicgrid.man create mode 100644 hacks/glx/dangerball.c create mode 100644 hacks/glx/dangerball.man create mode 100644 hacks/glx/dnalogo.c create mode 100644 hacks/glx/dolphin.c create mode 100644 hacks/glx/dropshadow.c create mode 100644 hacks/glx/dropshadow.h create mode 100755 hacks/glx/dxf2gl.pl create mode 100644 hacks/glx/e_textures.h create mode 100644 hacks/glx/endgame.c create mode 100644 hacks/glx/endgame.man create mode 100644 hacks/glx/engine.c create mode 100644 hacks/glx/engine.man create mode 100644 hacks/glx/extrusion-helix2.c create mode 100644 hacks/glx/extrusion-helix3.c create mode 100644 hacks/glx/extrusion-helix4.c create mode 100644 hacks/glx/extrusion-joinoffset.c create mode 100644 hacks/glx/extrusion-screw.c create mode 100644 hacks/glx/extrusion-taper.c create mode 100644 hacks/glx/extrusion-twistoid.c create mode 100644 hacks/glx/extrusion.c create mode 100644 hacks/glx/extrusion.h create mode 100644 hacks/glx/extrusion.man create mode 100644 hacks/glx/flipflop.c create mode 100644 hacks/glx/flipflop.man create mode 100644 hacks/glx/flipscreen3d.c create mode 100644 hacks/glx/flipscreen3d.man create mode 100644 hacks/glx/fliptext.c create mode 100644 hacks/glx/fliptext.man create mode 100644 hacks/glx/flurry-smoke.c create mode 100644 hacks/glx/flurry-spark.c create mode 100644 hacks/glx/flurry-star.c create mode 100644 hacks/glx/flurry-texture.c create mode 100644 hacks/glx/flurry.c create mode 100644 hacks/glx/flurry.h create mode 100644 hacks/glx/flurry.man create mode 100644 hacks/glx/flyingtoasters.c create mode 100644 hacks/glx/flyingtoasters.man create mode 100644 hacks/glx/font-ximage.c create mode 100644 hacks/glx/font-ximage.h create mode 100644 hacks/glx/fps-gl.c create mode 100644 hacks/glx/gears.c create mode 100644 hacks/glx/gears.man create mode 100644 hacks/glx/gflux.c create mode 100644 hacks/glx/gflux.man create mode 100644 hacks/glx/glblur.c create mode 100644 hacks/glx/glblur.man create mode 100644 hacks/glx/glcells.c create mode 100644 hacks/glx/glcells.man create mode 100644 hacks/glx/gleidescope.c create mode 100644 hacks/glx/gleidescope.man create mode 100644 hacks/glx/glforestfire.c create mode 100644 hacks/glx/glforestfire.man create mode 100644 hacks/glx/glhanoi.c create mode 100644 hacks/glx/glhanoi.man create mode 100644 hacks/glx/glknots.c create mode 100644 hacks/glx/glknots.man create mode 100644 hacks/glx/gllist.c create mode 100644 hacks/glx/gllist.h create mode 100644 hacks/glx/glmatrix.c create mode 100644 hacks/glx/glmatrix.man create mode 100644 hacks/glx/glplanet.c create mode 100644 hacks/glx/glplanet.man create mode 100644 hacks/glx/glschool.c create mode 100644 hacks/glx/glschool.h create mode 100644 hacks/glx/glschool.man create mode 100644 hacks/glx/glschool_alg.c create mode 100644 hacks/glx/glschool_alg.h create mode 100644 hacks/glx/glschool_gl.c create mode 100644 hacks/glx/glschool_gl.h create mode 100644 hacks/glx/glslideshow.c create mode 100644 hacks/glx/glslideshow.man create mode 100644 hacks/glx/glsnake.c create mode 100644 hacks/glx/glsnake.man create mode 100644 hacks/glx/gltext.c create mode 100644 hacks/glx/gltext.man create mode 100644 hacks/glx/gltrackball.c create mode 100644 hacks/glx/gltrackball.h create mode 100644 hacks/glx/glut_roman.h create mode 100644 hacks/glx/glut_stroke.c create mode 100644 hacks/glx/glut_swidth.c create mode 100644 hacks/glx/glutstroke.h create mode 100644 hacks/glx/glxfonts.c create mode 100644 hacks/glx/glxfonts.h create mode 100644 hacks/glx/grab-ximage.c create mode 100644 hacks/glx/grab-ximage.h create mode 100644 hacks/glx/hypertorus.c create mode 100644 hacks/glx/hypertorus.man create mode 100644 hacks/glx/hypnowheel.c create mode 100644 hacks/glx/hypnowheel.man create mode 100644 hacks/glx/involute.c create mode 100644 hacks/glx/involute.h create mode 100644 hacks/glx/jigglypuff.c create mode 100644 hacks/glx/jigglypuff.man create mode 100644 hacks/glx/jigsaw.c create mode 100644 hacks/glx/jigsaw.man create mode 100644 hacks/glx/juggler3d.c create mode 100644 hacks/glx/juggler3d.man create mode 100644 hacks/glx/klein.c create mode 100644 hacks/glx/klein.man create mode 100644 hacks/glx/lament.c create mode 100644 hacks/glx/lament.man create mode 100644 hacks/glx/lavalite.c create mode 100644 hacks/glx/lavalite.man create mode 100644 hacks/glx/lockward.c create mode 100644 hacks/glx/lockward.man create mode 100644 hacks/glx/marching.c create mode 100644 hacks/glx/marching.h create mode 100644 hacks/glx/menger.c create mode 100644 hacks/glx/menger.man create mode 100644 hacks/glx/mirrorblob.c create mode 100644 hacks/glx/mirrorblob.man create mode 100644 hacks/glx/moebius.c create mode 100644 hacks/glx/moebius.man create mode 100644 hacks/glx/moebiusgears.c create mode 100644 hacks/glx/moebiusgears.man create mode 100644 hacks/glx/molecule.c create mode 100644 hacks/glx/molecule.man create mode 100755 hacks/glx/molecules.sh create mode 100644 hacks/glx/morph3d.c create mode 100644 hacks/glx/morph3d.man create mode 100644 hacks/glx/noof.c create mode 100644 hacks/glx/noof.man create mode 100644 hacks/glx/normals.c create mode 100644 hacks/glx/normals.h create mode 100644 hacks/glx/photopile.c create mode 100644 hacks/glx/photopile.man create mode 100644 hacks/glx/pinion.c create mode 100644 hacks/glx/pinion.man create mode 100644 hacks/glx/pipeobjs.c create mode 100644 hacks/glx/pipes.c create mode 100644 hacks/glx/pipes.man create mode 100644 hacks/glx/polyhedra-gl.c create mode 100644 hacks/glx/polyhedra.c create mode 100644 hacks/glx/polyhedra.h create mode 100644 hacks/glx/polyhedra.man create mode 100644 hacks/glx/polytopes.c create mode 100644 hacks/glx/polytopes.man create mode 100644 hacks/glx/providence.c create mode 100644 hacks/glx/providence.man create mode 100644 hacks/glx/pulsar.c create mode 100644 hacks/glx/pulsar.man create mode 100644 hacks/glx/queens.c create mode 100644 hacks/glx/queens.man create mode 100644 hacks/glx/rotator.c create mode 100644 hacks/glx/rotator.h create mode 100644 hacks/glx/rubik.c create mode 100644 hacks/glx/rubik.man create mode 100644 hacks/glx/rubikblocks.c create mode 100644 hacks/glx/rubikblocks.man create mode 100644 hacks/glx/s1_1.c create mode 100644 hacks/glx/s1_2.c create mode 100644 hacks/glx/s1_3.c create mode 100644 hacks/glx/s1_4.c create mode 100644 hacks/glx/s1_5.c create mode 100644 hacks/glx/s1_6.c create mode 100644 hacks/glx/s1_b.c create mode 100644 hacks/glx/sballs.c create mode 100644 hacks/glx/sballs.man create mode 100644 hacks/glx/shark.c create mode 100644 hacks/glx/sierpinski3d.c create mode 100644 hacks/glx/sierpinski3d.man create mode 100644 hacks/glx/skytentacles.c create mode 100644 hacks/glx/skytentacles.man create mode 100644 hacks/glx/sonar-icmp.c create mode 100644 hacks/glx/sonar-sim.c create mode 100644 hacks/glx/sonar.c create mode 100644 hacks/glx/sonar.h create mode 100644 hacks/glx/sonar.man create mode 100644 hacks/glx/sphere.c create mode 100644 hacks/glx/sphere.h create mode 100644 hacks/glx/spheremonics.c create mode 100644 hacks/glx/spheremonics.man create mode 100644 hacks/glx/sproingies.c create mode 100644 hacks/glx/sproingies.h create mode 100644 hacks/glx/sproingies.man create mode 100644 hacks/glx/sproingiewrap.c create mode 100644 hacks/glx/stairs.c create mode 100644 hacks/glx/stairs.man create mode 100644 hacks/glx/starwars.c create mode 100644 hacks/glx/starwars.man create mode 100644 hacks/glx/starwars.txt create mode 100644 hacks/glx/stonerview-move.c create mode 100644 hacks/glx/stonerview-move.h create mode 100644 hacks/glx/stonerview-osc.c create mode 100644 hacks/glx/stonerview-osc.h create mode 100644 hacks/glx/stonerview-view.c create mode 100644 hacks/glx/stonerview.c create mode 100644 hacks/glx/stonerview.h create mode 100644 hacks/glx/stonerview.man create mode 100644 hacks/glx/superquadrics.c create mode 100644 hacks/glx/superquadrics.man create mode 100644 hacks/glx/surfaces.c create mode 100644 hacks/glx/surfaces.man create mode 100644 hacks/glx/swim.c create mode 100644 hacks/glx/tangram.c create mode 100644 hacks/glx/tangram.man create mode 100644 hacks/glx/tangram_shapes.c create mode 100644 hacks/glx/tangram_shapes.h create mode 100644 hacks/glx/teapot.c create mode 100644 hacks/glx/teapot.h create mode 100644 hacks/glx/texfont.c create mode 100644 hacks/glx/texfont.h create mode 100644 hacks/glx/timetunnel.c create mode 100644 hacks/glx/timetunnel.man create mode 100644 hacks/glx/toast.c create mode 100644 hacks/glx/toast2.c create mode 100644 hacks/glx/toaster.c create mode 100644 hacks/glx/toaster_base.c create mode 100644 hacks/glx/toaster_handle.c create mode 100644 hacks/glx/toaster_handle2.c create mode 100644 hacks/glx/toaster_jet.c create mode 100644 hacks/glx/toaster_knob.c create mode 100644 hacks/glx/toaster_slots.c create mode 100644 hacks/glx/toaster_wing.c create mode 100644 hacks/glx/topblock.c create mode 100644 hacks/glx/topblock.h create mode 100644 hacks/glx/topblock.man create mode 100644 hacks/glx/trackball.c create mode 100644 hacks/glx/trackball.h create mode 100644 hacks/glx/tube.c create mode 100644 hacks/glx/tube.h create mode 100644 hacks/glx/tunnel_draw.c create mode 100644 hacks/glx/tunnel_draw.h create mode 100644 hacks/glx/voronoi.c create mode 100644 hacks/glx/voronoi.man create mode 100644 hacks/glx/whale.c create mode 100644 hacks/glx/xlock-gl-utils.c create mode 100644 hacks/glx/xpm-ximage.c create mode 100644 hacks/glx/xpm-ximage.h create mode 100644 hacks/glx/xscreensaver-gl-helper.c create mode 100644 hacks/glx/xscreensaver-gl-helper.man create mode 100644 hacks/goop.c create mode 100644 hacks/goop.man create mode 100644 hacks/grav.c create mode 100644 hacks/grav.man create mode 100644 hacks/greynetic.c create mode 100644 hacks/greynetic.man create mode 100644 hacks/halftone.c create mode 100644 hacks/halftone.man create mode 100644 hacks/halo.c create mode 100644 hacks/halo.man create mode 100644 hacks/helix.c create mode 100644 hacks/helix.man create mode 100644 hacks/hopalong.c create mode 100644 hacks/hopalong.man create mode 100644 hacks/hyperball.c create mode 100644 hacks/hyperball.man create mode 100644 hacks/hypercube.c create mode 100644 hacks/hypercube.man create mode 100644 hacks/ifs.c create mode 100644 hacks/ifs.man create mode 100644 hacks/images/6x10font.xbm create mode 100644 hacks/images/amiga.xpm create mode 100644 hacks/images/apple2font.xbm create mode 100644 hacks/images/atari.xbm create mode 100644 hacks/images/atm.xbm create mode 100644 hacks/images/blocktube.xpm create mode 100644 hacks/images/bob.xbm create mode 100644 hacks/images/bubbles/blood.pov create mode 100644 hacks/images/bubbles/blood1.xpm create mode 100644 hacks/images/bubbles/blood10.xpm create mode 100644 hacks/images/bubbles/blood11.xpm create mode 100644 hacks/images/bubbles/blood2.xpm create mode 100644 hacks/images/bubbles/blood3.xpm create mode 100644 hacks/images/bubbles/blood4.xpm create mode 100644 hacks/images/bubbles/blood5.xpm create mode 100644 hacks/images/bubbles/blood6.xpm create mode 100644 hacks/images/bubbles/blood7.xpm create mode 100644 hacks/images/bubbles/blood8.xpm create mode 100644 hacks/images/bubbles/blood9.xpm create mode 100644 hacks/images/bubbles/blue.pov create mode 100644 hacks/images/bubbles/blue1.xpm create mode 100644 hacks/images/bubbles/blue10.xpm create mode 100644 hacks/images/bubbles/blue11.xpm create mode 100644 hacks/images/bubbles/blue2.xpm create mode 100644 hacks/images/bubbles/blue3.xpm create mode 100644 hacks/images/bubbles/blue4.xpm create mode 100644 hacks/images/bubbles/blue5.xpm create mode 100644 hacks/images/bubbles/blue6.xpm create mode 100644 hacks/images/bubbles/blue7.xpm create mode 100644 hacks/images/bubbles/blue8.xpm create mode 100644 hacks/images/bubbles/blue9.xpm create mode 100644 hacks/images/bubbles/glass.pov create mode 100644 hacks/images/bubbles/glass1.xpm create mode 100644 hacks/images/bubbles/glass10.xpm create mode 100644 hacks/images/bubbles/glass11.xpm create mode 100644 hacks/images/bubbles/glass2.xpm create mode 100644 hacks/images/bubbles/glass3.xpm create mode 100644 hacks/images/bubbles/glass4.xpm create mode 100644 hacks/images/bubbles/glass5.xpm create mode 100644 hacks/images/bubbles/glass6.xpm create mode 100644 hacks/images/bubbles/glass7.xpm create mode 100644 hacks/images/bubbles/glass8.xpm create mode 100644 hacks/images/bubbles/glass9.xpm create mode 100644 hacks/images/bubbles/jade.pov create mode 100644 hacks/images/bubbles/jade1.xpm create mode 100644 hacks/images/bubbles/jade10.xpm create mode 100644 hacks/images/bubbles/jade11.xpm create mode 100644 hacks/images/bubbles/jade2.xpm create mode 100644 hacks/images/bubbles/jade3.xpm create mode 100644 hacks/images/bubbles/jade4.xpm create mode 100644 hacks/images/bubbles/jade5.xpm create mode 100644 hacks/images/bubbles/jade6.xpm create mode 100644 hacks/images/bubbles/jade7.xpm create mode 100644 hacks/images/bubbles/jade8.xpm create mode 100644 hacks/images/bubbles/jade9.xpm create mode 100644 hacks/images/chromesphere.xpm create mode 100644 hacks/images/earth.xpm create mode 100644 hacks/images/ground.xpm create mode 100644 hacks/images/hmac.xpm create mode 100644 hacks/images/jigglymap.xpm create mode 100644 hacks/images/lament.xpm create mode 100644 hacks/images/m6502/._amiga.asm create mode 100644 hacks/images/m6502/._colors.asm create mode 100644 hacks/images/m6502/._disco.asm create mode 100644 hacks/images/m6502/._softsprite.asm create mode 100644 hacks/images/m6502/._starfield2d.asm create mode 100644 hacks/images/m6502/amiga.asm create mode 100644 hacks/images/m6502/breakout.asm create mode 100644 hacks/images/m6502/byterun.asm create mode 100644 hacks/images/m6502/cellular-30.asm create mode 100644 hacks/images/m6502/cellular-600.asm create mode 100644 hacks/images/m6502/colors.asm create mode 100644 hacks/images/m6502/crunch6502.asm create mode 100644 hacks/images/m6502/demoscene.asm create mode 100644 hacks/images/m6502/disco.asm create mode 100644 hacks/images/m6502/dmsc.asm create mode 100644 hacks/images/m6502/dragon-fractal.asm create mode 100644 hacks/images/m6502/fullscreenlogo.asm create mode 100644 hacks/images/m6502/keftal.asm create mode 100644 hacks/images/m6502/matrix.asm create mode 100644 hacks/images/m6502/noise.asm create mode 100644 hacks/images/m6502/random-walk.asm create mode 100644 hacks/images/m6502/random.asm create mode 100644 hacks/images/m6502/random2.asm create mode 100644 hacks/images/m6502/rorschach.asm create mode 100644 hacks/images/m6502/santa.asm create mode 100644 hacks/images/m6502/selfmodify.asm create mode 100644 hacks/images/m6502/sierpinsky.asm create mode 100644 hacks/images/m6502/softsprite.asm create mode 100644 hacks/images/m6502/spacer.asm create mode 100644 hacks/images/m6502/starfield2d.asm create mode 100644 hacks/images/m6502/wave6502.asm create mode 100644 hacks/images/m6502/zookeeper.asm create mode 100644 hacks/images/mac.xbm create mode 100644 hacks/images/macbomb.xbm create mode 100644 hacks/images/matrix1.xbm create mode 100644 hacks/images/matrix1.xpm create mode 100644 hacks/images/matrix1b.xbm create mode 100644 hacks/images/matrix1b.xpm create mode 100644 hacks/images/matrix2.xbm create mode 100644 hacks/images/matrix2.xpm create mode 100644 hacks/images/matrix2b.xbm create mode 100644 hacks/images/matrix2b.xpm create mode 100644 hacks/images/matrix3.xpm create mode 100644 hacks/images/molecules/adenine.pdb create mode 100644 hacks/images/molecules/adrenochrome.pdb create mode 100644 hacks/images/molecules/bucky.pdb create mode 100644 hacks/images/molecules/caffeine.pdb create mode 100644 hacks/images/molecules/chlordecone.pdb create mode 100644 hacks/images/molecules/cocaine.pdb create mode 100644 hacks/images/molecules/codeine.pdb create mode 100644 hacks/images/molecules/cyclohexane.pdb create mode 100644 hacks/images/molecules/cytosine.pdb create mode 100644 hacks/images/molecules/dna.pdb create mode 100644 hacks/images/molecules/dodecahedrane.pdb create mode 100644 hacks/images/molecules/dthc.pdb create mode 100644 hacks/images/molecules/dynamite.pdb create mode 100644 hacks/images/molecules/glycol.pdb create mode 100644 hacks/images/molecules/guanine.pdb create mode 100644 hacks/images/molecules/heroin.pdb create mode 100644 hacks/images/molecules/hexahelicene.pdb create mode 100644 hacks/images/molecules/ibuprofen.pdb create mode 100644 hacks/images/molecules/lsd.pdb create mode 100644 hacks/images/molecules/menthol.pdb create mode 100644 hacks/images/molecules/mescaline.pdb create mode 100644 hacks/images/molecules/methamphetamine.pdb create mode 100644 hacks/images/molecules/morphine.pdb create mode 100644 hacks/images/molecules/nicotine.pdb create mode 100644 hacks/images/molecules/novocaine.pdb create mode 100644 hacks/images/molecules/olestra.pdb create mode 100644 hacks/images/molecules/penicillin.pdb create mode 100644 hacks/images/molecules/sarin.pdb create mode 100644 hacks/images/molecules/strychnine.pdb create mode 100644 hacks/images/molecules/sucrose.pdb create mode 100644 hacks/images/molecules/thalidomide.pdb create mode 100644 hacks/images/molecules/thymine.pdb create mode 100644 hacks/images/molecules/viagra.pdb create mode 100644 hacks/images/molecules/vitaminb6.pdb create mode 100644 hacks/images/molecules/vitaminc.pdb create mode 100644 hacks/images/molecules/vx.pdb create mode 100644 hacks/images/noseguy/nose-f1.xbm create mode 100644 hacks/images/noseguy/nose-f1.xpm create mode 100644 hacks/images/noseguy/nose-f2.xbm create mode 100644 hacks/images/noseguy/nose-f2.xpm create mode 100644 hacks/images/noseguy/nose-f3.xbm create mode 100644 hacks/images/noseguy/nose-f3.xpm create mode 100644 hacks/images/noseguy/nose-f4.xbm create mode 100644 hacks/images/noseguy/nose-f4.xpm create mode 100644 hacks/images/noseguy/nose-l1.xbm create mode 100644 hacks/images/noseguy/nose-l1.xpm create mode 100644 hacks/images/noseguy/nose-l2.xbm create mode 100644 hacks/images/noseguy/nose-l2.xpm create mode 100644 hacks/images/noseguy/nose-r1.xbm create mode 100644 hacks/images/noseguy/nose-r1.xpm create mode 100644 hacks/images/noseguy/nose-r2.xbm create mode 100644 hacks/images/noseguy/nose-r2.xpm create mode 100644 hacks/images/osx_10_2.xpm create mode 100644 hacks/images/osx_10_3.xpm create mode 100644 hacks/images/pacman/eyes-d.xpm create mode 100644 hacks/images/pacman/eyes-l.xpm create mode 100644 hacks/images/pacman/eyes-r.xpm create mode 100644 hacks/images/pacman/eyes-u.xpm create mode 100644 hacks/images/pacman/ghost-d1.xpm create mode 100644 hacks/images/pacman/ghost-d2.xpm create mode 100644 hacks/images/pacman/ghost-l1.xpm create mode 100644 hacks/images/pacman/ghost-l2.xpm create mode 100644 hacks/images/pacman/ghost-mask.xpm create mode 100644 hacks/images/pacman/ghost-r1.xpm create mode 100644 hacks/images/pacman/ghost-r2.xpm create mode 100644 hacks/images/pacman/ghost-s1.xpm create mode 100644 hacks/images/pacman/ghost-s2.xpm create mode 100644 hacks/images/pacman/ghost-sf1.xpm create mode 100644 hacks/images/pacman/ghost-sf2.xpm create mode 100644 hacks/images/pacman/ghost-u1.xpm create mode 100644 hacks/images/pacman/ghost-u2.xpm create mode 100644 hacks/images/pacman/pacman-0.xpm create mode 100644 hacks/images/pacman/pacman-d1.xpm create mode 100644 hacks/images/pacman/pacman-d2.xpm create mode 100644 hacks/images/pacman/pacman-ds1.xpm create mode 100644 hacks/images/pacman/pacman-ds2.xpm create mode 100644 hacks/images/pacman/pacman-ds3.xpm create mode 100644 hacks/images/pacman/pacman-ds4.xpm create mode 100644 hacks/images/pacman/pacman-ds5.xpm create mode 100644 hacks/images/pacman/pacman-ds6.xpm create mode 100644 hacks/images/pacman/pacman-ds7.xpm create mode 100644 hacks/images/pacman/pacman-ds8.xpm create mode 100644 hacks/images/pacman/pacman-l1.xpm create mode 100644 hacks/images/pacman/pacman-l2.xpm create mode 100644 hacks/images/pacman/pacman-r1.xpm create mode 100644 hacks/images/pacman/pacman-r2.xpm create mode 100644 hacks/images/pacman/pacman-u1.xpm create mode 100644 hacks/images/pacman/pacman-u2.xpm create mode 100644 hacks/images/sball-bg.xpm create mode 100644 hacks/images/sball.xpm create mode 100644 hacks/images/scales.xpm create mode 100644 hacks/images/sea-texture.xpm create mode 100644 hacks/images/som.xbm create mode 100644 hacks/images/timetunnel0.xpm create mode 100644 hacks/images/timetunnel1.xpm create mode 100644 hacks/images/timetunnel2.xpm create mode 100644 hacks/images/toast.xpm create mode 100644 hacks/images/tree.xpm create mode 100644 hacks/images/tunnel0.xpm create mode 100644 hacks/images/tunnel1.xpm create mode 100644 hacks/images/tunnel2.xpm create mode 100644 hacks/images/tunnel3.xpm create mode 100644 hacks/images/tunnel4.xpm create mode 100644 hacks/images/tunnel5.xpm create mode 100644 hacks/images/tunnelstar.xpm create mode 100644 hacks/imsmap.c create mode 100644 hacks/imsmap.man create mode 100644 hacks/interaggregate.c create mode 100644 hacks/interaggregate.man create mode 100644 hacks/interference.c create mode 100644 hacks/interference.man create mode 100644 hacks/intermomentary.c create mode 100644 hacks/intermomentary.man create mode 100644 hacks/juggle.c create mode 100644 hacks/juggle.man create mode 100644 hacks/julia.c create mode 100644 hacks/julia.man create mode 100644 hacks/kaleidescope.c create mode 100644 hacks/kaleidescope.man create mode 100644 hacks/kumppa.c create mode 100644 hacks/kumppa.man create mode 100644 hacks/laser.c create mode 100644 hacks/laser.man create mode 100644 hacks/lcdscrub.c create mode 100644 hacks/lcdscrub.man create mode 100644 hacks/lightning.c create mode 100644 hacks/lightning.man create mode 100644 hacks/link_axp.com create mode 100644 hacks/link_decc.com create mode 100644 hacks/lisa.c create mode 100644 hacks/lisa.man create mode 100644 hacks/lissie.c create mode 100644 hacks/lissie.man create mode 100755 hacks/ljlatest create mode 100644 hacks/ljlatest.man create mode 100644 hacks/lmorph.c create mode 100644 hacks/lmorph.man create mode 100644 hacks/loop.c create mode 100644 hacks/loop.man create mode 100644 hacks/m6502.c create mode 100755 hacks/m6502.sh create mode 100644 hacks/maze.c create mode 100644 hacks/maze.man create mode 100644 hacks/memscroller.c create mode 100644 hacks/memscroller.man create mode 100644 hacks/metaballs.c create mode 100644 hacks/metaballs.man create mode 100644 hacks/moire.c create mode 100644 hacks/moire.man create mode 100644 hacks/moire2.c create mode 100644 hacks/moire2.man create mode 100644 hacks/mountain.c create mode 100644 hacks/mountain.man create mode 100644 hacks/munch.c create mode 100644 hacks/munch.man create mode 100755 hacks/munge-ad.pl create mode 100644 hacks/nerverot.c create mode 100644 hacks/nerverot.man create mode 100644 hacks/noseguy.c create mode 100644 hacks/noseguy.man create mode 100644 hacks/pacman.c create mode 100644 hacks/pacman.h create mode 100644 hacks/pacman.man create mode 100644 hacks/pacman_ai.c create mode 100644 hacks/pacman_ai.h create mode 100644 hacks/pacman_level.c create mode 100644 hacks/pacman_level.h create mode 100644 hacks/pedal.c create mode 100644 hacks/pedal.man create mode 100644 hacks/penetrate.c create mode 100644 hacks/penetrate.man create mode 100644 hacks/penrose.c create mode 100644 hacks/penrose.man create mode 100644 hacks/petri.c create mode 100644 hacks/petri.man create mode 100644 hacks/phosphor.c create mode 100644 hacks/phosphor.man create mode 100644 hacks/piecewise.c create mode 100644 hacks/piecewise.man create mode 100644 hacks/polyominoes.c create mode 100644 hacks/polyominoes.man create mode 100644 hacks/pong.c create mode 100644 hacks/pong.man create mode 100644 hacks/popsquares.c create mode 100644 hacks/pyro.c create mode 100644 hacks/pyro.man create mode 100644 hacks/qix.c create mode 100644 hacks/qix.man create mode 100644 hacks/rd-bomb.c create mode 100644 hacks/rd-bomb.man create mode 100644 hacks/ripples.c create mode 100644 hacks/ripples.man create mode 100644 hacks/rocks.c create mode 100644 hacks/rocks.man create mode 100644 hacks/rorschach.c create mode 100644 hacks/rorschach.man create mode 100644 hacks/rotor.c create mode 100644 hacks/rotor.man create mode 100644 hacks/rotzoomer.c create mode 100644 hacks/rotzoomer.man create mode 100644 hacks/screenhack.c create mode 100644 hacks/screenhack.h create mode 100644 hacks/screenhackI.h create mode 100644 hacks/shadebobs.c create mode 100644 hacks/shadebobs.man create mode 100644 hacks/sierpinski.c create mode 100644 hacks/sierpinski.man create mode 100644 hacks/slidescreen.c create mode 100644 hacks/slidescreen.man create mode 100644 hacks/slip.c create mode 100644 hacks/slip.man create mode 100644 hacks/speedmine.c create mode 100644 hacks/speedmine.man create mode 100644 hacks/sphere.c create mode 100644 hacks/sphere.man create mode 100644 hacks/spiral.c create mode 100644 hacks/spiral.man create mode 100644 hacks/spotlight.c create mode 100644 hacks/spotlight.man create mode 100644 hacks/squiral.c create mode 100644 hacks/squiral.man create mode 100644 hacks/starfish.c create mode 100644 hacks/starfish.man create mode 100644 hacks/strange.c create mode 100644 hacks/strange.man create mode 100644 hacks/substrate.c create mode 100644 hacks/substrate.man create mode 100644 hacks/swirl.c create mode 100644 hacks/swirl.man create mode 100644 hacks/t3d.c create mode 100644 hacks/t3d.man create mode 100644 hacks/thornbird.c create mode 100644 hacks/thornbird.man create mode 100644 hacks/triangle.c create mode 100644 hacks/triangle.man create mode 100644 hacks/truchet.c create mode 100644 hacks/truchet.man create mode 100644 hacks/twang.c create mode 100644 hacks/twang.man create mode 100644 hacks/vermiculate.c create mode 100644 hacks/vermiculate.man create mode 100755 hacks/vidwhacker create mode 100644 hacks/vidwhacker.man create mode 100644 hacks/vines.c create mode 100644 hacks/vines.man create mode 100644 hacks/vms_axp.opt create mode 100644 hacks/vms_axp_12.opt create mode 100644 hacks/vms_decc.opt create mode 100644 hacks/vms_decc_12.opt create mode 100644 hacks/wander.c create mode 100644 hacks/wander.man create mode 100755 hacks/webcollage create mode 100644 hacks/webcollage-cocoa.m create mode 100644 hacks/webcollage-helper-cocoa.m create mode 100644 hacks/webcollage-helper.c create mode 100644 hacks/webcollage.man create mode 100644 hacks/whirlwindwarp.c create mode 100644 hacks/whirlwindwarp.man create mode 100644 hacks/whirlygig.c create mode 100644 hacks/whirlygig.man create mode 100644 hacks/worm.c create mode 100644 hacks/worm.man create mode 100644 hacks/wormhole.c create mode 100644 hacks/wormhole.man create mode 100644 hacks/xanalogtv.c create mode 100644 hacks/xanalogtv.man create mode 100644 hacks/xflame.c create mode 100644 hacks/xflame.man create mode 100644 hacks/xjack.c create mode 100644 hacks/xjack.man create mode 100644 hacks/xlockmore.c create mode 100644 hacks/xlockmore.h create mode 100644 hacks/xlockmoreI.h create mode 100644 hacks/xlyap.c create mode 100644 hacks/xlyap.man create mode 100644 hacks/xmatrix.c create mode 100644 hacks/xmatrix.man create mode 100755 hacks/xml2man.pl create mode 100644 hacks/xpm-pixmap.c create mode 100644 hacks/xpm-pixmap.h create mode 100644 hacks/xrayswarm.c create mode 100644 hacks/xrayswarm.man create mode 100644 hacks/xscreensaver-sgigl.c create mode 100644 hacks/xspirograph.c create mode 100644 hacks/xspirograph.man create mode 100644 hacks/xsublim.c create mode 100644 hacks/xsublim.man create mode 100644 hacks/zoom.c create mode 100644 hacks/zoom.man create mode 100644 install-sh create mode 100644 intltool-extract.in create mode 100644 intltool-merge.in create mode 100644 intltool-update.in create mode 100644 makevms.com create mode 100644 po/._ja.po create mode 100644 po/._nb.po create mode 100644 po/ChangeLog create mode 100644 po/Makefile.in.in create mode 100644 po/POTFILES.in create mode 100644 po/ca.po create mode 100644 po/da.po create mode 100644 po/de.po create mode 100644 po/es.po create mode 100644 po/et.po create mode 100644 po/fi.po create mode 100644 po/fr.po create mode 100644 po/hu.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/ko.po create mode 100644 po/nb.po create mode 100644 po/nl.po create mode 100644 po/pl.po create mode 100644 po/pt.po create mode 100644 po/pt_BR.po create mode 100644 po/ru.po create mode 100644 po/sk.po create mode 100644 po/sv.po create mode 100755 po/update.sh create mode 100644 po/vi.po create mode 100644 po/wa.po create mode 100644 po/zh_CN.po create mode 100644 po/zh_TW.po create mode 100644 setup.com create mode 100644 utils/._colors.c create mode 100644 utils/Makefile.in create mode 100644 utils/README create mode 100755 utils/ad2c create mode 100644 utils/alpha.c create mode 100644 utils/alpha.h create mode 100644 utils/colorbars.c create mode 100644 utils/colorbars.h create mode 100644 utils/colors.c create mode 100644 utils/colors.h create mode 100644 utils/compile_axp.com create mode 100644 utils/compile_decc.com create mode 100644 utils/erase.c create mode 100644 utils/erase.h create mode 100644 utils/fade.c create mode 100644 utils/fade.h create mode 100644 utils/grabclient.c create mode 100644 utils/grabscreen.c create mode 100644 utils/grabscreen.h create mode 100644 utils/hsv.c create mode 100644 utils/hsv.h create mode 100644 utils/images/logo-180.gif create mode 100644 utils/images/logo-180.xpm create mode 100644 utils/images/logo-50.gif create mode 100644 utils/images/logo-50.xpm create mode 100644 utils/images/logo-big.gif create mode 100644 utils/images/logo.eps create mode 100644 utils/images/screensaver-cmndln.png create mode 100644 utils/images/screensaver-colorselector.png create mode 100644 utils/images/screensaver-diagnostic.png create mode 100644 utils/images/screensaver-locking.png create mode 100644 utils/images/screensaver-power.png create mode 100644 utils/images/screensaver-snap.png create mode 100644 utils/logo.c create mode 100644 utils/minixpm.c create mode 100644 utils/minixpm.h create mode 100644 utils/overlay.c create mode 100644 utils/resources.c create mode 100644 utils/resources.h create mode 100644 utils/spline.c create mode 100644 utils/spline.h create mode 100644 utils/usleep.c create mode 100644 utils/usleep.h create mode 100644 utils/utils.h create mode 100644 utils/version.h create mode 100644 utils/visual-gl.c create mode 100644 utils/visual.c create mode 100644 utils/visual.h create mode 100644 utils/vms-gtod.c create mode 100644 utils/vms-gtod.h create mode 100644 utils/vms-strdup.c create mode 100644 utils/vroot.h create mode 100644 utils/xdbe.c create mode 100644 utils/xdbe.h create mode 100644 utils/xmu.c create mode 100644 utils/xmu.h create mode 100644 utils/xscreensaver-intl.h create mode 100644 utils/xshm.c create mode 100644 utils/xshm.h create mode 100644 utils/yarandom.c create mode 100644 utils/yarandom.h create mode 100644 xscreensaver.spec create mode 100644 xscreensaver.xcodeproj/project.pbxproj diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..50dbe439 --- /dev/null +++ b/INSTALL @@ -0,0 +1,183 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 00000000..cdc566bc --- /dev/null +++ b/Makefile.in @@ -0,0 +1,373 @@ +# Makefile.in --- xscreensaver, Copyright (c) 1999-2010 Jamie Zawinski. +# the `../configure' script generates `Makefile' from this file. + +@SET_MAKE@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +SHELL = /bin/sh +SUBDIRS = utils driver hacks hacks/glx po +#SUBDIRS = utils driver hacks hacks/glx +SUBDIRS2 = $(SUBDIRS) OSX +TARFILES = README README.hacking README.VMS INSTALL \ + configure configure.in Makefile.in config.h.in \ + config.h-vms install-sh setup.com config.guess aclocal.m4 \ + config.sub makevms.com \ + intltool-merge.in intltool-extract.in intltool-update.in \ + xscreensaver.spec \ + xscreensaver.xcodeproj/project.pbxproj + +TAR = tar + +MAKE_SUBDIR = for dir in $(SUBDIRS); do (cd $$dir; $(MAKE) $@) || exit 5; done +MAKE_SUBDIR2 = for dir in $(SUBDIRS2);do (cd $$dir; $(MAKE) $@) || exit 5; done + +default:: + @$(MAKE_SUBDIR) +all:: + @$(MAKE_SUBDIR) +install:: + @$(MAKE_SUBDIR) +install-program:: + @$(MAKE_SUBDIR) +install-man:: + @$(MAKE_SUBDIR) +install-strip:: + @$(MAKE_SUBDIR) +uninstall:: + @$(MAKE_SUBDIR) +uninstall-program:: + @$(MAKE_SUBDIR) +uninstall-man:: + @$(MAKE_SUBDIR) +depend:: + @$(MAKE_SUBDIR) +distdepend:: + @$(MAKE) update_spec_version + @$(MAKE_SUBDIR2) + @cd po ; $(MAKE) update-po + +TAGS:: tags +tags:: + @$(MAKE_SUBDIR) + +clean:: + @$(MAKE_SUBDIR2) + +distclean:: clean + -rm -f config.h Makefile config.status config.cache config.log TAGS *~ "#"* intltool-extract intltool-merge intltool-update + @$(MAKE_SUBDIR2) + +dist:: tar + +# This really makes me sick... +tar:: + @ \ + sh config.status ; \ + rm -f configure ; \ + $(MAKE) configure ; \ + $(MAKE) version-date distdepend ; \ + VERS=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' utils/version.h` ; \ + NAME="xscreensaver-$$VERS" ; \ + rm -rf $$NAME ; ln -s . $$NAME ; \ + FILES= ; \ + ADIR=archive/ ; \ + for subdir in $(SUBDIRS2) ; do \ + d=`pwd` ; \ + cd $$subdir ; \ + FILES="$$FILES `$(MAKE) echo_tarfiles \ + | grep -v '^.*make\[' \ + | sed \"s|^|$$subdir/|g;s| | $$subdir/|g\" \ + ` "; \ + cd $$d ; done ; \ + echo creating tar file $$ADIR$$NAME.tar.gz... ; \ + GZIP="-9v" $(TAR) -vchzf $$ADIR$$NAME.tar.gz \ + `echo $(TARFILES) $$FILES | sed "s|^|$$NAME/|g; s| | $$NAME/|g" ` ; \ + rm $$NAME + + +# This also makes me sick... +# autoconf generates a configure script that begins with a very hard to read, +# nearly impossible to customize --help blurb. This horrid set of regexps +# go through and clean up the help text, by inserting whitespace and ripping +# out options we don't use. Odds are good that this will fail with any version +# of autoconf other than the ones I've tried (2.12 and 2.13.) +# +# NOTE: we now require autoconf 2.63 or earlier, because later versions have +# the "Expanded-Before-Required" change and I can't make any sense of it. +# If someone wants to send me a patch to make configure.in work with 2.64 +# or later, feel free. Personally, I can't be bothered. +# +configure:: + autoconf263 + autoheader263 + @TMP=configure.$$$$ ; \ + echo "munging configure's --help message..." ; \ + ( perl -e ' \ + my $$file=""; \ + while (<>) { $$file .= $$_; } \ + $$_ = $$file; \ + \ + s/^(Configuration:)$$/\n$$1\n/m; \ + s/^(Directory and file names:)$$/\n$$1\n/m; \ + s/^ --sbindir=.*\n//m; \ + s/^ --sysconfdir.*\n//m; \ + s/^ --sharedstatedir.*\n.*\n//m; \ + s/^ --localstatedir.*\n//m; \ + s/^ --infodir.*\n//m; \ + s/^(Host type:)$$/\n$$1\n/m; \ + s/\nFeatures and packages:\n.*library files are in DIR\n/\n/s;\ + s/--enable and --with options recognized://m; \ + s/\n --with-x .*?(["\n])/$$1/s; \ + s/\n(Installation options:\n)/$$1/s; \ + \ + s/^ --oldincludedir=.*$$/ \ + --x-includes=DIR X include files are in DIR\n \ + --x-libraries=DIR X library files are in DIR/m; \ + \ + s@mandir=.\$${prefix}/man.@mandir=\\\$${datadir}/man@; \ + \ + s@rm -f conftest@rm -rf conftest@g; \ + \ + print;' \ + < configure \ + > $$TMP && \ + cat $$TMP > configure ) ; \ + rm -f $$TMP + +bump-version:: + @ \ + SRC=utils/version.h ; \ + VERS=`sed -n 's/[^0-9]*\([0-9]\)\.\([0-9][^. ]*\).*/\1 \2/p' $$SRC` ; \ + set - $$VERS ; \ + MAJOR="$$1"; MINOR="$$2"; \ + NEW=`echo $$MINOR + 1 | bc` ; \ + NEW=`echo $$NEW | sed 's/^\([0-9]\)$$/0\1/'` ; \ + D=`date '+%d-%b-%Y'`; \ + ADIR=archive/ ; \ + if [ ! -f $${ADIR}xscreensaver-$$MAJOR.$$MINOR.tar.gz ]; then \ + echo "WARNING: $${ADIR}xscreensaver-$$MAJOR.$$MINOR.tar.gz does not exist.";\ + fi ; \ + if [ -f $${ADIR}xscreensaver-$$MAJOR.$$NEW.tar.gz ]; then \ + echo "WARNING: $${ADIR}xscreensaver-$$MAJOR.$$NEW.tar.gz already exists.";\ + fi ; \ + /bin/echo -n "Bumping $$MAJOR.$$MINOR to $$MAJOR.$$NEW ($$D), ok? "; \ + read line; \ + if [ "x$$line" != "xyes" -a "x$$line" != "xy" ]; then \ + exit 1 ; \ + fi ; \ + TMP=/tmp/bv.$$ ; \ + sed -e "s/\([0-9]\.[0-9][0-9]*\)/$$MAJOR.$$NEW/" \ + -e "s/\(([0-9][0-9]*-[A-Za-z][a-z][a-z]-[0-9][0-9][0-9]*\))/($$D)/" \ + $$SRC > $$TMP ; \ + /bin/echo -n "New version and date are "; \ + sed -n "s/[^0-9]*\([0-9]\.[0-9][0-9]*\) (\([-A-Za-z0-9]*\)).*/\1, \2./p" \ + $$TMP; \ + cat $$TMP > $$SRC ; \ + rm -f $$TMP; \ + echo "overwrote $$SRC"; \ + ls -lFd $$SRC + +bump_version:: bump-version +tick-version:: bump-version +tick_version:: bump-version + +version-date:: + @ \ + SRC=utils/version.h ; \ + D=`date '+%d-%b-%Y'`; \ + TMP=/tmp/bv.$$ ; \ + sed -e "s/([0-9][^()]*)/($$D)/" < $$SRC > $$TMP ; \ + /bin/echo -n "Updating date in $$SRC to \"$$D\"... " ; \ + if cmp -s $$SRC $$TMP ; then \ + echo "unchanged." ; \ + else \ + cat $$TMP > $$SRC ; \ + echo "done." ; \ + fi ; \ + rm -f $$TMP + + +update_spec_version:: + @S=$(srcdir)/xscreensaver.spec ; \ + U=$(srcdir)/utils/version.h ; \ + VERS=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' < $$U` ; \ + /bin/echo -n "Updating $$S to \"$$VERS\"... " ; \ + T=/tmp/xs.$$$$ ; \ + sed "s/^\(%define.version[^0-9]*\)\(.*\)/\1$$VERS/" \ + < $$S > $$T ; \ + if cmp -s $$S $$T ; then \ + echo "unchanged." ; \ + else \ + cat $$T > $$S ; \ + echo "done." ; \ + fi ; \ + rm $$T + +rpm:: + @ \ + VERS=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' utils/version.h` ; \ + DIR=`pwd`/rpm_build ; \ + ARCH=`rpm --showrc | sed -n 's/^build arch *: //p'` ; \ + ADIR=archive/ ; \ + TGZ=xscreensaver-$$VERS.tar.gz ; \ + if [ ! -f $${ADIR}$$TGZ ]; then \ + echo "$${ADIR}$$TGZ does not exist! Did you forget to \`make tar'?" ; \ + exit 1 ; \ + fi ; \ + rm -rf /var/tmp/xscreensaver-$$VERS-root ; \ + rm -rf $$DIR ; \ + mkdir $$DIR ; \ + ( cd $$DIR; mkdir BUILD RPMS RPMS/$$ARCH SOURCES SPECS SRPMS ) ; \ + cp -p $${ADIR}$$TGZ $$DIR/SOURCES/ ; \ + rpmbuild --define "_topdir $$DIR" \ + --define "USE_GL yes" \ + -v -ba xscreensaver.spec ; \ + echo '' ; \ + echo 'RPM build complete' ; \ + echo '' ; \ + rm -f $$DIR/$$TGZ ; \ + rm -rf $$DIR/BUILD/xscreensaver-$$VERS ; \ + mv $$DIR/SRPMS/xscreensaver*-$$VERS-*.rpm . ; \ + mv $$DIR/RPMS/$$ARCH/xscreensaver*-$$VERS-*.rpm . ; \ + rm -rf $$DIR ; \ + echo '' ; \ + ls -lFG xscreensaver*-$$VERS-*.rpm + +test-tar:: + @ \ + VERS=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' utils/version.h` ; \ + D=xscreensaver-$$VERS ; \ + ADIR=archive/ ; \ + NAME="$${ADIR}$$D.tar.gz" ; \ + if [ ! -f $$NAME ]; then \ + echo "$$NAME does not exist! Did you forget to \`make tar'?" ; \ + exit 1 ; \ + fi ; \ + \ + set -e ; \ + set -x ; \ + \ + if [ -d $$D ]; then \ + chmod -R u+w $$D ; \ + fi ; \ + rm -rf $$D ; \ + zcat $${ADIR}$$D.tar.gz | tar -xf - ; \ + cd $$D ; \ + chmod -R a-w . ; \ + chmod u+w . ; \ + mkdir BIN ; \ + mkdir BIN/motif ; \ + mkdir BIN/lesstif ; \ + chmod a-w . ; \ + \ + ( cd BIN/motif ; \ + CC=cc ; \ + export CC ; \ + ../../configure --without-xpm --without-xdbe --without-xshm \ + --with-motif=/usr/local/motif ; \ + echo --------------------------------------------------------------- ; \ + gmake all ; \ + ( cd driver; gmake tests ) ; \ + echo --------------------------------------------------------------- ); \ + \ + ( cd BIN/lesstif ; \ + CC=cc ; \ + export CC ; \ + ../../configure --with-motif=/usr/local/lesstif --without-gnome ; \ + echo --------------------------------------------------------------- ; \ + ( cd utils; gmake all ) ; \ + ( cd driver; gmake all ) ; \ + echo --------------------------------------------------------------- ); \ + \ + chmod -R u+w . + +dmg:: + cd OSX ; $(MAKE) release dmg + +www:: + @ \ + DEST=$$HOME/www/xscreensaver ; \ + VERS=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' utils/version.h` ; \ + HEAD="xscreensaver-$$VERS" ; \ + ADIR=archive/ ; \ + BNAME="$$HEAD.tar.gz" ; \ + NAME="$$ADIR$$BNAME" ; \ + DNAME="$$DEST/$$HEAD.tar.gz" ; \ + BNAME2="$$HEAD.dmg" ; \ + NAME2="$$ADIR$$BNAME2" ; \ + DNAME2="$$DEST/$$HEAD.dmg" ; \ + \ + if [ ! -f $$NAME ]; then \ + echo "$$NAME does not exist! Did you forget to \`make tar'?" ; \ + exit 1 ; \ + fi ; \ + if [ ! -f $$NAME2 ]; then \ + echo "$$NAME2 does not exist! Did you forget to \`make dmg'?" ; \ + exit 1 ; \ + fi ; \ + chmod a-w $$NAME ; \ + if [ -f $$DNAME ]; then \ + /bin/echo -n "WARNING: $$DNAME already exists! Overwrite? "; \ + read line; \ + if [ "x$$line" != "xyes" -a "x$$line" != "xy" ]; then \ + exit 1 ; \ + fi ; \ + fi ; \ + if [ -f $$DNAME2 ]; then \ + /bin/echo -n "WARNING: $$DNAME2 already exists! Overwrite? "; \ + read line; \ + if [ "x$$line" != "xyes" -a "x$$line" != "xy" ]; then \ + exit 1 ; \ + fi ; \ + fi ; \ + cp -p $$NAME $$DNAME ; \ + cp -p $$NAME2 $$DNAME2 ; \ + chmod u+w $$DNAME $$DNAME2 ; \ + cd $$DEST ; \ + \ + TMP=/tmp/xd.$$$$ ; \ + sed "s/xscreensaver-5\.[0-9][0-9ab]*/$$HEAD/g" download.html > $$TMP ; \ + echo '' ; \ + diff -U0 download.html $$TMP ; \ + echo '' ; \ + \ + for EXT in tar.gz dmg ; do \ + OLDEST=`ls xscreensaver*.$$EXT | head -n 1` ; \ + /bin/echo -n "Delete $$DEST/$$OLDEST? "; \ + read line; \ + if [ "x$$line" = "xyes" -o "x$$line" = "xy" ]; then \ + set -x ; \ + rm $$OLDEST ; \ + cvs remove $$OLDEST ; \ + set +x ; \ + fi ; \ + done ; \ + set -x ; \ + cvs add -kb $$BNAME $$BNAME2 ; \ + cat $$TMP > download.html ; \ + rm -f $$TMP ; \ + \ + (cd ..; $(MAKE) xscreensaver/changelog.html \ + xscreensaver/screenshots/index.html ); \ + cvs diff -U0 changelog.html ; \ + set +x ; \ + \ + /bin/echo -n "Ok? "; \ + read line; \ + if [ "x$$line" != "xyes" -a "x$$line" != "xy" ]; then \ + exit 1 ; \ + fi ; \ + \ + cvs commit -m "$$VERS" + + +count:: + @ \ + /bin/echo -n "Current hack count: " ; \ + ( ( cd hacks; make -s INSTALL=true install-program install-scripts ) ; \ + ( cd hacks/glx; make -s INSTALL=true install-program ) ) | \ + grep true | \ + grep -v helper | \ + grep -v ljlatest | \ + wc -l diff --git a/OSX/._InvertedSlider.m b/OSX/._InvertedSlider.m new file mode 100644 index 0000000000000000000000000000000000000000..4b2da8efd75b26cd10763d05b1b586c81ba57ebf GIT binary patch literal 173 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aWxQw_-lcf2;dkJ62zeN;|owJNXHx? z7DUs=h!7V~&d=3LEGWoH)eA|jC~?h8&QHnAO9v__&&*57FE7?LG%_`^HZ-y@006&~ B7r+1j literal 0 HcmV?d00001 diff --git a/OSX/._XScreenSaver.icns b/OSX/._XScreenSaver.icns new file mode 100644 index 0000000000000000000000000000000000000000..d28a0afd23fdf63259eab051a901ac3c9b925491 GIT binary patch literal 39555 zcmeI5cU)7~`^S?o#j15xAdq`W$ev6=+#7J!QTM3YVTY|%oNc`~3AksSbydZ>_XHIb z6$B@WTMh7Cz>2EXUrfa0w4_5I^V3=-mfKIb{ldCoaE_kAB^IDFy6U@;iozaEA6 z?1K-$s~+e*MjiO_g)bbw(9gXYjK6*0JN>Y)eo_6MsD8nO7b`0nj1}-j+gJa?|C8@4 zOk+lk9S{9CjhWyF@f(njI$jBX<}t{OsQc=4#y#S0ojbR0-@0}4=8fyuiEB;Mu3o*8 zntJ)-g_N^rPR7T@96Att-s$3L1~cP9WQP6y4EUo?cf5D+?p-h=Zh0WHYuD0fv&)w* zrX-z7h(8{4aNq772MTs2FlIhrWjuI5WDxfm>BK$O-8*;4+a9*YkkOSZsh2OEKbv?e z{#eYReY>`9PaJ-d!F>2I@&V%^`JZM<3Gv5{9Ne>G^Oh6R6Abbp^T9*n0SSi8j5joR z=~7B^VnW=}!~1q^i{6@`if3rS@`2iBN8TsW?NINC+YZ%1X^eVz>B70Qi6@W89N4{W z^M?Hi@;Jua|FA$YJbmKm;k`Tl-LT>KNy%}>Jg{J7sGn=%1|4_0iHi`vgt(Y}JOACV zX2;nRl4FedndC#p12ThAZ9?8=Bd>0tm|wk`dg(&)nS`ThqM`}Jv?J9nPtP$S5o3(2RB9o(^T)slHPz#`7;2xIY63zP{sWJ0wC>dKW% z=Mv)&@7lC#$(-cJw8de@UysO428^hEMh19h%O2vkdsS!Bu3o;7ln}G$-!*^DjnmOK zoI{KyZ?J)cOT7es?c1_;$?W}QHXHUq#?mSqTbOZMYSUwXE$zys^NI0?w*9ko_7}V=`!}ze zKcB2&vxl(~+NrW3Vcs-CIdJFBt(!N}E}uVh?7)^a3!~J}47_$Tm|0noj~Q7D9y>gG zlzHu&^Qc?MvLS7G`_|2CSI#HJ9o)8dQKY7Z;VuS|t<7R&FU)dyd^=M4(<6c&UItix zjx1prf_|Mnd1(8(zoN7?40kd#*+iB)dp;PdTX1&QFl+#ZkVx0BUQ9l5WXC^CW<}Pp z+rfy;W@JTXv$C?XXJt7g&2Ao;R%J_MG=Q9fHg8_LeD2hdUF(<5ih5=#+|F2<9hK#< z@Gv$!^Qnaw7~H#a>w0R+shC|Gm(L+4jvcEe2y*s@-SMfJXkrMrF&5{rva@rdvK`lD zEq0yv_%T5byY~agVbsOzspn50-5b4fzINL9pT>MUdh|EnjrsZ435$0mgY{O%vYg0l zr%SucBRykknu4QuZeF>V2&1=p@yzLy$B+H#haZ0YY3#W16Q@qstcyD#-@;(z{+&Z+ zJI%A)4+*|mZQSGmOr3joZ(X~TeB#iK^~>i+O`kex0x|xx2@@tx`Tb8)J8NEaoN6;e zle;p_`xr`h#cX1AX^R3b{jbv?B)?-MENi%qRA7rH8yL02}g)?ynw@0sDx@g|) zS+i!UXVsZKcmBdfOO`L+ko+%WcHZJ#uej6YWf#8>ypau!FB=s+I5QJW@7_$i4D({& zwoU6+Ehm=}%ZR0QmaSO6eATKoYj);sVl2p;m&;8)sMqPXennM-Lx@$G5IsKA(8<*pdBv_w3xYbJy-&yLa!{v3=W?t(!M*`S;%#`)CG{zbJ2J zuGfWqdR_T}nH?VG#2z1o4F%sHWrDyR@cVqy=@Z9~#;Ri)#Ky)PIdpLUzCF8lfy%C% z(F{#Kk;lx>pOY7r>wO}jy!h5Xoi^s?%qSW&coKx}aaG^1!9smD@${*LQwij$dI={^ z#2r5hLi_ga-WwOqh5>KBz5qC89@PYk@q8T%uur7}dSrq)a-?wF@sawC~h4_ss&p7&> zs09(M4I-P*46N68*TwVa&Yn31dwT34Fo}9%_b80VUnpy+!1n4(x39*PQtnR&T;ADh4bgnol7B-8=Xx`I-Ag$x8-_T>Sfq=;gsTNG=nTq>sZOW z9#OiPPd=adsHAk)VZXHe+@(fcam@4|h&)Cvk;BL)9(%wrK0tY&o(|jio!f(XTVRX5 z3c-MF|3oxHTi~G6rSQV@XM%4H=iVzXx^r;O(ER*krz&)1C+7AdX(fA zj2w7pJBNo)(@Z6&kdui?O(On0hU5nEc0u;o)Gl9ykQb>7 zosd{zVY0u5*BF;=_r={egMIP!gc;3vT6c!HE@I1J9;$+umQ;fQTg2IeL>SjDlaigRF`cr=BTQ&%Eg#};`Z#P=oQBOeO zsX$TDa1F1i`Jsv)f|^$cQ)I+-p7Wc;R?C2|d%5-Tq0m z`_R22wa&5d$@;I`J~n9#cE;4^9Hf?=l?7_B>Q#An4}x9hc!}4&q^xuV$!ms7ubATZ zw!OBeM2E&mrz=nGr(73PuF*M^q_6A~S{zx)jA%yk8gaYj<>n0G9e~cF6&z-7`ZK9A zkaA)7Dk@8f>yUW(exX+9s6W#_ka$?E(b<<@+1O(;$V_Sm4t2}R%S9^m4n)g9_sYw) zRX!o1`pu>ri*Cexv*^yDa_|U(RHs0Hq>J}rlo6!Pz9RN-|A*R2=0wnAbR+WW4dESx zZqjQz_yq}FPCS((c%O0Ey9@=ndsl>6H|BwuO95yWmT&4Hy;%z073OZ~HMdI5xm$i- z-ca7b99R$>Z2g2jmOEZX;VK(J@ap5@%VoN<1MvYj&z&gK6+o1VbLRU*7`4#N5MY@u!hkuBuTuT&aArF!#$*z_mhw|q6 zrq+PIZ&9I6SF&ZFPi|6D8OjGVjQRg|8E+tUj_^ap z!GL8?)pWWiLwSc`2y$~^JBFPMRwI36O+DAqRt)T z)&gD0z0eUz&lU2pI~)pn`FTXHgG~@-ke;leSHIaW2INuMt}U|bCr+0|>Ku#jFY{Yt z09pHjvhn^UYFotWbT2H>4dWfjM`HH07|bHQb4A6K>b^E;_LClI6+PPYTaU7Jh2ReZ z^1-nzDSRe00u$S~#`R*Ao=f+l!UE8HLJQg4DL9W z$4v3RxU*PS5UH~(E<))Gy1K%=<`Gq5jP68{Gl&)FY--FL5UX;HmU7@?NA<|isi1gs zho2AKEkZq=3jwPb*3C**Wj?6XK`PZUjGSl&OK&h}o;pSH8Z;Ygpl5YKaW0{lf1z{^ z9Wwg@;|zI8m5^ii;-aEqyjWOZbc$q*O>0Fkr`JL7LG~GC_e@9Yc!|JQprZx{~7IyrYFxdd&PhkSmX*rikV+gtqNrMfk^jsikQk}qv6^WN1lnD6Whtq}q)h_(L+b2GZgiMjL&0u-5MzNDQOrb+u^{fCRz@1h z>b`-V6BkR~lwFA8gC-%f09G56@Ajn^=Z97hwk&Y$URqKDN@Ov!2$YyQT1aDbHW;q* zdY~S{?!)!Cac646q z?oNa)fl)9Ek`N6QS_<+v59(7|L&YAcfQ&uNm4X6LDXCI18fh;)FIVdSz7P9M6zv5A zL{O~YS)SCnZT_>^l){i5hklX8c1Q}8ifby>=YmpY<_aC0q9A*X&JKNxgr&1|fY%k6 zWs_lNaO_c5S_(QuDFcL9AOsstF{QP23|!Lim~IQ;qj~zSGTIAphfx)4JJTA@33Vl_ zq8l5wm>xtK4Ayay7J?XoKSd-())1=a<^4S|UDIo&2`NF4=y5Wnh?_@}(^-G^;Am_r zBY4L(ph6TgkVv71ayA;%Ak5&6mdEsRC7Rj0d;M#o2U-mxu)XN-wq<*{s+%#`+G>qB zZK)m1jAA4aNjZXm0R(3A>^b-*Qz6P0l*N#7##>$8=FwL6BY1ITWtvhJ*nwF=l^I2i zoC9+RUsPI-G8HX2aMovv4{@6h@hgX&iWf(gsY~I+>N%^g=klC!x38}nA#%_dQQ%Nw z%$~((FF^ZoISZ`RV8wXeNIj3|%ypIjexcZCtRTvq%F7Dwt#0dzFXS9p3$Wj<2 zok>eMp|4ADLZ6D zP|Tu(QPJ9sjW%h$*v(vV0blm@^!At+kEdEUZv$&rRt4Q!JOL*i4BA#>a-gVdU^^Gm zgE4`GjQ8@wJ=vZlk0sb{n5mDduq%hw)X*AgE7D{KM;jMVgpu+0d>&JAnlMDcLD2Dd zO}V<}F@$Lz#WXHi$Yc7%5{4OiEqzcVoS@VY(wJsZ%rnCl^H}`f?~ov+u0WlusE91L zIat+Tlu1Le4BG}RZTjS8}=ZMB&&vC??G=0i_r8D|=_de%r@ zTx12Syc~_MF4AnDE>Nx6y>tg-d(@5@+vjd`*uFlHmjdyqN&qv{G$oE__f4rDPBj>G z{zaLYhOOjp&Jj~4{x<%%h$)kZ@pivWm@+{#u(oY9K`P94c67UTB(2%>c)u;KalB9${`knapO2GF!|hg9ZM! zsLg6q1GC9oTv}!{nG8i`WfWzKq*z8h3?h@F|1uJWI+Vd+EG#Z5McXK=LXXy-Lc-!d z_O`{WU12eA3Xy3{7Gs+BAHCW9=#O4|^k(A}aX`RA^PSeR7M=1+Mr50e-=8rV{|ZvB zA}RKPc#1kO&uA=Jly4-(0f93N`XhbD+%_2W)zSeY$6Ja7hb z)T{Zi@0Oa&6;p2A%Qf%yI&mjMufrCa^er}-4mO=KN1j7a&A#qzpjIo&sf$>izWFaV z?q(UUdd%1pmP;7uB^=!Lj0-SclRUAk7nfQF#9Ib?XQs`f48DiVSNfVN{9{QoYhOzP zVdm&0oehT8t4-6J8>tSLO;mY1@e?XhkVS28RiO{rMj9Qq2b3FzCfJm{3awU4q5S;( zerC%?{|d|9B6Gjt7KpLl_mJgw7plVl08(yI0m^wY?6$G(Jd>s!WlAeHnX<+1XDOZh z0ySnBr4QKz%Kyqs4E=Y2GTOKb32Pn84{a4YESB}ka>Bwg^_osv>gausSkl`V_0L>( z$>cU!LbaV^QU@EHViZC1IY3U%6ve0qg-!(Djqq+Ac($ZEREDi8HV5w;Bq3V@o zIqWJ3oMnD27%z$onPblDES4-ZnyBw5tQQoRHg--?W>ckY-Id)kjN4rUMIB0v1_SaL zlcQ0?t_;Ks{eTr^#h4{ujam9=$^d~Be$H+2hqOd9E6AmQXDl01#ozTXjR?>|V3i@t?x zlfNvMih!wR!t&WcuR9jA`Jv$9qGrw3lIF%#`%9*~UiS?0e@uDO&m&A!MXT{7Wxu0| zyDS$p(K#6kLuAKx|5~jk2*rpc*4`vxb+J_VEt%HVXsPf&L|Pyr41|UAP+E}c(PXpP zXfRV>9yIS%9y}W`!c@_|;bxPmB4i8bZjm1p?XT7)te%G8og2eU){}#b*6)z8)v{Md z`CUopKLY*rAbdbvf`nxS<-rX%o45RAGW9)Tp3sELZW>Z*`l)fU$)pci0>YbG8Nd0q zItU<~>Ybl0d}uxFS!5mgm$fHht&k*}e2*J__knOU5*9)Vf^fcNs>>Gha+$&0`>3&H z_s6N-T~ke~zqkv+EkW2}Qw!t%fKu2C=y4;geYIW9rdCU=cU|Wk;{Id(K{_v@=@m+G z$U172rNVd0{TmN02k``F7-vLu-OjO5s>+w!{Qx8lk=uM??1v36&T|b?r zY~VhNek{frTwZbj)>8=3hniJ>q5(H^DNla(a+4H%8k|q@WxEo&8jl8as&O&43nCo*MTDVJZ`OAXPpm+ z&49g(@dOdkntzBYMyk$;d?!Y#$#9|pDO~eWr`zAV3zt`j+s-$E@z9WzNNWt&IWNOy zI&?+zc+8>wyLVPWQlP5{pu7_!)jj}Fs(v%;L~!+aN=hI~O4d@+7r5$+#Iu%?)>6`s zN=Zk)JtbuTdaRTIXWDonvUQq%e! zSZRN@K+@tpSn2W{m+YKBcy21yXt>O2qorFC-;tI^^W8i<$=BGf;6_AZdmaF6_%B*I zH*X=(QZCz7-q)X|rAvkd4yYoAZ$V2_0^HedL%I-%miFw9?LybuftFrJv@{mc(v7dD zrD<*4+08NBNPy^}gKsB0F_>)CKbri|2_NiKRjr zxG@fsiN$hP&bVG2u|_ms*g(X?{9j2>u@+SX_12|cm>V7u3Iuf;*WImOzliJC(#WfI zssQS_loT?Vah6D`pU?r@{4zg1i-ph7&3N+pc!8U1$LUJor`nPI_ApHrut2IuQ8ja1Oz?^B5f* z^E!UoAKQKx-FfmHKTW|x5kI}Cour=PKDlqGXxIpzJDctH?azv-QzrYdJv`X1;XT8r zs3((?)Dww_CgXpn`DuS_=S%$5B?S*f{L}}-Z~}AVcn9z~T)8{j!;L>}m>Y)iRBp&! z-;oQjrZ|QP_W-x*kG%|0>zo6p3Jb@A42d=2NPN6FVsCd3wwqx3S0IC9Y!B$F=Wu_I zxflZuY1kJgHxhb*rGgBw)Il1|iQ}!1aX4~M!lNPE{d@3*BlH46wufuGe(sSVqNcwZ zc7kOBgsNd^HB&`GMMXn2SW{1_P{84dp-&K(UO&2H9Dx^`O?cS5b4N-&HJ~yJYuE)i z)c|}q%~TQGea2MJ6MYv;ah_Q;n`p+ z-Fx?Jmt_XFN?rDMC$Ppi))nX;$kB9_zKzF5S1)2=Kv##6kV*oPt6)-#hhKphlJIa< zbW&|^|AhzLQ+I55`|u(lqZ1md!8hQm1F?P2z-Sd;#X+nL#OiP)+-7pE@1-?O3Fuy9 z$Vi{ymcQ8Y0e;|(Z9WZAR-0B{?m&FMZRe_@taflc7cNdZUa|?QF;^u->$i?eA4y#4 z(1Cw$!5w`9*)W)n;A-<mckvHD`sPt>&z#v&cUeo*29s_HB>K zUmM#TgphS^FAze)(w%o3(sDk0u#0E{eZfP4w1QqWX$3qDBT?>n$)-kN^*KVR8{0Kl zEx&V_2mX=l#xS^^1DcCHQ5;byeZQi4YXs_WD1fU$_`#=`H1nw(@YZ2SPJp|!-MuDt zo>Dog37UtW*))cW1ZZ-@OklJ7D6at7jA#OSVSwfaVF#aaS0JUpT`yx{z+FMFp{EMg zK(=d_sfzrwUZCJX&j7a}9mSp`Br(jgmj26;T4Op>LV>&v!VbMmUY{z~^Hh4F3FS4Z zLqz4UCIp)Wq6Ey1pl^i&cMv2z?A(MwBda`g4uwngp!hO-eX3ZW;|;L_Pg+kB+(_WK zY1nlkgxKg15j`GgDQeyV@mJL2I$^+U24ja`=C7&MeYWF>Fz}*@yZf--y($McwT&wL z?dIE7MS3uhhwgIx62hFI=n@9(1{7Z;u%HJ77Gh}U>B9vZ7g!&EmTgMH+}JS6j?im2 z9*?bt^@Qyva;FI_sMQSvf-@L9@-l%%dV2kE(Bp`_z|OH*&(v0VRgxsufFtM7#xV7|`7H5oM#A0)a%ymvZiwya9au_M){AHi zF5*}iu-PG44DkY+{T$e=t=n)6Yzjhm(xX14qoUP-PR(fVJRlNG6i^2kAF%)-TI8ft z7$8~@d1i+r1;h-cp1ntNNgK8XUa39mptnFt#fe2qFy)-XfF%vVViCY}K;W3B zvlWQWVqpLthpVwh91$O7E4Uyb=dcdrDX7D?^~>RHX(x zodfO<3SbF6l|Efn=yw<}`$#rZHKm4};}GmHg{zb_Ikx#mV2%(t?o630>L&Q( zj^-QECfHiE_<68BTMq)UiO^FR*b)|qy+CUTqq^F=5#XIY`^v;7;0D2NWXlhFIO)#Y zy6fsQy0aePQjl~E1H1{6&tqFP5D3Ss)zNd}xLwZ*YKrm8+~ctk?D9NBI{ z8OO7Ux-r95TkX^F=NPXO2ms?nX@<@lU2u%Uhs_&#fXU3n$88 z1dqQ6eL;t!hR=bM32z^?ZGokl4wAi_hfK|RY71d zr~!fDU>gKp83}_AgJXwdl2cVI7!Iw$g3+Ni7QA>N##F>0$_p$Q4ywU|(Lsm>+n*i` zYyV7}Cd+H;ZA6ZN3B%FPF=2GPjR|k*k3LaEVJz{Oa|HMbTo?|1jtirM5f`>EY>8pq zMP3eEFPKiG8jeP8yg-K0(KH#(nteO+7W4LPbu}Cw2K{;-4qw5#*MP$#@Z-ycB5Y4#@p1B=1ycy znmB3R#F-Hc_-MzN7#YD_`vMc5L(Qzggqs!s1Wqe7TP~#<&AP-xcg^r~b`fC~n{sX- zR-C$a^R82-vOQ~d?Z_^=QcRVfxJgluPab**=!Htoq^OKLgyFNt6^5&8qIVwC>npC` zCjbyvQ~~0?f@zk_;I?Jg!-SHMRLkj!ZF=rC8)mkZ3_5B~36S{(wtg~A;o~2)Xs2wh z>0;pbri8WvS=V%m()SsxH}-3pVcZqE>SmDCFEIGgl>rjv?>Wy1aWDVo<(s6#EI;u> z!li0`0AIvcUcIUm4G8IAh!wZ}eeT~y!-XTX3llfWI_S@;dKkVEw(TNYN>TTHWGPfX z(IIN0Xzh7P$kaJYpG=ar{?c!SjpHJ)?KxT=OGsB+`bsw6_x8{RoW=P@cXZ9Sj_|m-0Xn|5uwb zO^DY?2een*wDgxBw@lppK;`$)^4JpNHO4YscJZ`iG*VW@g7V)&@9CEmVUN3TuIb0_ zHklUly?&sgr4#a+OLNXj2ZC}J*+Cut6)0B|;`@Fw(N`u*f7w35Vs}^89{N!y#_I5+%svP+6>=6Iumw0TDO&&Wk_O zY8@aLz4N-|uA&{V(3UayfX(5)k57u*?}&e39xjQ9J#C(iVbV#`Zu#8=5xei2OyynV z3TbL~V9SM)->5T^mj52TV+@t4=Zk-C85+`QiZ(bWz&O}l6Jy%re3OW$Ib31C$r=oN|DnT2k)9@IV#3lFS%p+nk)WxzhSnZm)Kt%R%$*N*@!$$Vc)43Z9BaGeUrJo)OU7!xS*4_<;LYOCX2|IrzW_Bz7i5(7JMr!vb? zNt;x{Qg4K4vc+tkDV*L3$GgI91WnFJ$D2qAxkudFv|QjB@_;%mX_ZJ&_Tg~JWN8he zThd;x=uKJgoG?BSw!f?BSZpmd#Nwf?gr`7yG=SG($~XY8GX>ETxL6nS)ss}3|99s8 z(mrGO%E$3iPEUjB6l4bF5Gv{+9BcsaN;7D}%88M73zJ>24)i~3=6ATO=w!Cd_w6s?#K{<3) zZVdFzcm{Pnf0L|SES+KPFIrn6Xn#l1u4spJVgh&ELCFxyamhgN`fKh^@OqYDQ&9*= z_YoYs>;KD9scgn?V%~UDPp+1h2Cus)PYsjrd8XXAz5LoaS-3?b=sZx^^^r={#g{+j z1pZT&pKpG=sN<}tStTRAznM2ZAyT-`GK$;D{GFiJm!fe?m4BMebA@5l*NW{XQ-Y** zROE~&p^9^ViTLAVWto7_RqPor7V!kV z7Y44k?D74|67MJC%MTt7du%qR`?Sbg?RU#$N|p-*UaIJC0#Yuk+C9U?&5!7<)(cxp ztk+hog5);E{=5I4HGla@YwC*J@--{gMei(3S+;Ux^x3?9g%(TvBa0{Czt4`l?Q9`;yHkaZM>LOR9R^w$R7n5ziUx#REFJy`xyX! z$#j>ychRNj+lUqu*I7^#NnCA2U(|(?+LJGq)Mor=ZEWJHnkBUisHg>6{H`Un#6wLj zDXt~Ouc^VUt*8BX^|ac@P*hu0P&?}FQ86<+dnKx-)hd3hfqUP+n)VL`_Kuu5GJEbj z@?pA?_G^FcyH?WDaCnu!4?edh-hG=Y+T}S~Aj3f_x%WGhVODMql)@6(P6HLb;+F5l zhMlvs|L~Cq$iK-V9=k(z&Yjy^-nEi;qpw`qT3l60J2491`!;OYR!J*Y$oyN{DruJt zZ9ed=*zi24Ekwn%DM1RkqK#6G@Nn1e_%1*XPx0^@ifI}73!$8rnbw|=`}oS*03iO^ z4-0q~K)f(-Rxa~mD?;w!EAIq=I8fp}mH?m0FaZer>#qXkWZjwH1J?wLe2_m{^jd=Fb+Hzel0=GdRjcRiU<3_ z$C>6T(RSd$KJd~%*!Q(OxIg}KJuT=wtEcUtkjoVtKg-wmFTtgv>=AyYbcEGeLlR^lE1L&)IPSs4-P9i2YjhOI<4F}^02UnHC zUaTsot;WF`ydLHq$j7{pl03j)N#IPbKPhL)0)rRhjYNn855QmMz>eo=p8Sgzug8)3 z26yyQ`2#%mC**ZN#V?o-7pLTEkdTK2aF7Jh-hp^E?M2aqIF_i39jj?C zUI!ESsscD35C~GkSp7Yy+fNrL_pa=7Ss|82-UV{bri8Rc*9UwT|n(Q+DnvtR$2m=qDwE}VMQgy z5F;larO3VG&DIi9URR-z`L-7wUgr&hq})LfBJYxI))?)mD*4BwbD+>RN(xy(u+R1`VueC2w^y|j_M*#an;ACiH)MV--3df{ZCZJ`Nb4Coj%K_-dy#J{rQAp5 zx%#266eR;ZVE_YtgXMBk;Rt(Dps(T)s)GeZ8|}qkMSH9LbD@(ei1$_HIBx>{tA`X@ z`wC!OkT9WOwZ#4cIpm;o7{K1a_{*>t4@c;jz*(5LA0c-Rlnc`@%D_8P-Wb%B?Ia2% zl7pwLxoXLNT1TL^t^&e_8zD!c}I zsJAri1-)mm*H&bE84m~8I~?5OD11q|OOQx58Cr?IMyREazTVLOFCjd@XD5#|N4S7F|2F*{7G0vDRfl`XvcR}L1S zh}oWjfht&u7_@&r)tncnIqwksWzLI-1LuYC*+KZg#zijo3-Q=`S|mer3nsoo4jHKi z2U$L@{{l2dV@qS+A^0l_ZBs!GVP4!0t*6jkSx}(Qy}?34?gCQu6TT5PVITHX8->P! z@f10OU^SF|3VCgEB>p)UHZNQMS_>y`;G^7CBNZeHukGbZL0`mpk$ZqufboJ_wOmcc zTdmjtQ@|okODfvPm$a0UFa#i{22XA8DuvB~@zRQp;fV0!FA-i;eOoPP2g@g@^Ad$ZE4POqQ5M4vq4Gq8 zmtIrCi4q4;dx`KmLO+q37Pk)sd+1g@xgXjwL6d%>ltU8^uCofJ)lhRBiXVNs9Jksv zdyE_9FGG{7$`$%MA=lCg%#wKysUgJzDZsm^g!cJj+-GWlcfC=vHbOpCIcJNQO+QfC z*?{OSWQk)qY%oLd7m9Libhld233I>xI7Eyx4D}5D5Q-V}28$LuKiMPDs>--hJJ5QW z?c#{-!WcBb+%Hyc6~MY=^NSu<&{Hew(r>^*+8Q577wXzF7|<^0y@Yl_4-g|vLChW4 z?j#-^e1aZKlw}Oi)hO&`f%1-s?N<8+L;4D`Tdn1Sv?>dGdxLW%tZJZ%vKDQJXx;P? ziPK=7PN8Flz%KqOuxk_3;LI-;0@%&%B|%zf6+%L`)l&_btP&(Z$*w-km7b;mVqpMk zmh9TVZnc;ThYKaU+JUgcR1E>`VqN4S3Y$MSn6BOJ4kr=_rm}|N$6n&PX*M-2&g3*D znmD`je9;V$Dfaeb9C)Gk`TY{>dwUOIl_zUC~+0f)Ra%1EMX0)2bKJ(^#OMU%hz>IPE z8ia0HfNts5UP8BEOrzWJBw$24z`h(f-{w`LTWG8%y2ZOyq1*X5Q(Dbz;h37t7LKto z+rJj#z-+7PcZcEeHJL3OMAz@09+YS~#i#%5s@ybcHb#@Iv3JVC~ic%W#(D zhiXs@M?Mc~;XqpfZz~*M!nN~)I~UaefbA;a$ZAXr>oHWc(xNs@yX;x{E*$m(p}mUt zcq5^euiiC(`wD=w2+iWpquI%EICE4f0kryA>Fzf48H$)h!q?)7WCS_UAtGYp#E1xD zq9eSBh!iGG=`TKMnYtrW=J&{w zZHeRlV2P4lNfLYm%A=L>2=WTK+N;vg>@e<4{kQ`r({gY87ix=e%9B7@?s*|XvAra* z1^gkPT#aHM`b$LvO_r}DhXBQ2$~d(y*u%7Ju1v7utA^jdB}o27zD)LCJx?Q!>vy(Kf3 ziY8S_BNV%qCrPkccS$88zq47g{(xfRxI(dTpyh`y1xdBUB^>i1r(dGV2DVYEoqNXY#f(^ zce`PU6JTvtrh~Nj=Of0&{61noan~|)%$(lhzf6Zk1Ag|NXqx7&5*??Gi?J3rxKFZPEb6ek=GBQ0zj!CKN|2sJTh9bc$() zaK=GFU(;Fvr$r`}By5=iD7G)G->Yh%*wPMCSwE9C9ey>MIPAW(eTlWq5YGh^n*`Db z#g0~-0;svrd&^WWte5%v8R~{=jCqi#|4#yC_9-!^uhEn!90X9akDw=icuf==C+^iv znra=XI%k%FatCvM-_7T^gjTc|lm}9PV!!3@0u;N*dsmEz)0^tAdiYQ^HorN>=W|2{ zbpc)xCR38|%Q8T*U&^)w`aOIGOg$$H*De>%wT=)(>m_0LByEcK3a2D-C&Y?|!ypa@ zufOFT0IwH$Zz&D|>Av3a_kG64i{*1CO1vhUdV0;IrNQf7(!^1ay3Zk_cLB&bNqaD3H%OoXU;GEjyGn3I%zg$#PKD-*PGd4van8J=<)4;1i--?*}&WnpQOZp{L?E6sCBWgMzJ5Obaz6~&p$F4ZJl5I9nVab zyB7zuspq&W`qWO!V8N-~Yj6IXKsA^o1MS6Ydjx+Xsi^2{(zk zHf>nX#mS!aAr3H^_RjU2R=&@}^yEM%x5oAAINE)H157)|dQI{7X&%{FlY(78ulpem zFzxF!@|3-A`|xXd$FduJhyzTAx?k`E-@pAz)9;>*KfnQ|V?8%X>knwZ(m3z~9AL5> z>$4S|KBWCh;~>xX9s+2;fu~RC2en^m9_;bH4lr3x4LE+`AJ%@QWzhRN0PTN{`E~oi z_Q3&mBbWD?h99Z@_jbV6e)kXUe`Td*@OwqzqqYB@4t%`!-_L=M*nXvD$h!~0$87(7 z9Qdg1zlQ@KxBYi_;3K#H&JKL+_TSZkkKX<}I`Hw^f46nu6SV(M4t$FC-^GDX(*8R* z@M+qox8t`uDg8w4zqJFOs(oQPz3(t!|5k%ztY%+-LQ_s zn;rO+?N=5H-5NOCzwyKICvCs-jQ1Cxy?F#aZTrTKoJMut7=TaPe&s*HW^WF_CvN|) z%JXyQH(eh2#O)iK^P0H4X#hTP`_|T?W_2CjFathu`;{GJ?)4o1hwXP!denDB|1;fd z9-q4Zp+24s-n0UI>h{BY|F`Xj`*HrK{qOd_Z~t?rH*A5Qdi=ZjVgIk&@BTmTe{K8! z+x)AIe{KB#m+NnB{?+DRZT`Lf1Hh*~|EZmSwezob{?*RE*L?hE)UH41{)3Oe-(N%U zYuDe}^|yBYtzCa>*WXu1pmzVM-G6HL-`ew!*PK3m@Y}WLU$y67wdY^8=U=Zr`(V_5 z{-y7~uibw>c%ylZK<)lhyZ_YgKmWD=d~p5GYi3_%<=fr=@jq?Un{-ZbN z-$$rFdwuhj|9$`0hu7b|q4~;A3O3yT_WJx|e0crgo0_j|EpdC}{eKKw{blIjF%zc# zIi2`;kLiC-oiJu_XdwTKH{Soqu+^W2g!UUc@|$lz=Hr`@L;Hn>$UOh+23)%SRu%$G$Z%99M2KpZL z*Kbj$&GBTrebK0%^MBlcjvRQ8`t$#;R7VBsb?Z33MRhonX=m@?=;-M5aUY=Q@OIVl zO!Q!V{0FW3iccOWA68Z}7%Sk5;XAVz!T1av<-zb}Fvg4;J04!U#K%;ke`TzIfBiqs CGrWEP literal 0 HcmV?d00001 diff --git a/OSX/English.lproj/InfoPlist.strings b/OSX/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000000000000000000000000000000000..dea12de4cad936a6204d4da70d2ca96aef900b31 GIT binary patch literal 92 zcmW-Z!3uyN5C!M#S9tbN-x2r|Q3;V~qzLu#)x*oq?lA28G2*azG7B@2orjH8u89{# bCX+-f2F*!V&^~bXzEEWk)pxI)ej3aVcM}l{ literal 0 HcmV?d00001 diff --git a/OSX/English.lproj/SaverTester.nib/classes.nib b/OSX/English.lproj/SaverTester.nib/classes.nib new file mode 100644 index 00000000..15381b04 --- /dev/null +++ b/OSX/English.lproj/SaverTester.nib/classes.nib @@ -0,0 +1,27 @@ + + + + + IBClasses + + + CLASS + SaverTester + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + CLASS + FirstResponder + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + IBVersion + 1 + + diff --git a/OSX/English.lproj/SaverTester.nib/info.nib b/OSX/English.lproj/SaverTester.nib/info.nib new file mode 100644 index 00000000..235c169a --- /dev/null +++ b/OSX/English.lproj/SaverTester.nib/info.nib @@ -0,0 +1,20 @@ + + + + + IBFramework Version + 670 + IBLastKnownRelativeProjectPath + ../../xscreensaver.xcodeproj + IBOldestOS + 5 + IBOpenObjects + + 29 + + IBSystem Version + 9E17 + targetFramework + IBCocoaFramework + + diff --git a/OSX/English.lproj/SaverTester.nib/keyedobjects.nib b/OSX/English.lproj/SaverTester.nib/keyedobjects.nib new file mode 100644 index 0000000000000000000000000000000000000000..929e2e98eaa23d21c9d262021592031f44326c9f GIT binary patch literal 12749 zcmbVS31Ab|*1mUgC(WK|GTDca&W+)L`ZB4~qfkAH zpqtQAbPHOBR-l#WcC;F;LHD5h(OR?~J%qNPt>|g=4BCdCL%Yx(^ak3C-bC-91Ly zhfm<|@k#t6K8Jt9=kY~`Wt2<`qhfT7nc*1=V`ZF7CL=IDCX2~tx-s3E9!zg$ATyX5 z!VG1GF(a8J%}Nh;zGhCq)%VPg%z1@Ep;hP< zdWBVyuJAGMD?~+>qKl%NqNk!4>Z9nR$cM24#bCuqycDj=6y=IqMK)a3E1DIv6>}5| znFH|a7UmiFUZuE0ai?NG(UM_gI4L9}$Vf7Z6p>;wnv5YOWGpEqWu%-`ka47vj3-rO z0;whw$s|%kCX)b}LTX7J36iN~8ktT)WCoc@>PZ7>Bw^A-W|0Vqk{D?wv&kGXm&_yA zlN-o@apXTgZ-J zN3x^XBDR=JG zPGhIDA$A5kldWeP*hV(YHnFqV2peT%Y%@EXox{#$=dstbH?Z^B8`%ZyLYA@;dlS2e zy_sFiE@79lx3J6DTiNC83U(EHXSuJjxxW4@&V=;HfQ*PksmO%Ph({J=MK)wd4&+2G zmFd9s&W%sUNSA0V5^9`A z32>j^1o5#pAnWM^)DQJXx#+se#0CVTRf&s6ps@*EhX$a$_%;+Z#{wW>X`D)Bap|Rt zgq#UF`KVwcDnNOYfRW~CEZmTo1Qit)3<}qV1DCczW^D)>iiW`sw1n9xRjp5uDMTYS zp%G{#$^)IW0taK|fEtV?Ru-XRx?wBlL8H+aRD#B$QdEY@Q3V=@D$#gUg(jeCG!acg zHE1#lped*p)uAAoil(9ID1>I9nN&mVG>!Ub7VScN(tdOx9YPCf5gkh_Xce7AYw2{_ zKxfg}^m@9G-b`nCte z6va?8nvLe5xo94`9^HWEqZ`ozv=C7w!R=+CSbeaDOO2_OZVc6q3fICuj&3Y$0JD=76Pg>Wi~j&0Dhf6>Qw!Cbz#|bgm=^x|=bzL_ z%~U6I8%2xI&1f-N0v00QSr`j8MCqgSY5Gjrpm5W?l3Mw$?&wyu9Q_U52HQ6_7>!F6 zHzgD0Xeuxq2TNE35E(Q*SUa;J5Sf|Sv{mSijpz>9TEHI?yWdJAnfTpQrpCwbl39sM zQ5p-xf(bJBqWdBPyy5s6|URK|e=qXUyJM`W1LG|Hiuw>4} zreI`hIMNXR+3zyYcC-V4(y8$2e;C2@UylJYvzP6_2r1?P-_yEUSi*I&|9 z(F7qV*6$-w%fDJYC>(Kx;|La zP{Wy9Y(*Xm#F`^8oP?Z?y?~rg2e*YR0J1L$xquEzAlGoF0y&*{^Xu!qv9Nb&1iWwz zNEnDj0*%vxg^lr%B%IE;3*Zc;!`s5?b`{RBR-Ce`aJZqmMF9CaiDJLFn~k`Jo8bXK zGq1%|cH%*xw~=&IoO>^agOGaUjj4l;nR>}{@o-!SOcv9UwwN3R1dCb-N_rb8oC!&zk)i2q0`3ZhrKD30OgINk(0 zu`Rs0mv;}zPFdZud)bwxVDGYt08VqGj0D$h<6w#WE_-*7 zPL1zH&ZKyn^OiOR>+8YbOZrc$mGfTf@1*@+i`N1D5Up>E{(3wEZ%8tp8FXgCcp#0% zW3D#<;k_sjtDWvGjm@hMmQ+Ax5Rbz7(J}RbP-7W{{v@=`cnhF4(x$f1p1Qmv@mLjZ z*^!(Y&UvLg-aynlKHTgro7V)TNLnmBZCZU$PN|{BX0W`tnDG@LdlLHd_ys_Z(B`(# zUk3D7l4On2SVC4P3t}a6%W9jG1=)+=1X6S8ytYWaeOVAB``p|XL39;^!3<)-ak4ce zCo22_{t(FBKo_({?jVr+IEl0Q^u`2dRi!|uHYVF@NuVkk{{h!Y5+BB216@iNwMF+R z(ETQfu0(H2&@C&6hM?s7@@DvRl~etIPXj@QE~ZOAjwi3AQW5{$8g}CjwWQUdz$Egg zgqvf@X{?6xgd=sqNIc*|A=OwHh}6aJx;n{Rz<&VZGWxeRL|_n_&)^mj#lDryssgbZwK> z6-Eb#qH=3i2Y?UI2i0-Jb(g9Vrhpj)b-}CjHDDqska~v)>zhjQ#>+K{e3>Y)nc+;~ zCZ>=X5pUvJdary)>hqxfkN=JLU(6_`h<>$|>w`8jqp^h7GGlQ+bPVd4;b4js=&8P|L_f3!?Li8az0*b``kgUD*F{pEM>627PpQEe6 zz@myhS2f~-A(t>El`rLqooiV_R+u7opc+Nvrc*lvS>D41CewwJpe}g z5zT=dF%))S0{w()KTDMG%u>{xybs`>^O;=iYuyPXK@D3NEe$pWA_3?+U@Hcx<^SU? z*k2}c#b9L-=;K8i0{giEtnEp< z?HX)?fwEVy`5o|-18qQ}R@Tw&m95o6t1ACZH*5m0Sb;Z#t=7@Tzo0PCh;fLO&OT!nqc{qmTtU{g*skLzhe*Q*c?A zOhl0ee5`}umC)r*x+|&YQDi7G6#~SzgY--KahV*%S`@5kM>lL>deODXcXw2DQgnv9 zU#D--JuP;j$fl&SG#Hs3f+NM«T_0Y$3DVw43t|fxI{iC^7LBK$D3==;4_>xQak^>;wTB z_~;-YW&_VUh*{b2eF#3Ez^oTOIdGjFAI*(_?}R;P;9f7}NH5Iog#Tn3PSEd8xa05e z$$?LHoaQ#T&INbOhB;lUv`=r)dAr2O_0q#z!fO{|5XXpK$QOPDuHz% zUpmC~Qjqp9*8(jP6MbA_V!%rXM^cH2m2dlUJwd;xC+WZGDf$CFO@E|6(VyuV`U^cvf2HT>Z}dF< zonD}S(2Mj>34@J6p$3l?6cQ#9W+gbRIw;{339BTmmas;`S_$hUte3Ds!bS;m5>Az{ zNy26@PYGKjY?ZK0!gdKepjwyR(<`4Od)voPK(i)XDt|gdqnVL3-)4uKe1GpSmx_ww znWY)}j6x^{f~Wzm+Fw4XL0L7NJBeug93!cqg=s!%y7dSQhPk9+`K%^uLVTdTTvR1< z(d|m9;#_ASs&3@!D3{TKU1wnf@1Tw;Yk9%a$4EK3QFt3@{lu=`0U;Pu$-A zN^X)}=IS04*7%dexc1Qy+~`TV@jnhBU_RORF74Zu%l@OkPqvPpt;W^brC-5QN_cq5j3SGYu9wJ3 zq2hmShpQpkmnFBv@Sw6zZj0fe<>W+bj6!mAj7DGC9>asobQFN(4^G+ID8oa~=}<~c zliOufB{$5d7XJWs`dGPbhKHat&=k3OMl0)^N z>WjDBL*Sw6ta#&nf4ueX3C(vF6rq#gQEWdbU23VeRpVd9wM-6^ugMYeFLIQ8LynPe z$#L=>IYGWBC&|CbDe?n3O@1Ulk)O#K@(VdjekJF~Z{$4rom?P)kc;F`7O|LRSOrU1 zmQ}JTtcq2$8dl5dSUqcCjV#BevL@Ed@~nlmvNqPvI#?&`V%@BVO=HtpFPp(;vI6U4 zMb^)@W81S?YzMX@+llSWc451+*=#qqJKKZp$@Y?PnuOCO?3Hkagfk^9NZ2P~QNn%+ zx07&t31>;TgM>RuxRZoCOSp@KyGl4)!rdg?UBW#i+*86pCr84)CEQ2CeI?va!u=(j zE8*)TJV3&E5*{ewdL zTR1!S7>g4@6qaW8WZaBpz?xMw+uThF!Q-s4zqCHD~b4mXXviQ8d@N80kg zKe%e{QEmpek$ZwGi9KO{_I`s-5c4v<40wM1&MbZ2E)r@NCj`fWWw7n-QewtJa{Q=EWDgBRZ*pw ztk|O1rg%}YPw}bZ7os3WC>67y_$z={it5QivWjdWJIL$g4YH5CP5w^ylLO>K@=x*! zIYhnyt3Lu3e++E>d$96f!G2k=T@EbM4c6BKULhI=uLw~ri3 z?2GK{?Az=?_H*_d_FJV-=~uQ_c2IUwc2Q<4yDNJtbCi9Q{gk=N0m^~O0_9-kP~~vt z2<0ecv2u)Ztg=j5p{!I^DXW!}lrxnv<$UD=B~{*}+@##1+@*X+`HAv~@(1NvX(WK~794TT-yOgYyjw#tG-Ba>Xic`j~dO`J)>J`sWoby zy0f~gx|_O(x|h1Qy01Dio7GFyx2W$@uTejs-m2cI z-lg8H-lN{DKBhjdKA}FTKBYdb{z;?IXf!&FLBnZG8d1|u(^=C`ldBn^8K?Y}=4-3_{1bhqmMrdy$VR<}dy;X15JN0gTdwsrstiDWNp|8|e>2J{As9&g;^o#V1^-J})>mS#@q<=;Ks{S?o z>-u-~@9FpJ59yEU|E)h`&>HLpr@?JVGvpca4TB6r48shChLMIUL$zV1VXonN!+gU6 z!-IwmhE0Zt4UZZgH*7KNG3+&bYB**%Za85$Y1A8y##E!(XffK14r7jSoUzV0)i~Wa z!?@gdn{lOam2tK4F5?>G{l<01jmE9UXN=DpcNh;EKQVr0{M`7Z@v!lT@jK&>#)}-{ z7>;{_YrrH`-D50s!MgGx>7x<>8S%#3sMKC4ow}NIwEycYHeyTHI^!+E=paT zx-|8%)Xk|+q&}7Ubn3R$?Wu32zLR<)_1DzjQh!hV!<1?Anf#{qrVge~rY@#zQ=w_3 zsoFHjG}$!8RA*XjT54KmT5h_{w9>T7^pNQh(=(=5O|O|=H@#u{&UDsv&UD^%!Ssje zPct?v%$(U|=FL`fmbs(3mwAABh`G#MZ4R2JnLjgsZvN7I*nGr%)O^f*+UdJ1FjyLfK{{2Tn6{C@rbe~|x*|C&F^pW;vRKk;Yyv;3bHV(Dz@YUyU_Zs}?1W$A6{ zYw2$pY#C}PvQ$_qEdfidC1{yu30dy2+-bSnvc__+0*6T3{V)9cmqJ9bp}1Ew+xajVd+hhwAGAMa-)w)@zQg{i{WbgR_BZTr z+JCVBX#d&%i~U#oZ}#8qf7t(Y=o}V@&C$h??db04>Bw>ParATKItDlfItmzvj;9>T49>nw9tI4hl1&T8j$=Pc)J=R&9CJmWm;Jm);` zyx_d(LN3NdTuPV9rE%$81{dctxp&kTbTz*%3R|i)oR~J{dtGlbG zE63Hx)z6jd8sHk}DsT;U4RsB7jc|=}6}!f`#=6Q}6|PEGm8;q{$u-$E#Z~8OaD`n* zUB_I8U8h{9T|c?bxX!xHxz4*TxGuVpn{gAj(yel9+&Z_x&ACl(-feZ;-A=dL zo#ytsGu=M7-`(Eb!QIK-#hvZ$?(XT%arbfebLYATxCgom+=Jai-NW4@+@svZ?lJDM z?lO0UyT%=GA9R1>{>=Tk`%Cv>_YwC|_c8Zz_X+n&_bK;j_fPIK?z8T5?(^;o?u#De zVLZg6^r$=+S0;_fGN7_pbK70Y$PzjVorSJK zH=&2nOXw~175WR;33)=kFi0383=;~4kwTF$S||}pg>qq>FkYA-OcZK_fKV$0g=s=a zm?<;}VPTdK6`F-P!aU&y;YMMhAPI|v#lliynXp{AO;{^X$32TIVh5Lnd!h^yF zVUzH%@Tl;(utj)M*eX0DJS*%Fo)=yeUKVx=yM*1s9$~MrPk38+S9nj@FB}j)6F&DX z_pR`)^4;lM>=ieeZ^ccPb?6Jh{MH^VzF2vmWkuUDsiGXS*#VOiXpLH42u!5S)420ATAIk z@n&(Uc&m7uxKdmtt`_eS*NFFu_lxVq2gMEICh=kMQSotci}<9tReVN#R@@;zFTN@uDC389(tW{VKo4uk#!HoZsZ<{Z_x-@ASL^9{q6l7{GI$={Mr8Q z{+|9Ee;+1;0*-%AG;>;xAoh<@P7b# Cf~jo) literal 0 HcmV?d00001 diff --git a/OSX/InvertedSlider.h b/OSX/InvertedSlider.h new file mode 100644 index 00000000..71efb161 --- /dev/null +++ b/OSX/InvertedSlider.h @@ -0,0 +1,20 @@ +/* xscreensaver, Copyright (c) 2006 Jamie Zawinski +* +* Permission to use, copy, modify, distribute, and sell this software and its +* documentation for any purpose is hereby granted without fee, provided that +* the above copyright notice appear in all copies and that both that +* copyright notice and this permission notice appear in supporting +* documentation. No representations are made about the suitability of this +* software for any purpose. It is provided "as is" without express or +* implied warranty. +* +* This is a subclass of NSSlider that is flipped horizontally: +* the high value is on the left and the low value is on the right. +*/ + +#import + +@interface InvertedSlider : NSSlider +{ +} +@end diff --git a/OSX/InvertedSlider.m b/OSX/InvertedSlider.m new file mode 100644 index 00000000..d2060d95 --- /dev/null +++ b/OSX/InvertedSlider.m @@ -0,0 +1,102 @@ +/* xscreensaver, Copyright (c) 2006 Jamie Zawinski +* +* Permission to use, copy, modify, distribute, and sell this software and its +* documentation for any purpose is hereby granted without fee, provided that +* the above copyright notice appear in all copies and that both that +* copyright notice and this permission notice appear in supporting +* documentation. No representations are made about the suitability of this +* software for any purpose. It is provided "as is" without express or +* implied warranty. +* +* This is a subclass of NSSlider that is flipped horizontally: +* the high value is on the left and the low value is on the right. +*/ + +#import "InvertedSlider.h" + +@implementation InvertedSlider + +-(double) transformValue:(double) value +{ + double low = [self minValue]; + double high = [self maxValue]; + double range = high - low; + double off = value - low; + double trans = low + (range - off); + // NSLog (@" ... %.1f -> %.1f [%.1f - %.1f]", value, trans, low, high); + return trans; +} + +-(double) doubleValue; +{ + return [self transformValue:[super doubleValue]]; +} + +-(void) setDoubleValue:(double)v +{ + return [super setDoubleValue:[self transformValue:v]]; +} + + +/* Implement all accessor methods in terms of "doubleValue" above. + */ + +-(float) floatValue; +{ + return (float) [self doubleValue]; +} + +-(int) intValue; +{ + return (int) [self doubleValue]; +} + +-(id) objectValue; +{ + return [NSNumber numberWithDouble:[self doubleValue]]; +} + +-(NSString *) stringValue; +{ + return [NSString stringWithFormat:@"%f", [self floatValue]]; +} + +- (NSAttributedString *)attributedStringValue; +{ + return [[NSAttributedString alloc] initWithString:[self stringValue]]; +} + + +/* Implment all setter methods in terms of "setDoubleValue", above. + */ + +-(void) setFloatValue:(float)v +{ + [self setDoubleValue:(double)v]; +} + +-(void) setIntValue:(int)v +{ + [self setDoubleValue:(double)v]; +} + +-(void) setObjectValue:(id )v +{ + NSAssert2((v == nil) || + [(NSObject *) v respondsToSelector:@selector(doubleValue)], + @"argument %@ to %s does not respond to doubleValue", + v, __PRETTY_FUNCTION__); + [self setDoubleValue:[((NSNumber *) v) doubleValue]]; +} + +-(void) setStringValue:(NSString *)v +{ + [self setDoubleValue:[v doubleValue]]; +} + +-(void) setAttributedStringValue:(NSAttributedString *)v +{ + [self setStringValue:[v string]]; +} + +@end diff --git a/OSX/Makefile b/OSX/Makefile new file mode 100644 index 00000000..03fc9899 --- /dev/null +++ b/OSX/Makefile @@ -0,0 +1,141 @@ +# XScreenSaver for MacOS X, Copyright (c) 2006 by Jamie Zawinski. + +XCODE_TARGET = "All Savers" + +default: release +all: debug release + +clean: + -rm -rf build +# cd ..; xcodebuild -target $(XCODE_TARGET) clean + +distclean: + -rm -f config.status config.cache config.log \ + *.bak *.rej TAGS *~ "#"* + -rm -rf autom4te*.cache + -rm -rf build + +distdepend:: update_plist_version + +debug: distdepend + cd ..; xcodebuild -target $(XCODE_TARGET) -configuration Debug build + +release:: distdepend + cd ..; xcodebuild -target $(XCODE_TARGET) -configuration Release build + +release:: check_versions + +release:: sign + +sign: + @for f in build/Release/*.saver ; do \ + codesign -vfs 'Jamie Zawinski' $$f ; \ + done + +check_versions: + @\ + SRC=../utils/version.h ; \ + V=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' $$SRC` ; \ + DIR=build/Release ; \ + RESULT=0 ; \ + for S in $$DIR/*.saver ; do \ + for P in $$S/Contents/Info.plist ; do \ + V2=`perl -0000 -n -e \ + 'm@CFBundleVersion\s*(.*?)@si \ + && print $$1' < $$P` ; \ + if [ "$$V2" != "$$V" ] ; then \ + echo "Wrong version: $$S ($$V2)" ; \ + RESULT=1 ; \ + fi ; \ + done ; \ + done ; \ + if [ "$$RESULT" = 0 ]; then echo "Versions match ($$V2)" ; fi ; \ + exit $$RESULT + + +echo_tarfiles: + @echo `find . \ + \( \( -name '.??*' -o -name build -o -name CVS -o -name '*~*' \ + -o -name 'jwz.*' \) \ + -prune \) \ + -o -type f -print \ + | sed 's@^\./@@' \ + | sort` + +update_plist_version: + @ \ + SRC=../utils/version.h ; \ + V=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' $$SRC` ; \ + T=/tmp/xs.$$$$ ; \ + for S in XScreenSaver.plist SaverTester.plist ; do \ + /bin/echo -n "Updating version number in $$S to \"$$V\"... " ; \ + KEYS="CFBundleVersion|CFBundleShortVersionString" ; \ + perl -0777 -pne \ + "s@(($$KEYS)\s*)[^<>]+()@\$${1}$$V\$${3}@g" \ + < $$S > $$T ; \ + if cmp -s $$S $$T ; then \ + echo "unchanged." ; \ + else \ + cat $$T > $$S ; \ + echo "done." ; \ + fi ; \ + done ; \ + rm $$T + +# -format UDBZ saves 4% (~1.2 MB) over UDZO. +dmg:: distdepend check_versions + @ \ + set -e ; \ + SRC=../utils/version.h ; \ + V=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' $$SRC` ; \ + TMPDIR="build" ; \ + SRC="build/Release" ; \ + BASE="xscreensaver-$$V" ; \ + OUTDIR="../archive" ; \ + DMG="$$OUTDIR/$$BASE.dmg" ; \ + TMPDMG="$$TMPDIR/tmp.dmg" ; \ + VOLNAME="XScreenSaver $$V" ; \ + STAGE="$$TMPDIR/dmg_stage" ; \ + rm -f "$$DMG" ; \ + rm -rf "$$STAGE" ; \ + echo + mkdir "$$STAGE" ; \ + mkdir "$$STAGE" ; \ + \ + retired=`perl -0 -ne \ + 's/\\\\\\n//g; m/^RETIRED_EXES\s*=\s*(.*)$$/m && print "$$1\n"' \ + ../hacks/Makefile.in ; \ + perl -0 -ne \ + 's/\\\\\\n//g; m/^RETIRED_GL_EXES\s*=\s*(.*)$$/m && print "$$1\n"' \ + ../hacks/glx/Makefile.in` ; \ + \ + for f in $$SRC/*.saver ; do \ + ok=yes ; \ + ff=`echo $$f | perl -e '$$_=<>; s@^.*/(.*)\..*$$@\L$$1@; print'`; \ + for r in $$retired ; do \ + if [ "$$ff" = "$$r" ]; then ok=no ; fi ; \ + done ; \ + if [ "$$ok" = yes ]; then \ + echo + cp -pr "$$f" "$$STAGE/" ; \ + cp -pr "$$f" "$$STAGE/" ; \ + else \ + echo skipping "$$f" ; \ + fi ; \ + done ; \ + set -x ; \ + cp -p bindist.rtf "$$STAGE/ READ ME.rtf" ; \ + cp -p bindist-DS_Store "$$STAGE/.DS_Store" ; \ + cp -p XScreenSaverDMG.icns "$$STAGE/.VolumeIcon.icns" ; \ + /Developer/Tools/SetFile -a C "$$STAGE" ; \ + /Developer/Tools/SetFile -a E "$$STAGE/ READ ME.rtf" ; \ + hdiutil makehybrid -quiet -ov -hfs -hfs-volume-name "$$VOLNAME" \ + -hfs-openfolder "$$STAGE" "$$STAGE" -o "$$TMPDMG" ; \ + rm -rf "$$STAGE" ; \ + hdiutil convert -quiet -ov -format UDBZ -imagekey zlib-level=9 \ + "$$TMPDMG" -o "$$DMG" ; \ + rm -f "$$TMPDMG" ; \ + ls -ldhgF "$$DMG" + +# Adding this is cute: +# hdiutil internet-enable -yes -quiet "$$DMG" +# but means that nobody will ever see the display settings I used! +# When finder copies the .dmg to a folder, it doesn't preserve them. diff --git a/OSX/PrefsReader.h b/OSX/PrefsReader.h new file mode 100644 index 00000000..9905aeb0 --- /dev/null +++ b/OSX/PrefsReader.h @@ -0,0 +1,39 @@ +/* xscreensaver, Copyright (c) 2006 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* This implements the substrate of the xscreensaver preferences code: + It does this by writing defaults to, and reading values from, the + NSUserDefaultsController (and ScreenSaverDefaults/NSUserDefaults) + and thereby reading the preferences that may have been edited by + the UI (XScreenSaverConfigSheet). + */ + +#import +#import "jwxyz.h" + +@interface PrefsReader : NSObject +{ + NSUserDefaultsController *userDefaultsController; + NSUserDefaults *userDefaults; // this is actually a 'ScreenSaverDefaults' +} + +- (id) initWithName: (NSString *) name + xrmKeys: (const XrmOptionDescRec *) opts + defaults: (const char * const *) defs; + +- (NSUserDefaultsController *) userDefaultsController; + +- (char *) getStringResource: (const char *) name; +- (double) getFloatResource: (const char *) name; +- (int) getIntegerResource: (const char *) name; +- (BOOL) getBooleanResource: (const char *) name; + +@end diff --git a/OSX/PrefsReader.m b/OSX/PrefsReader.m new file mode 100644 index 00000000..507a557b --- /dev/null +++ b/OSX/PrefsReader.m @@ -0,0 +1,300 @@ +/* xscreensaver, Copyright (c) 2006-2009 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* This implements the substrate of the xscreensaver preferences code: + It does this by writing defaults to, and reading values from, the + NSUserDefaultsController (and ScreenSaverDefaults/NSUserDefaults) + and thereby reading the preferences that may have been edited by + the UI (XScreenSaverConfigSheet). + */ + +#import +#import "PrefsReader.h" +#import "screenhackI.h" + +@implementation PrefsReader + +/* Converts an array of "key:value" strings to an NSDictionary. + */ +- (NSDictionary *) defaultsToDict: (const char * const *) defs +{ + NSDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:20]; + while (*defs) { + char *line = strdup (*defs); + char *key, *val; + key = line; + while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t') + key++; + val = key; + while (*val && *val != ':') + val++; + if (*val != ':') abort(); + *val++ = 0; + while (*val == ' ' || *val == '\t') + val++; + + int L = strlen(val); + while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t')) + val[--L] = 0; + + // When storing into preferences, look at the default string and + // decide whether it's a boolean, int, float, or string, and store + // an object of the appropriate type in the prefs. + // + NSString *nskey = [NSString stringWithCString:key + encoding:NSUTF8StringEncoding]; + NSObject *nsval; + int dd; + double ff; + char cc; + if (!strcasecmp (val, "true") || !strcasecmp (val, "yes")) + nsval = [NSNumber numberWithBool:YES]; + else if (!strcasecmp (val, "false") || !strcasecmp (val, "no")) + nsval = [NSNumber numberWithBool:NO]; + else if (1 == sscanf (val, " %d %c", &dd, &cc)) + nsval = [NSNumber numberWithInt:dd]; + else if (1 == sscanf (val, " %lf %c", &ff, &cc)) + nsval = [NSNumber numberWithDouble:ff]; + else + nsval = [NSString stringWithCString:val encoding:NSUTF8StringEncoding]; + +// NSLog (@"default: \"%@\" = \"%@\" [%@]", nskey, nsval, [nsval class]); + [dict setValue:nsval forKey:nskey]; + free (line); + defs++; + } + return dict; +} + + +/* Initialize the Cocoa preferences database: + - sets the default preferences values from the 'defaults' array; + - binds 'self' to each preference as an observer; + - ensures that nothing is mentioned in 'options' and not in 'defaults'; + - ensures that nothing is mentioned in 'defaults' and not in 'options'. + */ +- (void) registerXrmKeys: (const XrmOptionDescRec *) opts + defaults: (const char * const *) defs +{ + // Store the contents of 'defaults' into the real preferences database. + NSDictionary *defsdict = [self defaultsToDict:defs]; + [userDefaults registerDefaults:defsdict]; + + userDefaultsController = + [[NSUserDefaultsController alloc] initWithDefaults:userDefaults + initialValues:defsdict]; + + NSDictionary *optsdict = [NSMutableDictionary dictionaryWithCapacity:20]; + + while (opts[0].option) { + //const char *option = opts->option; + const char *resource = opts->specifier; + + while (*resource == '.' || *resource == '*') + resource++; + NSString *nsresource = [NSString stringWithCString:resource + encoding:NSUTF8StringEncoding]; + + // make sure there's no resource mentioned in options and not defaults. + if (![defsdict objectForKey:nsresource]) { + if (! (!strcmp(resource, "font") || // don't warn about these + !strcmp(resource, "textLiteral") || + !strcmp(resource, "textFile") || + !strcmp(resource, "textURL") || + !strcmp(resource, "imageDirectory"))) + NSLog (@"warning: \"%s\" is in options but not defaults", resource); + } + [optsdict setValue:nsresource forKey:nsresource]; + + opts++; + } + + // make sure there's no resource mentioned in defaults and not options. + NSEnumerator *enumerator = [defsdict keyEnumerator]; + NSString *key; + while ((key = [enumerator nextObject])) { +#if 0 + if (! [optsdict objectForKey:key]) + if (! ([key isEqualToString:@"foreground"] || // don't warn about these + [key isEqualToString:@"background"] || + [key isEqualToString:@"Background"] || + [key isEqualToString:@"geometry"] || + [key isEqualToString:@"font"] || + [key isEqualToString:@"dontClearRoot"] || + + // fps.c settings + [key isEqualToString:@"fpsSolid"] || + [key isEqualToString:@"fpsTop"] || + [key isEqualToString:@"titleFont"] || + + // analogtv.c settings + [key isEqualToString:@"TVBrightness"] || + [key isEqualToString:@"TVColor"] || + [key isEqualToString:@"TVContrast"] || + [key isEqualToString:@"TVTint"] + )) + NSLog (@"warning: \"%@\" is in defaults but not options", key); +#endif /* 0 */ + } + +} + +- (NSUserDefaultsController *) userDefaultsController +{ + NSAssert(userDefaultsController, @"userDefaultsController uninitialized"); + return userDefaultsController; +} + + +- (NSObject *) getObjectResource: (const char *) name +{ + while (1) { + NSString *key = [NSString stringWithCString:name + encoding:NSUTF8StringEncoding]; + NSObject *obj = [userDefaults objectForKey:key]; + if (obj) + return obj; + + // If key is "foo.bar.baz", check "foo.bar.baz", "bar.baz", and "baz". + // + const char *dot = strchr (name, '.'); + if (dot && dot[1]) + name = dot + 1; + else + return nil; + } +} + + +- (char *) getStringResource: (const char *) name +{ + NSObject *o = [self getObjectResource:name]; + //NSLog(@"%s = %@",name,o); + if (o == nil) { + if (! (!strcmp(name, "eraseMode") || // erase.c + // xlockmore.c reads all of these whether used or not... + !strcmp(name, "right3d") || + !strcmp(name, "left3d") || + !strcmp(name, "both3d") || + !strcmp(name, "none3d") || + !strcmp(name, "font") || + !strcmp(name, "labelFont") || // grabclient.c + !strcmp(name, "titleFont") || + !strcmp(name, "fpsFont") || // fps.c + !strcmp(name, "foreground") || // fps.c + !strcmp(name, "background") + )) + NSLog(@"warning: no preference \"%s\" [string]", name); + return NULL; + } + if (! [o isKindOfClass:[NSString class]]) { + NSLog(@"asked for %s as a string, but it is a %@", name, [o class]); + o = [(NSNumber *) o stringValue]; + } + + NSString *os = (NSString *) o; + char *result = strdup ([os cStringUsingEncoding:NSUTF8StringEncoding]); + + // Kludge: if the string is surrounded with single-quotes, remove them. + // This happens when the .xml file says things like arg="-foo 'bar baz'" + if (result[0] == '\'' && result[strlen(result)-1] == '\'') { + result[strlen(result)-1] = 0; + strcpy (result, result+1); + } + + // Kludge: assume that any string that begins with "~" and has a "/" + // anywhere in it should be expanded as if it is a pathname. + if (result[0] == '~' && strchr (result, '/')) { + os = [NSString stringWithCString:result encoding:NSUTF8StringEncoding]; + free (result); + result = strdup ([[os stringByExpandingTildeInPath] + cStringUsingEncoding:NSUTF8StringEncoding]); + } + + return result; +} + + +- (double) getFloatResource: (const char *) name +{ + NSObject *o = [self getObjectResource:name]; + if (o == nil) { + // xlockmore.c reads all of these whether used or not... + if (! (!strcmp(name, "cycles") || + !strcmp(name, "size") || + !strcmp(name, "use3d") || + !strcmp(name, "delta3d") || + !strcmp(name, "wireframe") || + !strcmp(name, "showFPS") || + !strcmp(name, "fpsSolid") || + !strcmp(name, "fpsTop") || + !strcmp(name, "mono") || + !strcmp(name, "count") || + !strcmp(name, "ncolors") || + !strcmp(name, "doFPS") || // fps.c + !strcmp(name, "eraseSeconds") // erase.c + )) + NSLog(@"warning: no preference \"%s\" [float]", name); + return 0.0; + } + if ([o isKindOfClass:[NSString class]]) { + return [(NSString *) o doubleValue]; + } else if ([o isKindOfClass:[NSNumber class]]) { + return [(NSNumber *) o doubleValue]; + } else { + NSAssert2(0, @"%s = \"%@\" but should have been an NSNumber", name, o); + abort(); + } +} + + +- (int) getIntegerResource: (const char *) name +{ + // Sliders might store float values for integral resources; round them. + float v = [self getFloatResource:name]; + int i = (int) (v + (v < 0 ? -0.5 : 0.5)); // ignore sign or -1 rounds to 0 + // if (i != v) NSLog(@"%s: rounded %.3f to %d", name, v, i); + return i; +} + + +- (BOOL) getBooleanResource: (const char *) name +{ + int n = [self getIntegerResource:name]; + if (n == 0) return NO; + else if (n == 1) return YES; + else { + NSAssert2(0, @"%s = %d but should have been 0 or 1", name, n); + abort(); + } +} + + +- (id) initWithName: (NSString *) name + xrmKeys: (const XrmOptionDescRec *) opts + defaults: (const char * const *) defs +{ + self = [self init]; + if (!self) return nil; + + userDefaults = [ScreenSaverDefaults defaultsForModuleWithName:name]; + + [self registerXrmKeys:opts defaults:defs]; + return self; +} + +- (void) dealloc +{ + [userDefaultsController release]; + [super dealloc]; +} + +@end diff --git a/OSX/README b/OSX/README new file mode 100644 index 00000000..551e3bb6 --- /dev/null +++ b/OSX/README @@ -0,0 +1,10 @@ + +This directory contains the MacOS-specific code for building a Cocoa +version of xscreensaver without using X11. + +To build it, just type "make", or use the included XCode project. The +executables will show up in the "build/Release/" and/or "build/Debug/" +directories. + +To build these programs, XCode 2.4 or later is required. +To run them, MacOS 10.4.0 or later is required. diff --git a/OSX/SaverTester.h b/OSX/SaverTester.h new file mode 100644 index 00000000..7226b74e --- /dev/null +++ b/OSX/SaverTester.h @@ -0,0 +1,21 @@ +/* xscreensaver, Copyright (c) 2006-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#import +#import + +@interface SaverTester : NSObject +{ + NSArray *saverNames; + NSArray *windows; +} + +@end diff --git a/OSX/SaverTester.m b/OSX/SaverTester.m new file mode 100644 index 00000000..a2ab4937 --- /dev/null +++ b/OSX/SaverTester.m @@ -0,0 +1,352 @@ +/* xscreensaver, Copyright (c) 2006-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* This is just a test harness, really -- it makes a window with an + XScreenSaverGLView in it and a Preferences button, because I don't want + to have to debug these programs by installing them as screen savers + first! + */ + +#import "SaverTester.h" +#import "XScreenSaverGLView.h" + +@implementation SaverTester + +- (ScreenSaverView *) makeSaverView: (NSString *) module +{ + NSString *dir = [[[NSBundle mainBundle] bundlePath] + stringByDeletingLastPathComponent]; + NSString *name = [module stringByAppendingPathExtension:@"saver"]; + NSString *path = [dir stringByAppendingPathComponent:name]; + NSBundle *bundleToLoad = [NSBundle bundleWithPath:path]; + Class new_class = [bundleToLoad principalClass]; + NSAssert1 (new_class, @"unable to load \"%@\"", path); + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = 320; + rect.size.height = 240; + + id instance = [[new_class alloc] initWithFrame:rect isPreview:YES]; + NSAssert1 (instance, @"unable to instantiate %@", new_class); + return (ScreenSaverView *) instance; +} + + +static ScreenSaverView * +find_saverView_child (NSView *v) +{ + NSArray *kids = [v subviews]; + int nkids = [kids count]; + int i; + for (i = 0; i < nkids; i++) { + NSObject *kid = [kids objectAtIndex:i]; + if ([kid isKindOfClass:[ScreenSaverView class]]) { + return (ScreenSaverView *) kid; + } else { + ScreenSaverView *sv = find_saverView_child ((NSView *) kid); + if (sv) return sv; + } + } + return 0; +} + + +static ScreenSaverView * +find_saverView (NSView *v) +{ + while (1) { + NSView *p = [v superview]; + if (p) v = p; + else break; + } + return find_saverView_child (v); +} + + +- (void) openPreferences: (NSObject *) button +{ + ScreenSaverView *sv = find_saverView ((NSView *) button); + NSAssert (sv, @"no saver view"); + NSWindow *prefs = [sv configureSheet]; + + [NSApp beginSheet:prefs + modalForWindow:[sv window] + modalDelegate:self + didEndSelector:@selector(preferencesClosed:returnCode:contextInfo:) + contextInfo:nil]; + int code = [NSApp runModalForWindow:prefs]; + + /* Restart the animation if the "OK" button was hit, but not if "Cancel". + We have to restart *both* animations, because the xlockmore-style + ones will blow up if one re-inits but the other doesn't. + */ + if (code != NSCancelButton) { + [sv stopAnimation]; + [sv startAnimation]; + } +} + +- (void) preferencesClosed: (NSWindow *) sheet + returnCode: (int) returnCode + contextInfo: (void *) contextInfo +{ + [NSApp stopModalWithCode:returnCode]; +} + + +- (void)selectedSaverDidChange:(NSDictionary *)change +{ + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + NSString *module = [prefs stringForKey:@"selectedSaverName"]; + int i; + + if (! module) return; + + for (i = 0; i < [windows count]; i++) { + NSView *cv = [[windows objectAtIndex:i] contentView]; + ScreenSaverView *old_view = find_saverView (cv); + NSView *sup = [old_view superview]; + + [old_view stopAnimation]; + [old_view removeFromSuperview]; + + ScreenSaverView *new_view = [self makeSaverView:module]; + [new_view setFrame: [old_view frame]]; + [sup addSubview: new_view]; + [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + [new_view startAnimation]; + } + + NSUserDefaultsController *ctl = + [NSUserDefaultsController sharedUserDefaultsController]; + [ctl save:self]; +} + + +- (NSArray *) listSaverBundleNames +{ + NSString *dir = [[[NSBundle mainBundle] bundlePath] + stringByDeletingLastPathComponent]; + NSString *longest = 0; + NSArray *matches = 0; + NSArray *exts = [NSArray arrayWithObjects:@"saver", nil]; + unsigned int n = [dir completePathIntoString: &longest + caseSensitive: NO + matchesIntoArray: &matches + filterTypes: exts]; + if (n <= 0) { + NSLog (@"no .saver bundles found in \"%@/\"!", dir); + exit (1); + } + + int i; + NSMutableArray *result = [NSMutableArray arrayWithCapacity: n+1]; + for (i = 0; i < n; i++) + [result addObject: [[[matches objectAtIndex: i] lastPathComponent] + stringByDeletingPathExtension]]; + return result; +} + + +- (NSPopUpButton *) makeMenu +{ + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = 10; + rect.size.height = 10; + NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect + pullsDown:NO]; + int i; + float max_width = 0; + for (i = 0; i < [saverNames count]; i++) { + NSString *name = [saverNames objectAtIndex:i]; + [popup addItemWithTitle:name]; + [[popup itemWithTitle:name] setRepresentedObject:name]; + [popup sizeToFit]; + NSRect r = [popup frame]; + if (r.size.width > max_width) max_width = r.size.width; + } + + // Bind the menu to preferences, and trigger a callback when an item + // is selected. + // + NSString *key = @"values.selectedSaverName"; + NSUserDefaultsController *prefs = + [NSUserDefaultsController sharedUserDefaultsController]; + [prefs addObserver:self + forKeyPath:key + options:0 + context:@selector(selectedSaverDidChange:)]; + [popup bind:@"selectedObject" + toObject:prefs + withKeyPath:key + options:nil]; + [prefs setAppliesImmediately:YES]; + + NSRect r = [popup frame]; + r.size.width = max_width; + [popup setFrame:r]; + return popup; +} + + +/* This is called when the "selectedSaverName" pref changes, e.g., + when a menu selection is made. + */ +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + SEL dispatchSelector = (SEL)context; + if (dispatchSelector != NULL) { + [self performSelector:dispatchSelector withObject:change]; + } else { + [super observeValueForKeyPath:keyPath + ofObject:object + change:change + context:context]; + } +} + + + +- (NSWindow *) makeWindow +{ + NSRect rect; + static int count = 0; + + // make a "Preferences" button + // + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + NSButton *pb = [[NSButton alloc] initWithFrame:rect]; + [pb setTitle:@"Preferences"]; + [pb setBezelStyle:NSRoundedBezelStyle]; + [pb sizeToFit]; + + + NSRect sv_rect = rect; + sv_rect.size.width = 320; + sv_rect.size.height = 240; + ScreenSaverView *sv = [[ScreenSaverView alloc] // dummy placeholder + initWithFrame:sv_rect + isPreview:YES]; + + rect.origin.x = ([sv frame].size.width - + [pb frame].size.width) / 2; + [pb setFrameOrigin:rect.origin]; + + // grab the click + // + [pb setTarget:self]; + [pb setAction:@selector(openPreferences:)]; + + // Make a saver selection menu + // + NSPopUpButton *menu = [self makeMenu]; + rect.origin.x = 2; + rect.origin.y = 2; + [menu setFrameOrigin:rect.origin]; + + // make a box to wrap the saverView + // + rect = [sv frame]; + rect.origin.x = 0; + rect.origin.y = [pb frame].origin.y + [pb frame].size.height; + NSBox *gbox = [[NSBox alloc] initWithFrame:rect]; + rect.size.width = rect.size.height = 10; + [gbox setContentViewMargins:rect.size]; + [gbox setTitlePosition:NSNoTitle]; + [gbox addSubview:sv]; + [gbox sizeToFit]; + + // make a box to wrap the other two boxes + // + rect.origin.x = rect.origin.y = 0; + rect.size.width = [gbox frame].size.width; + rect.size.height = [gbox frame].size.height + [gbox frame].origin.y; + NSBox *pbox = [[NSBox alloc] initWithFrame:rect]; + [pbox setTitlePosition:NSNoTitle]; + [pbox setBorderType:NSNoBorder]; + [pbox addSubview:gbox]; + [pbox addSubview:menu]; + [pbox addSubview:pb]; + [pbox sizeToFit]; + + // and make a window to hold that. + // + NSScreen *screen = [NSScreen mainScreen]; + rect = [pbox frame]; + rect.origin.x = ([screen frame].size.width - rect.size.width) / 2; + rect.origin.y = ([screen frame].size.height - rect.size.height) / 2; + + rect.origin.x += rect.size.width * (count ? 0.55 : -0.55); + + NSWindow *window = [[NSWindow alloc] + initWithContentRect:rect + styleMask:(NSTitledWindowMask | + NSClosableWindowMask | + NSMiniaturizableWindowMask | + NSResizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES + screen:screen]; + [window setTitle:@"XScreenSaver"]; + [window setMinSize:[window frameRectForContentRect:rect].size]; + + [[window contentView] addSubview:pbox]; + + [sv setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + [pb setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; + [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; + [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + + char buf[100]; + sprintf (buf, "SaverDebugWindow%d", count); + [window setFrameAutosaveName: + [NSString stringWithCString:buf encoding:NSUTF8StringEncoding]]; + [window setFrameUsingName:[window frameAutosaveName]]; + + [window makeKeyAndOrderFront:window]; + + [sv startAnimation]; // this is the dummy saver + + count++; + + return window; +} + + +- (void)applicationDidFinishLaunching: (NSNotification *) notif +{ + saverNames = [[self listSaverBundleNames] retain]; + + int i, n = 2; + NSMutableArray *w = [[NSMutableArray arrayWithCapacity: n+1] retain]; + windows = w; + for (i = 0; i < n; i++) + [w addObject: [self makeWindow]]; + + [self selectedSaverDidChange:nil]; +} + + +/* When the window closes, exit (even if prefs still open.) +*/ +- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n +{ + return YES; +} + +@end diff --git a/OSX/SaverTester.plist b/OSX/SaverTester.plist new file mode 100644 index 00000000..760d1068 --- /dev/null +++ b/OSX/SaverTester.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + XScreenSaver + CFBundleIdentifier + org.jwz.xscreensaver.${EXECUTABLE_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 5.11 + CFBundleSignature + ???? + CFBundleVersion + 5.11 + LSMinimumSystemVersion + 10.4 + NSMainNibFile + SaverTester + NSPrincipalClass + NSApplication + + diff --git a/OSX/XScreenSaver.icns b/OSX/XScreenSaver.icns new file mode 100644 index 0000000000000000000000000000000000000000..15a25c7f7432b3184ac56e8b996683bf4d036e5b GIT binary patch literal 39163 zcmeI4cUTkI*T<7k#Z}j~0)fnskY1^R*gIfb%i7DTTikBJy1ULK0efBR+E&H2_X;W~ zDhO5-yCRBG4IPq93h%iSP^@cxet*0mYREI^d(S=h+>-gsVa%vs#xoczOk*ba`7#&- z@)__|34i7>$c(7_>U73E;%=Qgw{PFNb@S$p>(_~EP1CMky^@-G`Qn9?vu94m$Hg2v z5PRO~;%Npm<3VJG{rwF1qfU3cckk|9Fe7exAhT=N(rB~GmoBCxok@s49&>Qt?i~jT zb|o-oK44`$ctB(j_ZjKLJ=WbjcgWiww#Jarl`E;2FP%S|cq;x_%%Od|wr)=xev-j_ z_%QMT<01K0V`{R#3o#@zp~KruXh z;^^VMJO16U;rL0(amGBbU}UJDYvKkSce;s-5Wa-Cn0-6{-LPiI*%OjujQN@5L&gI# zgHdfl-ex1OZlIW7y_$OILh_k}qlb2HUB7zesg!u(QO1H7EeMGBGm|T+7tbY~ia)Yv z+s0K(w_OF3SjIxygvfBde?L8)9@!Ud&ZnF?adiK-jjNaZbq{R3V;GC7Y{>idX`4HD zp5;&@$e;_!r;Z)mv2oRsc{ji!&g%$c@ly+w2{>dzwFT)|sC`BTcxKBU;A_CdzdDjQpvaa(HBV}C8}%BAy(@rSnkvvl?rs@mcJW7%sh(yoA4ClBvh zziiIml}|19GnPLlA2Bis7);{XU=p|8Zr!?Z4SAIiyE}UMoMn~IZ1yo$yxOMTt((`c zUA>fY>ge8oSI(P9R60LxXD@BT%zU|_&u-qle)V$7sbl*$ubMxftYNcFoPsuQUb}qm)RA55m(GfMW+~jxSehM`<*@KDHazpGg%=pyyL0P$ zYRajYT^pCrAtwIvi<%(F*&BApr)HvwA>77ToWsh_&WXx)T$i=jb>8F01U>BD4^ve0#Y2$wy^X=%--+VXb=ieqQ-jNK}TN%r8BD0+??J|$_jHzh~j^4R> zU7PzxD)a%3`XwXIb^oeJj?x%;G5OPO&-A1 zxp(*0wM)q-4((XKe16pQsgouU<3F1)Vd9kE|0K1u=0(S;HZwH2D|4I|>ZpCvZ?cfB zYv$eCTaz;Gr{BGO1G*c#XY=YM^JZwsKYu6w_-y)gLam)KYwp5DYmN>5ml2uE$RTqV z=kPb*DlOAU)@EfrhP0S8gSYoVM*6)wH?Ced6L)ZX^y;OH=FOfpYo>Zuo!N8eFI==_ z`SK0P|1xIhEzb3dJ6&FO@%z9V+0gj1QNe>VGr{!k&9uufFZONQv~JaMaw)NlSXyV< zisj2!ty;5YXWk~pg1mXT+~k9Loo?$_WOjDegm@<;~3Xy0A~LD?c!^!=s$o zy9Tp~)xmnECm0@}hFR zPb8EV-}iacVsLv*zK9z7Pfjm_&;lzo!<3~Yg-`?GOE2mw?quve^?2KOH9dJ@`GUpk;(6HWKqKKKnNa^^N^(-- z>60hoj>aB7u>VjrW5yHK<zBL`zft8GN52!bAcD0)Wb>JU_4@9* zc>dhkGpArrk39q?QBUk1h4J_cWepYBUVZ8I)woj1-AiR2{?SvmR9aui3rf0j>Eik1 zq|+zkVvj^Km^z*IiG4D!)rDK?o3m~-xqWeZN!g`6qo>`tIWxET$=-<*;PgPG)kSBH zOBXMkKY#9A3X$CCY*NzMgwDJz*V9rj!?p`YilfmCvOuk4CG&bj>1ICpeCDH)(p`uB z((-ec8g<1n(|;iH7`a3aBb#{a0mJwJ<$ZcOY~y!s59V!wE%qt|1GfDW(F|>YgHD&i z3(ubkzBQbCue|8a!8t?o^N*dX(3PE-+l!=?>`^Yl)Ne*>(Ro%s+_uaZy;|wEOj@p z!{%}!B?;*`Af1AO89LW1Nr^e>OHDdm;huz`+$T>K=nHg(nd>{ol6lNrTL-fkFhQGU zLMLHONgv4DflgwujUcsX2BRpl(C!?sn~o@CU79gfJNQWrWQvBJV+e+{oOF5B*l zyKe^j;_C@Bn(?^U;R-@$S9bOfuSL8@(!HPo5@@x8H5oj*4B1{p6gr-#CB|xbja|p@ zcK2+RRRjuGPfv5@d2!E`5jy+Atd%V}i+BzD!}LW8AOU_L2?$RSvM(qs9ID}YaPh>+ zuFa+xbp-{58Hd!(c$nfwNdfey{Lr^-5a$3fWlLOqN3p%UQ_ok^V@s4 zZ#IHSLDBA|ZaiFmqeMp(*q7Xyqr&F!7+jLqFziug<{;i4ltZ-4?HiEFB_-No#|yk} zg~i1qG(0EQwRc4vUP3vct5Z;x@^>JI%S|a$>+DMRjCNnhYZwlT;9%a~hxFM6wv9Wt zqZ#TF@~MKFR|ivM#C7Ch$z=tk&Z%VEC=ahMcInkRhvIvSTYBPA9t>OyI+3aBdsxNS?jY%$E7HHPJnlt*#_typ>rsY{oN}tOxw|}la%AFMrv7) z2lMt-Cl7QEb*xldf;v_N9aHn1UDsB|Z|Hd8csU6@)0N%+NwoXWy&|>FvGB?IuiHL0 zX$*G8)aD$dmYtOaYOv~6d3O(jUFLX+*S(~ybOgz3hD)!Q;`g?_wx>jg#z?0tPwl5% z7gMg$Ih3TY>=RlXS;>rOM)DeQyXEEP4B;Js&Y~3@W^ej4sWOmqVfQL3ONr}{c=&#y zR_CZc(>{=RSgg_6mtWb~V=~B0Y6cE<%gf6}D)bIS%Ru+a%e7TLA))%srW=cH#C)^p z&Y^Pf2!d3nK!2o*_hOV0q|Uw~_HX}(+Dhg`&|`EX^6CxY9fWSuYdiP_30+P+l_Pkc zaoM{J1-W}ygjqM{ftX7HXcm@l>L9&Y3f>jwZs|3*O3k@jeqP>C-oYGL5FBj%gg%x# zUPj?68$s~u>Wlz;~x+g<KmqqFvi|;SbuZCvAe zu}aUSdr@Hl=slqY?T{cuPVWe6<#goi-G-N;3FuUIeogTD#AL_?M@Wp)dk>-4PN<#I z3+zo*YIVC86%`KS#lVJ+{9}O}M6M#zrl;4#NZ5O}$tebRoXcaT_+Q*vtSgAr*%cR| z^aWjAVP5lyDltZPqR1J<3UoF#W)6r|xkgJlaIvF$Wav~-yt%{Ahwc`kp3a4URSfH9 zC95(YRO%p=Y8ggOG=rr#7&K3vB6$s({bHbJbwP11p_qT6bPgRd`vT((c}bO!WB1~s zqG7yPSYUO~Kj`HEQm|R22aX9Z-@l&bid>IM8aCuSZ zUVU!^|*0oYXx#+ zkxijI_K?qmDh0^KQaez9jgMJWghmcrDUCEbf<0st*gJAPFIDbNge`$lFbk3p4Ha4n z@;49aQ(8mC9;twgJi@nE`%Dz=1p`D-tl+ymsdL->XR#@T zAv+HJB8%;i6etzfRI1MfrOM0|Iyj;rdyUQx{TB&KXXya1D=^C@!_MH?qpY+Pbcj+0 z2(ds2Hkx8eYwH-eq~S5$7QjdI^j&4N7vK(~D%N(UHJlUbN>)WTHf%9Hh%y+g<0LHv zF#>;zNQ$f>RL{%%dt$n#*Gdyof*{f3WJnP=k0htF{_Mfg*i=UFj%z@LC}tp$LJj3? zG^RnA!5b})>E%i^vv>FU*F+Dr8bn}w(cf*$_HtD>W3aW=8gbfEJD3^8NFtJQ1OWpG z%;wp1@J*&dlr1QWA>)jyZ2r7W-mvw|u!iW)fw<`BN9v>atBT5#ah zXNnJTn-B3Thngac&7+vcB@1~> zpIE{$L$9STii8774Izzb7R5X>Y%!0;|NRaLQtArSxr&O&a+`xy4Mv$X6w9z}&@vuN z{?mC9e`5atzt%z7OrG{4c(|9tJ+qZ2@@-g*K0oXWR`KJL91ttSBkE#SPGfh+Ccy`~E>fxxtpz|-v%rtBz$2mt#nK*9z zxQHo}i1BvgCQO;29?Kj*o*YYza~wZ;{Md05BgnChCr+L`VeHt6@Hb-8gt6l%{Px@5 zg&GJEJ%ekrd0Qhe2dg^gl+zP=_)YjD^J|rDz*vRp`;$Q%G3+f4OZjYgbszn?ht7lf{^( z{YP&$Kl-EB9=+K(MH~>Y(0r%0tVO51k`dV^bNe_j1j9 zy-wW8(Ce^;CVh)drh`qV%#r61RI{%;8>rQaa_Svq5*CbCY>&2y(0r8f>-kE8$D1+}I^Oe4)3jbKr%-Yw|K$tl?NoRwh^=i}f=0>W+ zWfN82PW*&Q6l77`TUF>owvk4M?E&S6p$Rr+uR^QUQYb$^zn|H%(Z9lSx5(UYxCLUY z_dR5}-G!>~KY)~5RDg1x47+V?JI|zPN14)!O{Q$I`&mjSzd(%{M(IN~f%3od5<~wT zpo})ILc&_d@8#@Z?(f1vW}{t9o{T2HTK^CvS`0AB(3!mn|M92S`U12y3AxT^;%9^@Cv`^iVefe z`T(-Lf-)Zv-YD0bA9=ZTeG)v+yqt5jlp;*7OWF?%H&Wk?D^gvgDq2r2Db*V{%QAC2 zr&K8yfpQ+dlfh~&{YogEYBcxWL|WSFgQWqU2P_q>JygB2EQehMfwRny1>;3=A#==m zoyC%cMice@g!O^~)5gvz%518%t-G>YhH<-Vpr}KM(O^J6gR)&dzpt6FIvF1xlwP;= z){>SGeTaLrwPs62t1sr}QkKJw+ng&kXNpqVk8M8JY%WjVEPZS$3*315i;Jd9^5N~u zsj{}Nk&g}3_NH!uk4YmP3?!V~M3Sem+V?wR?fnO7anZMMZSt4JQV}rKOjtfU=yk_p zHa`?RT-2=DTGHH@YJbUe*Xy1^{*Nh7`gw$js%SNyr0jPzahK(SCOVU$Fhq82_pjAz zf>4ZDV(m>5Ru@Z!-;!x(!s=-V-nlW%WIZ{^X#EZeTP=Hal;4$f{v*&|55fn;B}iCS zP#)ZHvw6!;CR5)d<_S%>?4}{5rk@%on@swUB_O=1mGPTztAhZ-sowe7!iUzwo<-J? ze_4AH)(T0o$@jR?cOM8xBVi$=APDDMrn+n~FP9n2y^k7Oc7L4O-8I#;`ir|D+!BNx zHnlMB4=9DbfF3u(+E?4vY-+W{de?Q%A?`ocAEfglnqHw4hpeMUSt@*|+`sYAasWS* zo@Th~oo6}awA6gM$=WSgQjR3UY|issV>+%VLBaSr{dxu!-Q4h)dz~^GtF;$gw_4W@ zH=84I&mINV`yDWa#zb|Dw;u1LH}$}zg5FdLS0Ljb*7eg_$_DPU=zqaDgUd?}zi`AT$3ok{6;tI3XlCQT?IMyMitCXmwB!sc&HO8?4cW+7NQ(uX%Jv#L=d z!nlAIaYj;lpduQ}Dg@@+xs@*HM%jyl`vvI%*q#Rf8~%%y&dpm0w3N$smG||hY3Y(- zfdi_D;akwslmK_O+mJ2bEfuoe@$Z8-(2nB*VjqC2#uV2LVYiZ=wI#mGmTuKU=%s5LV)lcYv zZGM@bp2fmv=w>|me7wNTwc~Up@YC)d?4~`3{b=K-cs78J7x?J_Z1cB0i=I4rj-RGr zp@^Se)J{@Qai82bR5WY^&z;S7`}SwW)G3qw*d88i*YKXXL$oB7W+FVK{-gal8Zg9Io7*?cv6sHp~sfcq%vKuJ6bNSW_IsgnNKn^~YX@ zsCCYPQ-y`UfDDN>;YfVEIAU*i54M|N`d1)>V{8xTs^@TjkGU8FE@{{oCN~m#fu({B zu+%{s%!%Wzka0M2Pr{=i+x>g+g(LI=LAHl$yMFGGAfl%KHtYn;1PE2b&}ycNgo=uW zXt1W9QlWsu6GNXMF1>zq$2bBnHkd6D*YIbjjmqA!ho(0BO#RpB3Hqr77xDyF(l#Ps_3NJ-u?>@x~J~g@b=+F zKt?AtR)cTAR|jJIo`KOSzKVlb8Hm;4NVv`9THi}+ni9~x#*mRd!7YEa3GQ|sK#8C5Up_?mp+oX(xC(Y+=4s$1hQc;9l_P+ zzo{~{m8^)y5IIIDaMnTCfojeQLtD*RQD>2VE<7=KG3?tOmA^H%IS3)^++HArgrz(0 zHl*cz_+S^&1p0!90%--kYSIdL8b+er@sdrA!0K~^Qa841uv&iSG7tPC*^OavJqI)w zd!jg^Q2KsF^VSH|;ZOiqgYbh-F=^&gIpD3skemQ_XS;h%>O7@#R1-80KeK5J7YWeh zhMB--_fcK}vKi3?^uhqm4Z;pSjixXT3ndgPs9y zLpq8*Nl0RtWi9=eBelkKri21{9fTcvnY=z#tmmopLKDhsQiq7jVND1&3q%Q+8$sU+ z1@0h7c-XlKgGN?)=o|{S>Ot{k_WD$@KF1qk1)j8?B)E~lanrEtLI|&!$$92Mh*$l=Gzsz4#tNU!n5n%>(>t(~tgIK&7+l1$FkrJouo&V6Hv2iS zSzEW^8rT$s?xaV3NJmAh0iBxB-g!VIm?)qQFg{`dLbS+9r!YXYAovPIYtz$U4ho1F zN43m7O=l|*oyEccJPuc5jW{Ac z%3Sz%7l>7!$T2O*B7GD*cOka&S@236K~s#eKPZ4D^i=wERiWQu z!0aR0Ox2Vca*ji=qs7J5;1}*v(&X6Y8-Y1O;J7nouBe;fk2{)gNSk15(cSC5-B7??!-k_UtPYn}8bxyOAwF=;5S0Z|knB&*;v2giArvF%0k~ zNIs8k)j%K|uU1FTiQ{%XE2t^TGnfjtn9X3<0oT|b&00;DXMz@5U066C1_&3_UPicB zI3QdYi3S|Ei|yQuBA6S|n8cV!(0(WgzC?3D&}ba4 z!Cc%fRaz%ErOzU^5TaaTK<_mk_HL?HZJ(gvH9_9NG%Ix&iXD3y=vIr>0kO)WK4?K9 zJ?v0lNe>qIg{GQ^yO%@=Ot;=7G$Al79H=gcy@Kjit1+7b)wRzHZ{`j%Xi-8^w&hgA zX32#o)s=SoP*=36g#p$4jZla661X;$HG0E5RIK(y_KG5VeyEBa5=Kwgffn26Ln*TtG3#wk7^D)P8I3|GM^Vk=)Pf@VRGw>OhBZsRLd%8gwT#yHBcQ#)s@SJID zHx%G1m_NsVZPo;`)?%N*!~_3DO0gHjk92c%brbkXan3@F@x9HqCI+^dKIl3hQWz7+ zR05G)u2O{d3FCV%!kDd6iE_K4SR9bt8Wb4WAPRh7-iAieB!`3He4#+m>+2AVgJH7O zQR}t;IPc7=s3nvEaoEF{UC0QC?uda8V5wj4nbf*#7ijSo>$%G+AC#ZzFOHOc<_y zjtQgdZA^GefAonW3S)`KoFl+j;KFe6b6gl*jJU9UVM`3-F7k5Vdckxe)o?X(;{`H| zuBORw*6iDnx0ttYtE=JgFzDCwaQF(=y#^c}fggV!4)bKIcFx~Ef17$MbKADLTZt_W z+t&GE$Wk>Xx1ejx@(4?vx^9`*pzbvvEtOVn|GZumF-!xYe#m` zm13&=#7&BNeDcskKrd8sCPii3Aq<~At}t9(6TS18USDzjJ^_HZq6!fA6-={a2DdG{ z9wwB8q*_i-Y}0eE*)X%MWYAG_N`TBSu=SH^3LpQVMLT74O&0^dHzl+c$hxLel)lek zy|G`*4CAiQRX2mAeu2S{t_+YUf6sYFhPKi~fb#SO>0scvy_5%v_`lhdX+peCI-tGcrlr69xMkwz z2P(gZmdBPDuQ8VCvWurBqmi;I7L@-MdQZQk2z%Uxb4~xtZj)&--|Gh|S~?-Gxisgj zbRZ~qksZ|GUx9KpA-?Y?6Mbc}^q1`;EOvKg?V%raV!VE^kfO7R&wO^7iD6xiD7roB zBuWHC{Ij=LtKvk_$OK_8Dp7(w4wc2~IiXb`7Z7oS@4Wakt=0j8(L1kO?kd^=3vC&L z57->;`}m}|{f_tt=HZfv*wg0O7$%)0?Uvt75V8BN$yDA&u8^i?2ew=&8AqLwwEXwz z9b>3WJzqSoN*ck@RRZyGt96@PF7rG6NYx7%aSSGsO8Q&k#JDU-WBHmV^zkcL!1Wij zl!Q`$Nfq*zmrV~^O4_VR?*+e^Ci8tIF-SUi z!F4Je^W>|aVoadKKX?h2sI887{YPgg+Up35OAPG5oysgnC2dj(OT7`I$riJDrf_;E z9PbK`5i~g?9d9Bfy zL#U{SaIgWuE6tz@D;LOfscgnLF>k!7 zCs#{LgV$Y@r-sS*JX7x5UViPIEZm|IbRH<|`bZ_};>(|M0{=CCdc@FIDt60V$VN?VjP{=125a>xHc))@v(PL2{d7|J{Gjn!o&{ zHFZU9`I;5$qIVXiEL*uT`fT35LW?E-k;Rg-a@8rbF^@DiIeEcY8eL&B6|Y&kX5-e} zlS_2e%7Zm|@f^UzHeSpss;smTnx~= zB(651Z|Xux?a3EQYBT<`Ha788&5~LMRMY}3e%F#(;-RLN6xWjC*VN$F*3*8xdRpyc zD5|Y0s2%n8sF<0Zy%JT^Y85}$z`bu@P5Xxedq++jnLYO%`7m8c`?Wv!T`Os6IK0Z= z2cKIL@4ihH?eZKgkl`Se-20u$Fe^6)N@0m?r-2Gzam)8&!_L{+fB47)lpuv% z(MG98c(`kKd>5dHr+D}c#k7q4g-}k*OlwcbeSBqY01*G|hXuR~AYPa^E0=k(6(RTV zm3IO_94PS~Oao#l1AeD+TBSnn6C#NQKh*pJ3EMyF zigP6k#GgwAz6yD;4^J)@`N@6S$$1*_0#O68Hy-dx9*jTZ!Cxxneu0udZ`_>fBUk!} zrqDdtshS59PwQzr;;*QuJ&W6T@L1?{kP`9Wo=WJx^w(GNVETkI5P!a&R)eYm0CLjx zv~>v(@(GlT1|IB7$XP+nzkEFp#(@XJuO+BiPm70E@nAprIMZAu+73L}2VVLI`@WV3 z_s3tZrv;s7^|T!na=D^~G#K$<6}(io61}#b7Q_&oe1Qi8!bSD8k(1R^8&2ufMLcXc zbW;J*4wyD&QV5}N0DV=@shY{!NyNma5flEf;b0u$;HpyCi&f>c)i_v#*TcL6`Ir|{ zk_Y%J37pCGC*>?zVDMtRkqB|%0r<-t*zp|AlYiCX^*Azr925C~xx9IxjKot>#P&AaQR7$vYCQu6Td z5_qAO76}v;4b|XHIZ{6%hwB5227HGoYyxsZVJ}y;=3@)-hF#yld}&sz!5zI+{s52t z33(k*@eAg|%_+GWB;+9h93%m>cOYI(dr>qYjwR}1$79D-k2&4ckK6izV1 z0_3fJ1-G2w4Vhm{cLLE~n^s;f(t3uDqZu#IUgVofDfdx%u72n%Macl)Fo1!+!E!mN zaD+W6&{y#Y)xmXLNT1TL^t^&e_8zD!c}IsJAri1-)mm*H&bE84m~8 zI~?5OD11q|OOQVQC1kXniofeTIL$`;=JD+dcu#BASzfht&u7_@&r)tncn zIqwksWzLI-1LuYC*+KZg#zijo3-Q=`S|mer3nsoo4jHKi2U$L@{{l2dV@qS+A^0l_ zZBs!GVP4!0t*6jkSx}(Qy}?34?gCQuH~dG~gnigkZ4?>@##7`Bg4Iy=Dde@uk@)9a z*t~50Yb~6(fsb-mjZ}~*ytbDs1$`0YMeYGs0mchz)p9i%Z?$3rOaY5DEvaZDU(!-a z!VrL*8a%bVs}wc|#!D+Yh9km@zeIRZ^=-AF9W0-aa`j=_2l@;Qm)QCX@324>EmN?c z^Ka3>O^rK+Bf^WnityU>)VL#B3P1#RzuBkD1P+ia@=ay>@jYXzYI;TDp%f}UDYmwp2l($@Gux=`1a!GLx_?-Z;1l#OuJkko5DNoPvt-u>cB{o)I9w>%)eeLm zrfLXi7waMyQP}*s!F26zcQ{BOn93T4AA5=GrrFfAIFr+qXyWY7^F=d2u84$4!KnI4 zFpj1PP~B>U=c%q^8r}_2-PyP^#`RUoP`haTvLzQBGyPP{bq>{w|(KMvxO7A|fVEjEEp6I>L*Hi05JK0;n8*u|T)( zjUuzc=&DOG;@HbrHhiG4&G zn_hTK86Tz_jfU(B!}X2Z_nt85D{f^xhhn9K<#CI4<+pkW1TE7nXQ#I7eb8)}-%d6( z-h9ztqEv?HNEv(zU$$Sq*mT9`CsSg3Z((Q#MH%`HHy8)CdT89=W!;@1xG*5=+8~*V z%&mc9{emjCiAGs|WSF{W*C* z<3R6rokgoC>VdEL0@a@%OHJ|KcuCNbSopVYny_7e#adv5==Uuk6gx%|rxmTY^bzfN zsQfug#wDh;xt_=yZvI1=c}9Rx>?qY40H*(nL>I2gx)*i!K7CF$(L@~+3D#4;ivFes z%g$aFBCt6?cBEJ|-lj~W*l5wPPRiSs{^FCCsXH=devd5KmN@PYmMGbkB*8bJJX#r# zAg_?Cy($gO4&&a`k3C>AE%(NMrM3vCJPDNLo);n%+e;E#z#jt2)hPC%zf?5PWcf;R z2vBUgxLpyT*f{P_mRaKSX9eFQW#qHVLU;)1m^3Ual%E19b}Y}UbxDeFbfT~~l}vlw zTN0<|^#WZ~FNjd=PdIPB)jEVfX74Rax+D~#*f{Qh9lcdqrv>fzBxIVu5luawWL|`0 z(&^H$Cq2BU97qQg8!DDcujK}`St^)BofWp)9(T{!TQYO0Xi}9lLa}Rkk_4-DmsBG1 zJDVlz4=6T{D-;U{T22VO#Hv}AZ@u_ZRpBzOpLcs{S89bsB5Hlrlo_n-$d5MlQ}mXu zqK=7ik%}<(@e&KA`)i=sbv)%&K(Pr6hKRJ;lJ*G2#&J1#w;Pr?0oG<^I!KFuK4M(V z?<4jTcP%r=%;_!u%XC;Y;AiiNrfJ?P(Q)dy7;CZg7S#vy6wa!NVt>QER|1N?uNadl zZ3D^T634}{F6AKHCJj*Rw}L+b#V+J)LUFW$nwum`rt3 zV*A4Sy{ZO^E$twc^)p%1;a8)H!|qGlmsraT@mxT$Ng$0->}bU)fSL=vw@mfIdYP}E zp>C+gm#)T2zv5|*F>>#;$GdPsn(IIb7mPRcQEJo-F%Kq zXhn-bc_0NS_FMigK(ULwcg2V}y{Z1HhYwZ18uDne$7(JYW~r?Pd`D#O0m{#Ud-=FeJ9ytGMy2$nmJ>R zuDA5!3ck7qiVdD-DGyWKy%plkldZGPkl~`1H_l7Hw~kgExhmoDykv(a@x8g;{g(S| zvK&2ZV%v|33s)5#YoOTUfMQd3ms)SG`RAn7 zYL4F#lVR1ZI%7>+lUK3+@96FOi!Q8Oy>KmplFZZ-K(UFbRM~bw zu_fzQY}mFt|MZFiYF+HBQS8Sm-JKBh^N$QhTW9pEj%$7~S@w=~>ej2@puy*F|J9&D zgZlM8b8&XGw_`GEs=f1TCd*sYp!~v##okpIr_iZ13E$=Vv zMjzq;)1mGc{J{5bztZ%(XX6iWfazGzP15=U+OISY`~U}-EXVq6MW+vGztT9!^Sy@v z+Hc_L6Z%2zSDFWVysraHmQw?cU-*Z$UuhZiz79b9pJRUAKCpdofZfRDeWu|@YX7|* zu(jX)L;GJ@X&L-p5%_5Bzo!Equl@IP;3KwQX&LhFL+~-%e;)@vYWwfuz{hR>-5vPI z?Z2}FAG`f`b>O47|BeoP{Py2%9ry(8zmo%>qWyPq;FGlf4i0>p_UY~Ttq!H1sQtHg z;8V2^4!qTo?31-$X?iOMK3)6pxd27rKR%p(!uBhTUDyrlIK0_`PuYHDvCyr7v;7-C z9DmaGE6;d;@!6Y4;M2Bm?8s?U=ZyjQwCz{^BW(8O0DR*1@2WgMcYf3Dkx$&du{p1a z%bNz^6Sr?|EoxTR;SDq36SrU4LFQi1@qgHU7o|siN7n0a3V!PThx&Lnc+(2-soM|p z{ol49?#KC`_P^WzzWvXg-mnFJ>hbU9hyA~9zx)5V|F!M^Z}YD<{x+qLJf+VfZK`K$K)_3G0HgHe0_s6BtwoaR=(ZyAOF+#KSKT0Yr9{Go%;5s-q3ty zCj}dxe|!D;hwu+U&tviK=ck9;uV|{h&79QHEb)XDy zTJOzefwuZ%PrgLqa;nYY_~5O)pP!j2hfoN y?@@pL-<9g9K)r4q$G4~sXEN>V9UL7Uoj&dtC_21dbvzS&u|ED6t^3j^kN*Qp-KKE> literal 0 HcmV?d00001 diff --git a/OSX/XScreenSaver.plist b/OSX/XScreenSaver.plist new file mode 100644 index 00000000..737647c7 --- /dev/null +++ b/OSX/XScreenSaver.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.jwz.xscreensaver.${EXECUTABLE_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + BNDL + CFBundleShortVersionString + 5.11 + CFBundleSignature + ???? + CFBundleVersion + 5.11 + LSMinimumSystemVersion + 10.4 + NSMainNibFile + SaverTester + NSPrincipalClass + XScreenSaver${EXECUTABLE_NAME}View + + diff --git a/OSX/XScreenSaverConfigSheet.h b/OSX/XScreenSaverConfigSheet.h new file mode 100644 index 00000000..0df9d591 --- /dev/null +++ b/OSX/XScreenSaverConfigSheet.h @@ -0,0 +1,38 @@ +/* xscreensaver, Copyright (c) 2006 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* XScreenSaver uses XML files to describe the user interface for configuring + the various screen savers. These files live in .../hacks/config/ and + say relatively high level things like: "there should be a checkbox + labelled "Leave Trails", and when it is checked, add the option '-trails' + to the command line when launching the program." + + This code reads that XML and constructs a Cocoa interface from it. + The Cocoa controls are hooked up to NSUserDefaultsController to save + those settings into the MacOS preferences system. The Cocoa preferences + names are the same as the resource names specified in the screenhack's + 'options' array (we use that array to map the command line switches + specified in the XML to the resource names to use). + */ + +#import +#import "jwxyz.h" + +@interface XScreenSaverConfigSheet : NSWindow +{ + NSUserDefaultsController *userDefaultsController; +} + +- (id)initWithXMLFile: (NSString *) xml_file + options: (const XrmOptionDescRec *) opts + controller: (NSUserDefaultsController *) prefs; + +@end diff --git a/OSX/XScreenSaverConfigSheet.m b/OSX/XScreenSaverConfigSheet.m new file mode 100644 index 00000000..40b638c8 --- /dev/null +++ b/OSX/XScreenSaverConfigSheet.m @@ -0,0 +1,1926 @@ +/* xscreensaver, Copyright (c) 2006-2009 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* XScreenSaver uses XML files to describe the user interface for configuring + the various screen savers. These files live in .../hacks/config/ and + say relatively high level things like: "there should be a checkbox + labelled "Leave Trails", and when it is checked, add the option '-trails' + to the command line when launching the program." + + This code reads that XML and constructs a Cocoa interface from it. + The Cocoa controls are hooked up to NSUserDefaultsController to save + those settings into the MacOS preferences system. The Cocoa preferences + names are the same as the resource names specified in the screenhack's + 'options' array (we use that array to map the command line switches + specified in the XML to the resource names to use). + */ + +#import "XScreenSaverConfigSheet.h" + +#import "jwxyz.h" +#import "InvertedSlider.h" +#import + +@implementation XScreenSaverConfigSheet + +#define LEFT_MARGIN 20 // left edge of window +#define COLUMN_SPACING 10 // gap between e.g. labels and text fields +#define LEFT_LABEL_WIDTH 70 // width of all left labels +#define LINE_SPACING 10 // leading between each line + +// redefine these since they don't work when not inside an ObjC method +#undef NSAssert +#undef NSAssert1 +#undef NSAssert2 +#undef NSAssert3 +#define NSAssert(CC,S) do { if (!(CC)) { NSLog(S); }} while(0) +#define NSAssert1(CC,S,A) do { if (!(CC)) { NSLog(S,A); }} while(0) +#define NSAssert2(CC,S,A,B) do { if (!(CC)) { NSLog(S,A,B); }} while(0) +#define NSAssert3(CC,S,A,B,C) do { if (!(CC)) { NSLog(S,A,B,C); }} while(0) + + +/* Given a command-line option, returns the corresponding resource name. + Any arguments in the switch string are ignored (e.g., "-foo x"). + */ +static NSString * +switch_to_resource (NSString *cmdline_switch, const XrmOptionDescRec *opts, + NSString **val_ret) +{ + char buf[255]; + char *tail = 0; + NSAssert(cmdline_switch, @"cmdline switch is null"); + if (! [cmdline_switch getCString:buf maxLength:sizeof(buf) + encoding:NSUTF8StringEncoding]) { + NSAssert1(0, @"unable to convert %@", cmdline_switch); + abort(); + } + char *s = strpbrk(buf, " \t\r\n"); + if (s && *s) { + *s = 0; + tail = s+1; + while (*tail && (*tail == ' ' || *tail == '\t')) + tail++; + } + + while (opts[0].option) { + if (!strcmp (opts[0].option, buf)) { + const char *ret = 0; + + if (opts[0].argKind == XrmoptionNoArg) { + if (tail && *tail) + NSAssert1 (0, @"expected no args to switch: \"%@\"", + cmdline_switch); + ret = opts[0].value; + } else { + if (!tail || !*tail) + NSAssert1 (0, @"expected args to switch: \"%@\"", + cmdline_switch); + ret = tail; + } + + if (val_ret) + *val_ret = (ret + ? [NSString stringWithCString:ret + encoding:NSUTF8StringEncoding] + : 0); + + const char *res = opts[0].specifier; + while (*res && (*res == '.' || *res == '*')) + res++; + return [NSString stringWithCString:res + encoding:NSUTF8StringEncoding]; + } + opts++; + } + + NSAssert1 (0, @"\"%@\" not present in options", cmdline_switch); + abort(); +} + + +/* Connects a control (checkbox, etc) to the corresponding preferences key. + */ +static void +bind_resource_to_preferences (NSUserDefaultsController *prefs, + NSObject *control, + NSString *pref_key, + const XrmOptionDescRec *opts) +{ + NSString *bindto = ([control isKindOfClass:[NSPopUpButton class]] + ? @"selectedObject" + : ([control isKindOfClass:[NSMatrix class]] + ? @"selectedIndex" + : @"value")); + [control bind:bindto + toObject:prefs + withKeyPath:[@"values." stringByAppendingString: pref_key] + options:nil]; + +# if 0 // #### + NSObject *def = [[prefs defaults] objectForKey:pref_key]; + NSString *s = [NSString stringWithFormat:@"bind: \"%@\"", pref_key]; + s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0]; + s = [NSString stringWithFormat:@"%@ = \"%@\"", s, def]; + s = [s stringByPaddingToLength:28 withString:@" " startingAtIndex:0]; + NSLog (@"%@ %@/%@", s, [def class], [control class]); +# endif +} + +static void +bind_switch_to_preferences (NSUserDefaultsController *prefs, + NSObject *control, + NSString *cmdline_switch, + const XrmOptionDescRec *opts) +{ + NSString *pref_key = switch_to_resource (cmdline_switch, opts, 0); + bind_resource_to_preferences (prefs, control, pref_key, opts); +} + + +/* Parse the attributes of an XML tag into a dictionary. + For input, the dictionary should have as attributes the keys, each + with @"" as their value. + On output, the dictionary will set the keys to the values specified, + and keys that were not specified will not be present in the dictionary. + Warnings are printed if there are duplicate or unknown attributes. + */ +static void +parse_attrs (NSMutableDictionary *dict, NSXMLNode *node) +{ + NSArray *attrs = [(NSXMLElement *) node attributes]; + int n = [attrs count]; + int i; + + // For each key in the dictionary, fill in the dict with the corresponding + // value. The value @"" is assumed to mean "un-set". Issue a warning if + // an attribute is specified twice. + // + for (i = 0; i < n; i++) { + NSXMLNode *attr = [attrs objectAtIndex:i]; + NSString *key = [attr name]; + NSString *val = [attr objectValue]; + NSString *old = [dict objectForKey:key]; + + if (! old) { + NSAssert2 (0, @"unknown attribute \"%@\" in \"%@\"", key, [node name]); + } else if ([old length] != 0) { + NSAssert2 (0, @"duplicate %@: \"%@\", \"%@\"", old, val); + } else { + [dict setValue:val forKey:key]; + } + } + + // Remove from the dictionary any keys whose value is still @"", + // meaning there was no such attribute specified. + // + NSArray *keys = [dict allKeys]; + n = [keys count]; + for (i = 0; i < n; i++) { + NSString *key = [keys objectAtIndex:i]; + NSString *val = [dict objectForKey:key]; + if ([val length] == 0) + [dict removeObjectForKey:key]; + } +} + + +/* Creates a label: an un-editable NSTextField displaying the given text. + */ +static NSTextField * +make_label (NSString *text) +{ + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + NSTextField *lab = [[NSTextField alloc] initWithFrame:rect]; + [lab setSelectable:NO]; + [lab setEditable:NO]; + [lab setBezeled:NO]; + [lab setDrawsBackground:NO]; + [lab setStringValue:text]; + [lab sizeToFit]; + return lab; +} + + +static NSView * +last_child (NSView *parent) +{ + NSArray *kids = [parent subviews]; + int nkids = [kids count]; + if (nkids == 0) + return 0; + else + return [kids objectAtIndex:nkids-1]; +} + + +/* Add the child as a subview of the parent, positioning it immediately + below or to the right of the previously-added child of that view. + */ +static void +place_child (NSView *parent, NSView *child, BOOL right_p) +{ + NSRect rect = [child frame]; + NSView *last = last_child (parent); + if (!last) { + rect.origin.x = LEFT_MARGIN; + rect.origin.y = [parent frame].size.height - rect.size.height + - LINE_SPACING; + } else if (right_p) { + rect = [last frame]; + rect.origin.x += rect.size.width + COLUMN_SPACING; + } else { + rect = [last frame]; + rect.origin.x = LEFT_MARGIN; + rect.origin.y -= [child frame].size.height + LINE_SPACING; + } + [child setFrameOrigin:rect.origin]; + [parent addSubview:child]; +} + + +static void traverse_children (NSUserDefaultsController *, + const XrmOptionDescRec *, + NSView *, NSXMLNode *); + + +/* Creates the checkbox (NSButton) described by the given XML node. + */ +static void +make_checkbox (NSUserDefaultsController *prefs, + const XrmOptionDescRec *opts, NSView *parent, NSXMLNode *node) +{ + NSMutableDictionary *dict = + [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"", @"id", + @"", @"_label", + @"", @"arg-set", + @"", @"arg-unset", + nil]; + parse_attrs (dict, node); + NSString *label = [dict objectForKey:@"_label"]; + NSString *arg_set = [dict objectForKey:@"arg-set"]; + NSString *arg_unset = [dict objectForKey:@"arg-unset"]; + + if (!label) { + NSAssert1 (0, @"no _label in %@", [node name]); + return; + } + if (!arg_set && !arg_unset) { + NSAssert1 (0, @"neither arg-set nor arg-unset provided in \"%@\"", + label); + } + if (arg_set && arg_unset) { + NSAssert1 (0, @"only one of arg-set and arg-unset may be used in \"%@\"", + label); + } + + // sanity-check the choice of argument names. + // + if (arg_set && ([arg_set hasPrefix:@"-no-"] || + [arg_set hasPrefix:@"--no-"])) + NSLog (@"arg-set should not be a \"no\" option in \"%@\": %@", + label, arg_set); + if (arg_unset && (![arg_unset hasPrefix:@"-no-"] && + ![arg_unset hasPrefix:@"--no-"])) + NSLog(@"arg-unset should be a \"no\" option in \"%@\": %@", + label, arg_unset); + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + + NSButton *button = [[NSButton alloc] initWithFrame:rect]; + [button setButtonType:([[node name] isEqualToString:@"radio"] + ? NSRadioButton + : NSSwitchButton)]; + [button setTitle:label]; + [button sizeToFit]; + place_child (parent, button, NO); + + bind_switch_to_preferences (prefs, button, + (arg_set ? arg_set : arg_unset), + opts); + [button release]; +} + + +/* Creates the NSTextField described by the given XML node. +*/ +static void +make_text_field (NSUserDefaultsController *prefs, + const XrmOptionDescRec *opts, NSView *parent, NSXMLNode *node, + BOOL no_label_p) +{ + NSMutableDictionary *dict = + [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"", @"id", + @"", @"_label", + @"", @"arg", + nil]; + parse_attrs (dict, node); + NSString *label = [dict objectForKey:@"_label"]; + NSString *arg = [dict objectForKey:@"arg"]; + + if (!label && !no_label_p) { + NSAssert1 (0, @"no _label in %@", [node name]); + return; + } + + NSAssert1 (arg, @"no arg in %@", label); + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + + NSTextField *txt = [[NSTextField alloc] initWithFrame:rect]; + + // make the default size be around 30 columns; a typical value for + // these text fields is "xscreensaver-text --cols 40". + // + [txt setStringValue:@"123456789 123456789 123456789 "]; + [txt sizeToFit]; + [[txt cell] setWraps:NO]; + [[txt cell] setScrollable:YES]; + [txt setStringValue:@""]; + + if (label) { + NSTextField *lab = make_label (label); + place_child (parent, lab, NO); + [lab release]; + } + + place_child (parent, txt, (label ? YES : NO)); + + bind_switch_to_preferences (prefs, txt, arg, opts); + [txt release]; +} + + +/* Creates the NSTextField described by the given XML node, + and hooks it up to a Choose button and a file selector widget. +*/ +static void +make_file_selector (NSUserDefaultsController *prefs, + const XrmOptionDescRec *opts, + NSView *parent, NSXMLNode *node, + BOOL dirs_only_p, + BOOL no_label_p) +{ + NSMutableDictionary *dict = + [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"", @"id", + @"", @"_label", + @"", @"arg", + nil]; + parse_attrs (dict, node); + NSString *label = [dict objectForKey:@"_label"]; + NSString *arg = [dict objectForKey:@"arg"]; + + if (!label && !no_label_p) { + NSAssert1 (0, @"no _label in %@", [node name]); + return; + } + + NSAssert1 (arg, @"no arg in %@", label); + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + + NSTextField *txt = [[NSTextField alloc] initWithFrame:rect]; + + // make the default size be around 20 columns. + // + [txt setStringValue:@"123456789 123456789 "]; + [txt sizeToFit]; + [txt setSelectable:YES]; + [txt setEditable:NO]; + [txt setBezeled:NO]; + [txt setDrawsBackground:NO]; + [[txt cell] setWraps:NO]; + [[txt cell] setScrollable:YES]; + [[txt cell] setLineBreakMode:NSLineBreakByTruncatingHead]; + [txt setStringValue:@""]; + + NSTextField *lab = 0; + if (label) { + lab = make_label (label); + place_child (parent, lab, NO); + [lab release]; + } + + place_child (parent, txt, (label ? YES : NO)); + + bind_switch_to_preferences (prefs, txt, arg, opts); + [txt release]; + + // Make the text field be the same height as the label. + if (lab) { + rect = [txt frame]; + rect.size.height = [lab frame].size.height; + [txt setFrame:rect]; + } + + // Now put a "Choose" button next to it. + // + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + NSButton *choose = [[NSButton alloc] initWithFrame:rect]; + [choose setTitle:@"Choose..."]; + [choose setBezelStyle:NSRoundedBezelStyle]; + [choose sizeToFit]; + + place_child (parent, choose, YES); + + // center the Choose button around the midpoint of the text field. + rect = [choose frame]; + rect.origin.y = ([txt frame].origin.y + + (([txt frame].size.height - rect.size.height) / 2)); + [choose setFrameOrigin:rect.origin]; + + [choose setTarget:[parent window]]; + if (dirs_only_p) + [choose setAction:@selector(chooseClickedDirs:)]; + else + [choose setAction:@selector(chooseClicked:)]; + + [choose release]; +} + + +/* Runs a modal file selector and sets the text field's value to the + selected file or directory. + */ +static void +do_file_selector (NSTextField *txt, BOOL dirs_p) +{ + NSOpenPanel *panel = [NSOpenPanel openPanel]; + [panel setAllowsMultipleSelection:NO]; + [panel setCanChooseFiles:!dirs_p]; + [panel setCanChooseDirectories:dirs_p]; + + NSString *file = [txt stringValue]; + if ([file length] <= 0) { + file = NSHomeDirectory(); + if (dirs_p) + file = [file stringByAppendingPathComponent:@"Pictures"]; + } + +// NSString *dir = [file stringByDeletingLastPathComponent]; + + int result = [panel runModalForDirectory:file //dir + file:nil //[file lastPathComponent] + types:nil]; + if (result == NSOKButton) { + NSArray *files = [panel filenames]; + file = ([files count] > 0 ? [files objectAtIndex:0] : @""); + file = [file stringByAbbreviatingWithTildeInPath]; + [txt setStringValue:file]; + + // Fuck me! Just setting the value of the NSTextField does not cause + // that to end up in the preferences! + // + NSDictionary *dict = [txt infoForBinding:@"value"]; + NSUserDefaultsController *prefs = [dict objectForKey:@"NSObservedObject"]; + NSString *path = [dict objectForKey:@"NSObservedKeyPath"]; + if ([path hasPrefix:@"values."]) // WTF. + path = [path substringFromIndex:7]; + [[prefs values] setValue:file forKey:path]; + +#if 0 + // make sure the end of the string is visible. + NSText *fe = [[txt window] fieldEditor:YES forObject:txt]; + NSRange range; + range.location = [file length]-3; + range.length = 1; + if (! [[txt window] makeFirstResponder:[txt window]]) + [[txt window] endEditingFor:nil]; +// [[txt window] makeFirstResponder:nil]; + [fe setSelectedRange:range]; +// [tv scrollRangeToVisible:range]; +// [txt setNeedsDisplay:YES]; +// [[txt cell] setNeedsDisplay:YES]; +// [txt selectAll:txt]; +#endif + } +} + +/* Returns the NSTextField that is to the left of or above the NSButton. + */ +static NSTextField * +find_text_field_of_button (NSButton *button) +{ + NSView *parent = [button superview]; + NSArray *kids = [parent subviews]; + int nkids = [kids count]; + int i; + NSTextField *f = 0; + for (i = 0; i < nkids; i++) { + NSObject *kid = [kids objectAtIndex:i]; + if ([kid isKindOfClass:[NSTextField class]]) { + f = (NSTextField *) kid; + } else if (kid == button) { + if (! f) abort(); + return f; + } + } + abort(); +} + + +- (void) chooseClicked:(NSObject *)arg +{ + NSButton *choose = (NSButton *) arg; + NSTextField *txt = find_text_field_of_button (choose); + do_file_selector (txt, NO); +} + +- (void) chooseClickedDirs:(NSObject *)arg +{ + NSButton *choose = (NSButton *) arg; + NSTextField *txt = find_text_field_of_button (choose); + do_file_selector (txt, YES); +} + + +/* Creates the number selection control described by the given XML node. + If "type=slider", it's an NSSlider. + If "type=spinbutton", it's a text field with up/down arrows next to it. +*/ +static void +make_number_selector (NSUserDefaultsController *prefs, + const XrmOptionDescRec *opts, + NSView *parent, NSXMLNode *node) +{ + NSMutableDictionary *dict = + [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"", @"id", + @"", @"_label", + @"", @"_low-label", + @"", @"_high-label", + @"", @"type", + @"", @"arg", + @"", @"low", + @"", @"high", + @"", @"default", + @"", @"convert", + nil]; + parse_attrs (dict, node); + NSString *label = [dict objectForKey:@"_label"]; + NSString *low_label = [dict objectForKey:@"_low-label"]; + NSString *high_label = [dict objectForKey:@"_high-label"]; + NSString *type = [dict objectForKey:@"type"]; + NSString *arg = [dict objectForKey:@"arg"]; + NSString *low = [dict objectForKey:@"low"]; + NSString *high = [dict objectForKey:@"high"]; + NSString *def = [dict objectForKey:@"default"]; + NSString *cvt = [dict objectForKey:@"convert"]; + + NSAssert1 (arg, @"no arg in %@", label); + NSAssert1 (type, @"no type in %@", label); + + if (! low) { + NSAssert1 (0, @"no low in %@", [node name]); + return; + } + if (! high) { + NSAssert1 (0, @"no high in %@", [node name]); + return; + } + if (! def) { + NSAssert1 (0, @"no default in %@", [node name]); + return; + } + if (cvt && ![cvt isEqualToString:@"invert"]) { + NSAssert1 (0, @"if provided, \"convert\" must be \"invert\" in %@", + label); + } + + // If either the min or max field contains a decimal point, then this + // option may have a floating point value; otherwise, it is constrained + // to be an integer. + // + NSCharacterSet *dot = + [NSCharacterSet characterSetWithCharactersInString:@"."]; + BOOL float_p = ([low rangeOfCharacterFromSet:dot].location != NSNotFound || + [high rangeOfCharacterFromSet:dot].location != NSNotFound); + + if ([type isEqualToString:@"slider"]) { + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = 150; + rect.size.height = 23; // apparent min height for slider with ticks... + NSSlider *slider; + if (cvt) + slider = [[InvertedSlider alloc] initWithFrame:rect]; + else + slider = [[NSSlider alloc] initWithFrame:rect]; + + [slider setMaxValue:[high doubleValue]]; + [slider setMinValue:[low doubleValue]]; + + int range = [slider maxValue] - [slider minValue] + 1; + int range2 = range; + int max_ticks = 21; + while (range2 > max_ticks) + range2 /= 10; + + // If we have elided ticks, leave it at the max number of ticks. + if (range != range2 && range2 < max_ticks) + range2 = max_ticks; + + // If it's a float, always display the max number of ticks. + if (float_p && range2 < max_ticks) + range2 = max_ticks; + + [slider setNumberOfTickMarks:range2]; + + [slider setAllowsTickMarkValuesOnly: + (range == range2 && // we are showing the actual number of ticks + !float_p)]; // and we want integer results + + // #### Note: when the slider's range is large enough that we aren't + // showing all possible ticks, the slider's value is not constrained + // to be an integer, even though it should be... + // Maybe we need to use a value converter or something? + + if (label) { + NSTextField *lab = make_label (label); + place_child (parent, lab, NO); + [lab release]; + } + + if (low_label) { + NSTextField *lab = make_label (low_label); + [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + [lab setAlignment:1]; // right aligned + rect = [lab frame]; + if (rect.size.width < LEFT_LABEL_WIDTH) + rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size + rect.size.height = [slider frame].size.height; + [lab setFrame:rect]; + place_child (parent, lab, NO); + [lab release]; + } + + place_child (parent, slider, (low_label ? YES : NO)); + + if (! low_label) { + rect = [slider frame]; + if (rect.origin.x < LEFT_LABEL_WIDTH) + rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled sliders line up too + [slider setFrame:rect]; + } + + if (high_label) { + NSTextField *lab = make_label (high_label); + [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + rect = [lab frame]; + rect.size.height = [slider frame].size.height; + [lab setFrame:rect]; + place_child (parent, lab, YES); + [lab release]; + } + + bind_switch_to_preferences (prefs, slider, arg, opts); + [slider release]; + + } else if ([type isEqualToString:@"spinbutton"]) { + + if (! label) { + NSAssert1 (0, @"no _label in spinbutton %@", [node name]); + return; + } + NSAssert1 (!low_label, + @"low-label not allowed in spinbutton \"%@\"", [node name]); + NSAssert1 (!high_label, + @"high-label not allowed in spinbutton \"%@\"", [node name]); + NSAssert1 (!cvt, @"convert not allowed in spinbutton \"%@\"", + [node name]); + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + + NSTextField *txt = [[NSTextField alloc] initWithFrame:rect]; + [txt setStringValue:@"0000.0"]; + [txt sizeToFit]; + [txt setStringValue:@""]; + + if (label) { + NSTextField *lab = make_label (label); + //[lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + [lab setAlignment:1]; // right aligned + rect = [lab frame]; + if (rect.size.width < LEFT_LABEL_WIDTH) + rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size + rect.size.height = [txt frame].size.height; + [lab setFrame:rect]; + place_child (parent, lab, NO); + [lab release]; + } + + place_child (parent, txt, (label ? YES : NO)); + + if (! label) { + rect = [txt frame]; + if (rect.origin.x < LEFT_LABEL_WIDTH) + rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled spinbtns line up + [txt setFrame:rect]; + } + + rect.size.width = rect.size.height = 10; + NSStepper *step = [[NSStepper alloc] initWithFrame:rect]; + [step sizeToFit]; + place_child (parent, step, YES); + rect = [step frame]; + rect.origin.x -= COLUMN_SPACING; // this one goes close + rect.origin.y += ([txt frame].size.height - rect.size.height) / 2; + [step setFrame:rect]; + + [step setMinValue:[low doubleValue]]; + [step setMaxValue:[high doubleValue]]; + [step setAutorepeat:YES]; + [step setValueWraps:NO]; + + double range = [high doubleValue] - [low doubleValue]; + if (range < 1.0) + [step setIncrement:range / 10.0]; + else if (range >= 500) + [step setIncrement:range / 100.0]; + else + [step setIncrement:1.0]; + + NSNumberFormatter *fmt = [[[NSNumberFormatter alloc] init] autorelease]; + [fmt setFormatterBehavior:NSNumberFormatterBehavior10_4]; + [fmt setNumberStyle:NSNumberFormatterDecimalStyle]; + [fmt setMinimum:[NSNumber numberWithDouble:[low doubleValue]]]; + [fmt setMaximum:[NSNumber numberWithDouble:[high doubleValue]]]; + [fmt setMinimumFractionDigits: (float_p ? 1 : 0)]; + [fmt setMaximumFractionDigits: (float_p ? 2 : 0)]; + + [fmt setGeneratesDecimalNumbers:float_p]; + [[txt cell] setFormatter:fmt]; + + + bind_switch_to_preferences (prefs, step, arg, opts); + bind_switch_to_preferences (prefs, txt, arg, opts); + + [step release]; + [txt release]; + + } else { + NSAssert2 (0, @"unknown type \"%@\" in \"%@\"", type, label); + } +} + + +static void +set_menu_item_object (NSMenuItem *item, NSObject *obj) +{ + /* If the object associated with this menu item looks like a boolean, + store an NSNumber instead of an NSString, since that's what + will be in the preferences (due to similar logic in PrefsReader). + */ + if ([obj isKindOfClass:[NSString class]]) { + NSString *string = (NSString *) obj; + if (NSOrderedSame == [string caseInsensitiveCompare:@"true"] || + NSOrderedSame == [string caseInsensitiveCompare:@"yes"]) + obj = [NSNumber numberWithBool:YES]; + else if (NSOrderedSame == [string caseInsensitiveCompare:@"false"] || + NSOrderedSame == [string caseInsensitiveCompare:@"no"]) + obj = [NSNumber numberWithBool:NO]; + else + obj = string; + } + + [item setRepresentedObject:obj]; + //NSLog (@"menu item \"%@\" = \"%@\" %@", [item title], obj, [obj class]); +} + + +/* Creates the popup menu described by the given XML node (and its children). +*/ +static void +make_option_menu (NSUserDefaultsController *prefs, + const XrmOptionDescRec *opts, + NSView *parent, NSXMLNode *node) +{ + NSArray *children = [node children]; + int i, count = [children count]; + + if (count <= 0) { + NSAssert1 (0, @"no menu items in \"%@\"", [node name]); + return; + } + + // get the "id" attribute off the \n"); + } + else if (p->type == DESCRIPTION) + { + if (p->string) + fprintf (out, " %s\n", p->string); + fprintf (out, "\n"); + } +} +#endif /* 0 */ + + +/* Like xmlGetProp() but parses a float out of the string. + If the number was expressed as a float and not an integer + (that is, the string contained a decimal point) then + `floatp' is set to TRUE. Otherwise, it is unchanged. + */ +static float +xml_get_float (xmlNodePtr node, const xmlChar *name, gboolean *floatpP) +{ + const char *s = (char *) xmlGetProp (node, name); + float f; + char c; + if (!s || 1 != sscanf (s, "%f %c", &f, &c)) + return 0; + else + { + if (strchr (s, '.')) *floatpP = TRUE; + return f; + } +} + + +static void sanity_check_parameter (const char *filename, + const xmlChar *node_name, + parameter *p); +static void sanity_check_text_node (const char *filename, + const xmlNodePtr node); +static void sanity_check_menu_options (const char *filename, + const xmlChar *node_name, + parameter *p); + +/* Allocates and returns a new `parameter' object based on the + properties in the given XML node. Returns 0 if there's nothing + to create (comment, or unknown tag.) + */ +static parameter * +make_parameter (const char *filename, xmlNodePtr node) +{ + parameter *p; + const char *name = (char *) node->name; + const char *convert; + gboolean floatp = FALSE; + + if (node->type == XML_COMMENT_NODE) + return 0; + + p = calloc (1, sizeof(*p)); + + if (!name) abort(); + else if (!strcmp (name, "command")) p->type = COMMAND; + else if (!strcmp (name, "fullcommand")) p->type = COMMAND; + else if (!strcmp (name, "_description")) p->type = DESCRIPTION; + else if (!strcmp (name, "fakepreview")) p->type = FAKEPREVIEW; + else if (!strcmp (name, "fake")) p->type = FAKE; + else if (!strcmp (name, "boolean")) p->type = BOOLEAN; + else if (!strcmp (name, "string")) p->type = STRING; + else if (!strcmp (name, "file")) p->type = FILENAME; + else if (!strcmp (name, "number")) p->type = SPINBUTTON; + else if (!strcmp (name, "select")) p->type = SELECT; + + else if (!strcmp (name, "xscreensaver-text") || /* these are ignored in X11 */ + !strcmp (name, "xscreensaver-image")) /* (they are used in Cocoa) */ + { + free (p); + return 0; + } + else if (node->type == XML_TEXT_NODE) + { + sanity_check_text_node (filename, node); + free (p); + return 0; + } + else + { + if (debug_p) + fprintf (stderr, "%s: WARNING: %s: unknown tag: \"%s\"\n", + blurb(), filename, name); + free (p); + return 0; + } + + if (p->type == SPINBUTTON) + { + const char *type = (char *) xmlGetProp (node, (xmlChar *) "type"); + if (!type || !strcmp (type, "spinbutton")) p->type = SPINBUTTON; + else if (!strcmp (type, "slider")) p->type = SLIDER; + else + { + if (debug_p) + fprintf (stderr, "%s: WARNING: %s: unknown %s type: \"%s\"\n", + blurb(), filename, name, type); + free (p); + return 0; + } + } + else if (p->type == DESCRIPTION) + { + if (node->xmlChildrenNode && + node->xmlChildrenNode->type == XML_TEXT_NODE && + !node->xmlChildrenNode->next) + p->string = (xmlChar *) + strdup ((char *) node->xmlChildrenNode->content); + } + + p->id = xmlGetProp (node, (xmlChar *) "id"); + p->label = xmlGetProp (node, (xmlChar *) "_label"); + p->low_label = xmlGetProp (node, (xmlChar *) "_low-label"); + p->high_label = xmlGetProp (node, (xmlChar *) "_high-label"); + p->low = xml_get_float (node, (xmlChar *) "low", &floatp); + p->high = xml_get_float (node, (xmlChar *) "high", &floatp); + p->value = xml_get_float (node, (xmlChar *) "default", &floatp); + p->integer_p = !floatp; + convert = (char *) xmlGetProp (node, (xmlChar *) "convert"); + p->invert_p = (convert && !strcmp (convert, "invert")); + p->arg = xmlGetProp (node, (xmlChar *) "arg"); + p->arg_set = xmlGetProp (node, (xmlChar *) "arg-set"); + p->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset"); + + /* Check for missing decimal point */ + if (debug_p && + p->integer_p && + (p->high != p->low) && + (p->high - p->low) <= 1) + fprintf (stderr, + "%s: WARNING: %s: %s: range [%.1f, %.1f] shouldn't be integral!\n", + blurb(), filename, p->id, + p->low, p->high); + + if (p->type == SELECT) + { + xmlNodePtr kids; + for (kids = node->xmlChildrenNode; kids; kids = kids->next) + { + parameter *s = make_select_option (filename, kids); + if (s) + p->options = g_list_append (p->options, s); + } + } + + sanity_check_parameter (filename, (const xmlChar *) name, p); + + return p; +} + + +/* Allocates and returns a new SELECT_OPTION `parameter' object based + on the properties in the given XML node. Returns 0 if there's nothing + to create (comment, or unknown tag.) + */ +static parameter * +make_select_option (const char *filename, xmlNodePtr node) +{ + if (node->type == XML_COMMENT_NODE) + return 0; + else if (node->type == XML_TEXT_NODE) + { + sanity_check_text_node (filename, node); + return 0; + } + else if (node->type != XML_ELEMENT_NODE) + { + if (debug_p) + fprintf (stderr, + "%s: WARNING: %s: %s: unexpected child tag type %d\n", + blurb(), filename, node->name, (int)node->type); + return 0; + } + else if (strcmp ((char *) node->name, "option")) + { + if (debug_p) + fprintf (stderr, + "%s: WARNING: %s: %s: child not an option tag: \"%s\"\n", + blurb(), filename, node->name, node->name); + return 0; + } + else + { + parameter *s = calloc (1, sizeof(*s)); + + s->type = SELECT_OPTION; + s->id = xmlGetProp (node, (xmlChar *) "id"); + s->label = xmlGetProp (node, (xmlChar *) "_label"); + s->arg_set = xmlGetProp (node, (xmlChar *) "arg-set"); + s->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset"); + + sanity_check_parameter (filename, node->name, s); + return s; + } +} + + +/* Rudimentary check to make sure someone hasn't typed "arg-set=" + when they should have typed "arg=", etc. + */ +static void +sanity_check_parameter (const char *filename, const xmlChar *node_name, + parameter *p) +{ + struct { + gboolean id; + gboolean label; + gboolean string; + gboolean low_label; + gboolean high_label; + gboolean low; + gboolean high; + gboolean value; + gboolean arg; + gboolean invert_p; + gboolean arg_set; + gboolean arg_unset; + } allowed, require; + + memset (&allowed, 0, sizeof (allowed)); + memset (&require, 0, sizeof (require)); + + switch (p->type) + { + case COMMAND: + allowed.arg = TRUE; + require.arg = TRUE; + break; + case FAKE: + break; + case DESCRIPTION: + allowed.string = TRUE; + break; + case FAKEPREVIEW: + break; + case STRING: + allowed.id = TRUE; + require.id = TRUE; + allowed.label = TRUE; + require.label = TRUE; + allowed.arg = TRUE; + require.arg = TRUE; + break; + case FILENAME: + allowed.id = TRUE; + require.id = TRUE; + allowed.label = TRUE; + allowed.arg = TRUE; + require.arg = TRUE; + break; + case SLIDER: + allowed.id = TRUE; + require.id = TRUE; + allowed.label = TRUE; + allowed.low_label = TRUE; + allowed.high_label = TRUE; + allowed.arg = TRUE; + require.arg = TRUE; + allowed.low = TRUE; + /* require.low = TRUE; -- may be 0 */ + allowed.high = TRUE; + /* require.high = TRUE; -- may be 0 */ + allowed.value = TRUE; + /* require.value = TRUE; -- may be 0 */ + allowed.invert_p = TRUE; + break; + case SPINBUTTON: + allowed.id = TRUE; + require.id = TRUE; + allowed.label = TRUE; + allowed.arg = TRUE; + require.arg = TRUE; + allowed.low = TRUE; + /* require.low = TRUE; -- may be 0 */ + allowed.high = TRUE; + /* require.high = TRUE; -- may be 0 */ + allowed.value = TRUE; + /* require.value = TRUE; -- may be 0 */ + allowed.invert_p = TRUE; + break; + case BOOLEAN: + allowed.id = TRUE; + require.id = TRUE; + allowed.label = TRUE; + allowed.arg_set = TRUE; + allowed.arg_unset = TRUE; + break; + case SELECT: + allowed.id = TRUE; + require.id = TRUE; + break; + case SELECT_OPTION: + allowed.id = TRUE; + allowed.label = TRUE; + require.label = TRUE; + allowed.arg_set = TRUE; + break; + default: + abort(); + break; + } + +# define WARN(STR) \ + fprintf (stderr, "%s: %s: " STR " in <%s%s id=\"%s\">\n", \ + blurb(), filename, node_name, \ + (!strcmp((char *) node_name, "number") \ + ? (p->type == SPINBUTTON ? " type=spinbutton" : " type=slider")\ + : ""), \ + (p->id ? (char *) p->id : "")) +# define CHECK(SLOT,NAME) \ + if (p->SLOT && !allowed.SLOT) \ + WARN ("\"" NAME "\" is not a valid option"); \ + if (!p->SLOT && require.SLOT) \ + WARN ("\"" NAME "\" is required") + + CHECK (id, "id"); + CHECK (label, "_label"); + CHECK (string, "(body text)"); + CHECK (low_label, "_low-label"); + CHECK (high_label, "_high-label"); + CHECK (low, "low"); + CHECK (high, "high"); + CHECK (value, "default"); + CHECK (arg, "arg"); + CHECK (invert_p, "convert"); + CHECK (arg_set, "arg-set"); + CHECK (arg_unset, "arg-unset"); +# undef CHECK +# undef WARN + + if (p->type == SELECT) + sanity_check_menu_options (filename, node_name, p); +} + + +static void +sanity_check_menu_options (const char *filename, const xmlChar *node_name, + parameter *p) +{ + GList *opts; + int noptions = 0; + int nulls = 0; + char *prefix = 0; + +/* fprintf (stderr, "\n## %s\n", p->id);*/ + for (opts = p->options; opts; opts = opts->next) + { + parameter *s = (parameter *) opts->data; + if (!s->arg_set) nulls++; + noptions++; + + if (s->arg_set) + { + char *a = strdup ((char *) s->arg_set); + char *spc = strchr (a, ' '); + if (spc) *spc = 0; + if (prefix) + { + if (strcmp (a, prefix)) + fprintf (stderr, + "%s: %s: both \"%s\" and \"%s\" used in \n", + blurb(), filename, p->id); +} + + +/* "text" nodes show up for all the non-tag text in the file, including + all the newlines between tags. Warn if there is text there that + is not whitespace. + */ +static void +sanity_check_text_node (const char *filename, const xmlNodePtr node) +{ + const char *body = (const char *) node->content; + if (node->type != XML_TEXT_NODE) abort(); + while (isspace (*body)) body++; + if (*body) + fprintf (stderr, "%s: WARNING: %s: random text present: \"%s\"\n", + blurb(), filename, body); +} + + +/* Returns a list of strings, every switch mentioned in the parameters. + The strings must be freed. + */ +static GList * +get_all_switches (const char *filename, GList *parms) +{ + GList *switches = 0; + GList *p; + for (p = parms; p; p = p->next) + { + parameter *pp = (parameter *) p->data; + + if (pp->type == SELECT) + { + GList *list2 = get_all_switches (filename, pp->options); + switches = g_list_concat (switches, list2); + } + if (pp->arg && *pp->arg) + switches = g_list_append (switches, strdup ((char *) pp->arg)); + if (pp->arg_set && *pp->arg_set) + switches = g_list_append (switches, strdup ((char *) pp->arg_set)); + if (pp->arg_unset && *pp->arg_unset) + switches = g_list_append (switches, strdup ((char *) pp->arg_unset)); + } + return switches; +} + + +/* Ensures that no switch is mentioned more than once. + */ +static void +sanity_check_parameters (const char *filename, GList *parms) +{ + GList *list = get_all_switches (filename, parms); + GList *p; + for (p = list; p; p = p->next) + { + char *sw = (char *) p->data; + GList *p2; + + if (*sw != '-' && *sw != '+') + fprintf (stderr, "%s: %s: switch does not begin with hyphen \"%s\"\n", + blurb(), filename, sw); + + for (p2 = p->next; p2; p2 = p2->next) + { + const char *sw2 = (const char *) p2->data; + if (!strcmp (sw, sw2)) + fprintf (stderr, "%s: %s: duplicate switch \"%s\"\n", + blurb(), filename, sw); + } + + free (sw); + } + g_list_free (list); +} + + +/* Helper for make_parameters() + */ +static GList * +make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent) +{ + GList *list = 0; + + for (; node; node = node->next) + { + const char *name = (char *) node->name; + if (!strcmp (name, "hgroup") || + !strcmp (name, "vgroup")) + { + GtkWidget *box = (*name == 'h' + ? gtk_hbox_new (FALSE, 0) + : gtk_vbox_new (FALSE, 0)); + GList *list2; + gtk_widget_show (box); + gtk_box_pack_start (GTK_BOX (parent), box, FALSE, FALSE, 0); + + list2 = make_parameters_1 (filename, node->xmlChildrenNode, box); + if (list2) + list = g_list_concat (list, list2); + } + else + { + parameter *p = make_parameter (filename, node); + if (p) + { + list = g_list_append (list, p); + make_parameter_widget (filename, p, parent); + } + } + } + return list; +} + + +/* Calls make_parameter() and make_parameter_widget() on each relevant + tag in the XML tree. Also handles the "hgroup" and "vgroup" flags. + Returns a GList of `parameter' objects. + */ +static GList * +make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent) +{ + for (; node; node = node->next) + { + if (node->type == XML_ELEMENT_NODE && + !strcmp ((char *) node->name, "screensaver")) + return make_parameters_1 (filename, node->xmlChildrenNode, parent); + } + return 0; +} + + +static gfloat +invert_range (gfloat low, gfloat high, gfloat value) +{ + gfloat range = high-low; + gfloat off = value-low; + return (low + (range - off)); +} + + +static GtkAdjustment * +make_adjustment (const char *filename, parameter *p) +{ + float range = (p->high - p->low); + float value = (p->invert_p + ? invert_range (p->low, p->high, p->value) + : p->value); + gfloat si = (p->high - p->low) / 100; + gfloat pi = (p->high - p->low) / 10; + gfloat page_size = ((p->type == SLIDER) ? 1 : 0); + + if (p->value < p->low || p->value > p->high) + { + if (debug_p && p->integer_p) + fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n", + blurb(), filename, + (int) p->value, (int) p->low, (int) p->high); + else if (debug_p) + fprintf (stderr, + "%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n", + blurb(), filename, p->value, p->low, p->high); + value = (value < p->low ? p->low : p->high); + } +#if 0 + else if (debug_p && p->value < 1000 && p->high >= 10000) + { + if (p->integer_p) + fprintf (stderr, + "%s: WARNING: %s: %d is suspicious for range [%d, %d]\n", + blurb(), filename, + (int) p->value, (int) p->low, (int) p->high); + else + fprintf (stderr, + "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n", + blurb(), filename, p->value, p->low, p->high); + } +#endif /* 0 */ + + si = (int) (si + 0.5); + pi = (int) (pi + 0.5); + if (si < 1) si = 1; + if (pi < 1) pi = 1; + + if (range <= 500) si = 1; + + return GTK_ADJUSTMENT (gtk_adjustment_new (value, + p->low, + p->high + page_size, + si, pi, page_size)); +} + + + +static void +set_widget_min_width (GtkWidget *w, int width) +{ + GtkRequisition req; + gtk_widget_size_request (GTK_WIDGET (w), &req); + if (req.width < width) + gtk_widget_set_size_request (GTK_WIDGET (w), width, -1); +} + + +/* If we're inside a vbox, we need to put an hbox in it, or labels appear + on top instead of to the left, and things stretch to the full width of + the window. + */ +static GtkWidget * +insert_fake_hbox (GtkWidget *parent) +{ + if (GTK_IS_VBOX (parent)) + { + GtkWidget *hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 4); + gtk_widget_show (hbox); + return hbox; + } + return parent; +} + + +/* Given a `parameter' struct, allocates an appropriate GtkWidget for it, + and stores it in `p->widget'. + `parent' must be a GtkBox. + */ +static void +make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) +{ + const char *label = (char *) p->label; + if (p->widget) return; + + switch (p->type) + { + case STRING: + { + parent = insert_fake_hbox (parent); + if (label) + { + GtkWidget *w = gtk_label_new (_(label)); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); + set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH); + gtk_widget_show (w); + gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4); + } + + p->widget = gtk_entry_new (); + if (p->string) + gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) p->string); + gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4); + break; + } + case FILENAME: + { + GtkWidget *L = gtk_label_new (label ? _(label) : ""); + GtkWidget *entry = gtk_entry_new (); + GtkWidget *button = gtk_button_new_with_label (_("Browse...")); + gtk_widget_show (entry); + gtk_widget_show (button); + p->widget = entry; + + gtk_signal_connect (GTK_OBJECT (button), + "clicked", GTK_SIGNAL_FUNC (browse_button_cb), + (gpointer) entry); + + gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5); + set_widget_min_width (GTK_WIDGET (L), MIN_LABEL_WIDTH); + gtk_widget_show (L); + + if (p->string) + gtk_entry_set_text (GTK_ENTRY (entry), (char *) p->string); + + parent = insert_fake_hbox (parent); + gtk_box_pack_start (GTK_BOX (parent), L, FALSE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (parent), entry, TRUE, TRUE, 4); + gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4); + break; + } + case SLIDER: + { + GtkAdjustment *adj = make_adjustment (filename, p); + GtkWidget *scale = gtk_hscale_new (adj); + GtkWidget *labelw = 0; + + if (label) + { + labelw = gtk_label_new (_(label)); + gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5); + set_widget_min_width (GTK_WIDGET (labelw), MIN_LABEL_WIDTH); + gtk_widget_show (labelw); + gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, FALSE, 2); + } + + /* Do this after 'labelw' so that it appears above, not to left. */ + parent = insert_fake_hbox (parent); + + if (p->low_label) + { + GtkWidget *w = gtk_label_new (_((char *) p->low_label)); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); + set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH); + gtk_widget_show (w); + gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4); + } + + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM); + gtk_scale_set_draw_value (GTK_SCALE (scale), debug_p); + gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2)); + set_widget_min_width (GTK_WIDGET (scale), MIN_SLIDER_WIDTH); + + gtk_box_pack_start (GTK_BOX (parent), scale, FALSE, FALSE, 4); + + gtk_widget_show (scale); + + if (p->high_label) + { + GtkWidget *w = gtk_label_new (_((char *) p->high_label)); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5); + set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH); + gtk_widget_show (w); + gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4); + } + + p->widget = scale; + break; + } + case SPINBUTTON: + { + GtkAdjustment *adj = make_adjustment (filename, p); + GtkWidget *spin = gtk_spin_button_new (adj, 15, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), adj->value); + set_widget_min_width (GTK_WIDGET (spin), MIN_SPINBUTTON_WIDTH); + + if (label) + { + GtkWidget *w = gtk_label_new (_(label)); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); + set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH); + gtk_widget_show (w); + parent = insert_fake_hbox (parent); + gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4); + } + + gtk_widget_show (spin); + gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4); + + p->widget = spin; + break; + } + case BOOLEAN: + { + p->widget = gtk_check_button_new_with_label (_(label)); + /* Let these stretch -- doesn't hurt. + parent = insert_fake_hbox (parent); + */ + gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4); + break; + } + case SELECT: + { + GtkWidget *opt = gtk_option_menu_new (); + GtkWidget *menu = gtk_menu_new (); + GList *opts; + + for (opts = p->options; opts; opts = opts->next) + { + parameter *s = (parameter *) opts->data; + GtkWidget *i = gtk_menu_item_new_with_label (_((char *) s->label)); + gtk_widget_show (i); + gtk_menu_append (GTK_MENU (menu), i); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu); + p->widget = opt; + parent = insert_fake_hbox (parent); + gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4); + break; + } + + case COMMAND: + case FAKE: + case DESCRIPTION: + case FAKEPREVIEW: + break; + default: + abort(); + } + + if (p->widget) + { + gtk_widget_set_name (p->widget, (char *) p->id); + gtk_widget_show (p->widget); + } +} + + +/* File selection. + Absurdly, there is no GTK file entry widget, only a GNOME one, + so in order to avoid depending on GNOME in this code, we have + to do it ourselves. + */ + +/* cancel button on GtkFileSelection: user_data unused */ +static void +file_sel_cancel (GtkWidget *button, gpointer user_data) +{ + GtkWidget *dialog = button; + while (dialog->parent) + dialog = dialog->parent; + gtk_widget_destroy (dialog); +} + +/* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */ +static void +file_sel_ok (GtkWidget *button, gpointer user_data) +{ + GtkWidget *entry = GTK_WIDGET (user_data); + GtkWidget *dialog = button; + const char *path; + while (dialog->parent) + dialog = dialog->parent; + gtk_widget_hide (dialog); + + path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog)); + /* apparently one doesn't free `path' */ + + gtk_entry_set_text (GTK_ENTRY (entry), path); + gtk_entry_set_position (GTK_ENTRY (entry), strlen (path)); + + gtk_widget_destroy (dialog); +} + +/* WM close on GtkFileSelection: user_data unused */ +static void +file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + file_sel_cancel (widget, user_data); +} + +/* "Browse" button: user_data is the corresponding GtkEntry */ +static void +browse_button_cb (GtkButton *button, gpointer user_data) +{ + GtkWidget *entry = GTK_WIDGET (user_data); + const char *text = gtk_entry_get_text (GTK_ENTRY (entry)); + GtkFileSelection *selector = + GTK_FILE_SELECTION (gtk_file_selection_new (_("Select file."))); + + gtk_file_selection_set_filename (selector, text); + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (file_sel_ok), + (gpointer) entry); + gtk_signal_connect (GTK_OBJECT (selector->cancel_button), + "clicked", GTK_SIGNAL_FUNC (file_sel_cancel), + (gpointer) entry); + gtk_signal_connect (GTK_OBJECT (selector), "delete_event", + GTK_SIGNAL_FUNC (file_sel_close), + (gpointer) entry); + + gtk_window_set_modal (GTK_WINDOW (selector), TRUE); + gtk_widget_show (GTK_WIDGET (selector)); +} + + +/* Converting to and from command-lines + */ + + +/* Returns a copy of string that has been quoted according to shell rules: + it may have been wrapped in "" and had some characters backslashed; or + it may be unchanged. + */ +static char * +shell_quotify (const char *string) +{ + char *string2 = (char *) malloc ((strlen (string) * 2) + 10); + const char *in; + char *out; + int need_quotes = 0; + int in_length = 0; + + out = string2; + *out++ = '"'; + for (in = string; *in; in++) + { + in_length++; + if (*in == '!' || + *in == '"' || + *in == '$') + { + need_quotes = 1; + *out++ = '\\'; + *out++ = *in; + } + else if (*in <= ' ' || + *in >= 127 || + *in == '\'' || + *in == '#' || + *in == '%' || + *in == '&' || + *in == '(' || + *in == ')' || + *in == '*') + { + need_quotes = 1; + *out++ = *in; + } + else + *out++ = *in; + } + *out++ = '"'; + *out = 0; + + if (in_length == 0) + need_quotes = 1; + + if (need_quotes) + return (string2); + + free (string2); + return strdup (string); +} + +/* Modify the string in place to remove wrapping double-quotes + and interior backslashes. + */ +static void +de_stringify (char *s) +{ + char q = s[0]; + if (q != '\'' && q != '\"' && q != '`') + abort(); + memmove (s, s+1, strlen (s)+1); + while (*s && *s != q) + { + if (*s == '\\') + memmove (s, s+1, strlen (s)+1); + s++; + } + if (*s != q) abort(); + *s = 0; +} + + +/* Substitutes a shell-quotified version of `value' into `p->arg' at + the place where the `%' character appeared. + */ +static char * +format_switch (parameter *p, const char *value) +{ + char *fmt = (char *) p->arg; + char *v2; + char *result, *s; + if (!fmt || !value) return 0; + v2 = shell_quotify (value); + result = (char *) malloc (strlen (fmt) + strlen (v2) + 10); + s = result; + for (; *fmt; fmt++) + if (*fmt != '%') + *s++ = *fmt; + else + { + strcpy (s, v2); + s += strlen (s); + } + *s = 0; + + free (v2); + return result; +} + + +/* Maps a `parameter' to a command-line switch. + Returns 0 if it can't, or if the parameter has the default value. + */ +static char * +parameter_to_switch (parameter *p) +{ + switch (p->type) + { + case COMMAND: + if (p->arg) + return strdup ((char *) p->arg); + else + return 0; + break; + case STRING: + case FILENAME: + if (!p->widget) return 0; + { + const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget)); + char *v; + if (!strcmp ((s ? s : ""), + (p->string ? (char *) p->string : ""))) + v = 0; /* same as default */ + else + v = format_switch (p, s); + + /* don't free `s' */ + return v; + } + case SLIDER: + case SPINBUTTON: + if (!p->widget) return 0; + { + GtkAdjustment *adj = + (p->type == SLIDER + ? gtk_range_get_adjustment (GTK_RANGE (p->widget)) + : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget))); + char buf[255]; + char *s1; + float value = (p->invert_p + ? invert_range (adj->lower, adj->upper, adj->value) - 1 + : adj->value); + + if (value == p->value) /* same as default */ + return 0; + + if (p->integer_p) + sprintf (buf, "%d", (int) (value + (value > 0 ? 0.5 : -0.5))); + else + sprintf (buf, "%.4f", value); + + s1 = strchr (buf, '.'); + if (s1) + { + char *s2 = s1 + strlen(s1) - 1; + while (s2 > s1 && *s2 == '0') /* lose trailing zeroes */ + *s2-- = 0; + if (s2 >= s1 && *s2 == '.') /* lose trailing decimal */ + *s2-- = 0; + } + return format_switch (p, buf); + } + case BOOLEAN: + if (!p->widget) return 0; + { + GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget); + const char *s = (gtk_toggle_button_get_active (b) + ? (char *) p->arg_set + : (char *) p->arg_unset); + if (s) + return strdup (s); + else + return 0; + } + case SELECT: + if (!p->widget) return 0; + { + GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget); + GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt)); + GtkWidget *selected = gtk_menu_get_active (menu); + GList *kids = gtk_container_children (GTK_CONTAINER (menu)); + int menu_elt = g_list_index (kids, (gpointer) selected); + GList *ol = g_list_nth (p->options, menu_elt); + parameter *o = (ol ? (parameter *) ol->data : 0); + const char *s; + if (!o) abort(); + if (o->type != SELECT_OPTION) abort(); + s = (char *) o->arg_set; + if (s) + return strdup (s); + else + return 0; + } + default: + if (p->widget) + abort(); + else + return 0; + } +} + +/* Maps a GList of `parameter' objects to a complete command-line string. + All arguments will be properly quoted. + */ +static char * +parameters_to_cmd_line (GList *parms, gboolean default_p) +{ + int L = g_list_length (parms); + int LL = 0; + char **strs = (char **) calloc (sizeof (*parms), L); + char *result; + char *out; + int i, j; + + for (i = 0, j = 0; parms; parms = parms->next, i++) + { + parameter *p = (parameter *) parms->data; + if (!default_p || p->type == COMMAND) + { + char *s = parameter_to_switch (p); + strs[j++] = s; + LL += (s ? strlen(s) : 0) + 1; + } + } + + result = (char *) malloc (LL + 10); + out = result; + for (i = 0; i < j; i++) + if (strs[i]) + { + strcpy (out, strs[i]); + out += strlen (out); + *out++ = ' '; + free (strs[i]); + } + *out = 0; + while (out > result && out[-1] == ' ') /* strip trailing spaces */ + *(--out) = 0; + free (strs); + + return result; +} + + +/* Returns a GList of the tokens the string, using shell syntax; + Quoted strings are handled as a single token. + */ +static GList * +tokenize_command_line (const char *cmd) +{ + GList *result = 0; + const char *s = cmd; + while (*s) + { + const char *start; + char *ss; + for (; isspace(*s); s++); /* skip whitespace */ + + start = s; + if (*s == '\'' || *s == '\"' || *s == '`') + { + char q = *s; + s++; + while (*s && *s != q) /* skip to matching quote */ + { + if (*s == '\\' && s[1]) /* allowing backslash quoting */ + s++; + s++; + } + s++; + } + else + { + while (*s && + (! (isspace(*s) || + *s == '\'' || + *s == '\"' || + *s == '`'))) + s++; + } + + if (s > start) + { + ss = (char *) malloc ((s - start) + 1); + strncpy (ss, start, s-start); + ss[s-start] = 0; + if (*ss == '\'' || *ss == '\"' || *ss == '`') + de_stringify (ss); + result = g_list_append (result, ss); + } + } + + return result; +} + +static void parameter_set_switch (parameter *, gpointer value); +static gboolean parse_command_line_into_parameters_1 (const char *filename, + GList *parms, + const char *option, + const char *value, + parameter *parent); + + +/* Parses the command line, and flushes those options down into + the `parameter' structs in the list. + */ +static void +parse_command_line_into_parameters (const char *filename, + const char *cmd, GList *parms) +{ + GList *tokens = tokenize_command_line (cmd); + GList *rest; + for (rest = tokens; rest; rest = rest->next) + { + char *option = rest->data; + rest->data = 0; + + if (option[0] != '-' && option[0] != '+') + { + if (debug_p) + fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n", + blurb(), filename, option); + } + else + { + char *value = 0; + + if (rest->next) /* pop off the arg to this option */ + { + char *s = (char *) rest->next->data; + /* the next token is the next switch iff it matches "-[a-z]". + (To avoid losing on "-x -3.1".) + */ + if (s && (s[0] != '-' || !isalpha(s[1]))) + { + value = s; + rest->next->data = 0; + rest = rest->next; + } + } + + parse_command_line_into_parameters_1 (filename, parms, + option, value, 0); + if (value) free (value); + free (option); + } + } + g_list_free (tokens); +} + + +static gboolean +compare_opts (const char *option, const char *value, + const char *template) +{ + int ol = strlen (option); + char *c; + + if (strncmp (option, template, ol)) + return FALSE; + + if (template[ol] != (value ? ' ' : 0)) + return FALSE; + + /* At this point, we have a match against "option". + If template contains a %, we're done. + Else, compare against "value" too. + */ + c = strchr (template, '%'); + if (c) + return TRUE; + + if (!value) + return (template[ol] == 0); + if (strcmp (template + ol + 1, value)) + return FALSE; + + return TRUE; +} + + +static gboolean +parse_command_line_into_parameters_1 (const char *filename, + GList *parms, + const char *option, + const char *value, + parameter *parent) +{ + GList *p; + parameter *match = 0; + gint which = -1; + gint index = 0; + + for (p = parms; p; p = p->next) + { + parameter *pp = (parameter *) p->data; + which = -99; + + if (pp->type == SELECT) + { + if (parse_command_line_into_parameters_1 (filename, + pp->options, + option, value, + pp)) + { + which = -2; + match = pp; + } + } + else if (pp->arg) + { + if (compare_opts (option, value, (char *) pp->arg)) + { + which = -1; + match = pp; + } + } + else if (pp->arg_set) + { + if (compare_opts (option, value, (char *) pp->arg_set)) + { + which = 1; + match = pp; + } + } + else if (pp->arg_unset) + { + if (compare_opts (option, value, (char *) pp->arg_unset)) + { + which = 0; + match = pp; + } + } + + if (match) + break; + + index++; + } + + if (!match) + { + if (debug_p && !parent) + fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n", + blurb(), filename, option, (value ? value : "")); + return FALSE; + } + + switch (match->type) + { + case STRING: + case FILENAME: + case SLIDER: + case SPINBUTTON: + if (which != -1) abort(); + parameter_set_switch (match, (gpointer) value); + break; + case BOOLEAN: + if (which != 0 && which != 1) abort(); + parameter_set_switch (match, GINT_TO_POINTER(which)); + break; + case SELECT_OPTION: + if (which != 1) abort(); + parameter_set_switch (parent, GINT_TO_POINTER(index)); + break; + default: + break; + } + return TRUE; +} + + +/* Set the parameter's value. + For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*. + For BOOLEAN and SELECT, `value' is an int. + */ +static void +parameter_set_switch (parameter *p, gpointer value) +{ + if (p->type == SELECT_OPTION) abort(); + if (!p->widget) return; + switch (p->type) + { + case STRING: + case FILENAME: + { + gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value); + break; + } + case SLIDER: + case SPINBUTTON: + { + GtkAdjustment *adj = + (p->type == SLIDER + ? gtk_range_get_adjustment (GTK_RANGE (p->widget)) + : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget))); + float f; + char c; + + if (1 == sscanf ((char *) value, "%f %c", &f, &c)) + { + if (p->invert_p) + f = invert_range (adj->lower, adj->upper, f) - 1; + gtk_adjustment_set_value (adj, f); + } + break; + } + case BOOLEAN: + { + GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget); + gtk_toggle_button_set_active (b, GPOINTER_TO_INT(value)); + break; + } + case SELECT: + { + gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget), + GPOINTER_TO_INT(value)); + break; + } + default: + abort(); + } +} + + +static void +restore_defaults (const char *progname, GList *parms) +{ + for (; parms; parms = parms->next) + { + parameter *p = (parameter *) parms->data; + if (!p->widget) continue; + switch (p->type) + { + case STRING: + case FILENAME: + { + gtk_entry_set_text (GTK_ENTRY (p->widget), + (p->string ? (char *) p->string : "")); + break; + } + case SLIDER: + case SPINBUTTON: + { + GtkAdjustment *adj = + (p->type == SLIDER + ? gtk_range_get_adjustment (GTK_RANGE (p->widget)) + : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget))); + float value = (p->invert_p + ? invert_range (p->low, p->high, p->value) + : p->value); + gtk_adjustment_set_value (adj, value); + break; + } + case BOOLEAN: + { + /* A toggle button should be on by default if it inserts + nothing into the command line when on. E.g., it should + be on if `arg_set' is null. + */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget), + (!p->arg_set || !*p->arg_set)); + break; + } + case SELECT: + { + GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget); + GList *opts; + int selected = 0; + int index; + + for (opts = p->options, index = 0; opts; + opts = opts->next, index++) + { + parameter *s = (parameter *) opts->data; + /* The default menu item is the first one with + no `arg_set' field. */ + if (!s->arg_set) + { + selected = index; + break; + } + } + + gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected); + break; + } + default: + abort(); + } + } +} + + + +/* Documentation strings + */ + +static char * +get_description (GList *parms, gboolean verbose_p) +{ + parameter *doc = 0; + for (; parms; parms = parms->next) + { + parameter *p = (parameter *) parms->data; + if (p->type == DESCRIPTION) + { + doc = p; + break; + } + } + + if (!doc || !doc->string) + return 0; + else + { + char *d = strdup ((char *) doc->string); + char *s; + char *p; + for (s = d; *s; s++) + if (s[0] == '\n') + { + if (s[1] == '\n') /* blank line: leave it */ + s++; + else if (s[1] == ' ' || s[1] == '\t') + s++; /* next line is indented: leave newline */ + else if (!strncmp(s+1, "http:", 5)) + s++; /* next line begins a URL: leave newline */ + else + s[0] = ' '; /* delete newline to un-fold this line */ + } + + /* strip off leading whitespace on first line only */ + for (s = d; *s && (*s == ' ' || *s == '\t'); s++) + ; + while (*s == '\n') /* strip leading newlines */ + s++; + if (s != d) + memmove (d, s, strlen(s)+1); + + /* strip off trailing whitespace and newlines */ + { + int L = strlen(d); + while (L && isspace(d[L-1])) + d[--L] = 0; + } + + /* strip off duplicated whitespaces */ + for (s = d; *s; s++) + if (s[0] == ' ') + { + p = s+1; + while (*s == ' ') + s++; + if (*p && (s != p)) + memmove (p, s, strlen(s)+1); + } + +#if 0 + if (verbose_p) + { + fprintf (stderr, "%s: text read is \"%s\"\n", blurb(),doc->string); + fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d); + fprintf (stderr, "%s: translation is \"%s\"\n", blurb(), _(d)); + } +#endif /* 0 */ + + return (d); + } +} + + +/* External interface. + */ + +static conf_data * +load_configurator_1 (const char *program, const char *arguments, + gboolean verbose_p) +{ + const char *dir = hack_configuration_path; + char *base_program; + int L = strlen (dir); + char *file; + char *s; + FILE *f; + conf_data *data; + + if (L == 0) return 0; + + base_program = strrchr(program, '/'); + if (base_program) base_program++; + if (!base_program) base_program = (char *) program; + + file = (char *) malloc (L + strlen (base_program) + 10); + data = (conf_data *) calloc (1, sizeof(*data)); + + strcpy (file, dir); + if (file[L-1] != '/') + file[L++] = '/'; + strcpy (file+L, base_program); + + for (s = file+L; *s; s++) + if (*s == '/' || *s == ' ') + *s = '_'; + else if (isupper (*s)) + *s = tolower (*s); + + strcat (file+L, ".xml"); + + f = fopen (file, "r"); + if (f) + { + int res, size = 1024; + char chars[1024]; + xmlParserCtxtPtr ctxt; + xmlDocPtr doc = 0; + GtkWidget *vbox0; + GList *parms; + + if (verbose_p) + fprintf (stderr, "%s: reading %s...\n", blurb(), file); + + res = fread (chars, 1, 4, f); + if (res <= 0) + { + free (data); + data = 0; + goto DONE; + } + + ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file); + while ((res = fread(chars, 1, size, f)) > 0) + xmlParseChunk (ctxt, chars, res, 0); + xmlParseChunk (ctxt, chars, 0, 1); + doc = ctxt->myDoc; + xmlFreeParserCtxt (ctxt); + fclose (f); + + /* Parsed the XML file. Now make some widgets. */ + + vbox0 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox0); + + parms = make_parameters (file, doc->xmlRootNode, vbox0); + sanity_check_parameters (file, parms); + + xmlFreeDoc (doc); + + restore_defaults (program, parms); + if (arguments && *arguments) + parse_command_line_into_parameters (program, arguments, parms); + + data->widget = vbox0; + data->parameters = parms; + data->description = get_description (parms, verbose_p); + } + else + { + parameter *p; + + if (verbose_p) + fprintf (stderr, "%s: %s does not exist.\n", blurb(), file); + + p = calloc (1, sizeof(*p)); + p->type = COMMAND; + p->arg = (xmlChar *) strdup (arguments); + + data->parameters = g_list_append (0, (gpointer) p); + } + + data->progname = strdup (program); + + DONE: + free (file); + return data; +} + +static void +split_command_line (const char *full_command_line, + char **prog_ret, char **args_ret) +{ + char *line = strdup (full_command_line); + char *prog; + char *args; + char *s; + + prog = line; + s = line; + while (*s) + { + if (isspace (*s)) + { + *s = 0; + s++; + while (isspace (*s)) s++; + break; + } + else if (*s == '=') /* if the leading word contains an "=", skip it. */ + { + while (*s && !isspace (*s)) s++; + while (isspace (*s)) s++; + prog = s; + } + s++; + } + args = s; + + *prog_ret = strdup (prog); + *args_ret = strdup (args); + free (line); +} + + +conf_data * +load_configurator (const char *full_command_line, gboolean verbose_p) +{ + char *prog; + char *args; + conf_data *cd; + debug_p = verbose_p; + split_command_line (full_command_line, &prog, &args); + cd = load_configurator_1 (prog, args, verbose_p); + free (prog); + free (args); + return cd; +} + + + +char * +get_configurator_command_line (conf_data *data, gboolean default_p) +{ + char *args = parameters_to_cmd_line (data->parameters, default_p); + char *result = (char *) malloc (strlen (data->progname) + + strlen (args) + 2); + strcpy (result, data->progname); + strcat (result, " "); + strcat (result, args); + free (args); + return result; +} + + +void +set_configurator_command_line (conf_data *data, const char *full_command_line) +{ + char *prog; + char *args; + split_command_line (full_command_line, &prog, &args); + if (data->progname) free (data->progname); + data->progname = prog; + restore_defaults (prog, data->parameters); + parse_command_line_into_parameters (prog, args, data->parameters); + free (args); +} + +void +free_conf_data (conf_data *data) +{ + if (data->parameters) + { + GList *rest; + for (rest = data->parameters; rest; rest = rest->next) + { + free_parameter ((parameter *) rest->data); + rest->data = 0; + } + g_list_free (data->parameters); + data->parameters = 0; + } + + if (data->widget) + gtk_widget_destroy (data->widget); + + if (data->progname) + free (data->progname); + if (data->description) + free (data->description); + + memset (data, ~0, sizeof(*data)); + free (data); +} + + +#endif /* HAVE_GTK && HAVE_XML -- whole file */ diff --git a/driver/demo-Gtk-conf.h b/driver/demo-Gtk-conf.h new file mode 100644 index 00000000..f462152c --- /dev/null +++ b/driver/demo-Gtk-conf.h @@ -0,0 +1,31 @@ +/* demo-Gtk-conf.c --- implements the dynamic configuration dialogs. + * xscreensaver, Copyright (c) 2001-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifndef _DEMO_GTK_CONF_H_ +#define _DEMO_GTK_CONF_H_ + +typedef struct { + GtkWidget *widget; /* the container widget with the sliders and stuff. */ + GList *parameters; /* internal data -- hands off */ + char *progname; + char *progclass; + char *description; +} conf_data; + +extern conf_data *load_configurator (const char *cmd_line, gboolean verbose_p); +extern char *get_configurator_command_line (conf_data *, gboolean default_p); +extern void set_configurator_command_line (conf_data *, const char *cmd_line); +extern void free_conf_data (conf_data *); + +extern const char *hack_configuration_path; + +#endif /* _DEMO_GTK_CONF_H_ */ diff --git a/driver/demo-Gtk-stubs.h b/driver/demo-Gtk-stubs.h new file mode 100644 index 00000000..c897d3ed --- /dev/null +++ b/driver/demo-Gtk-stubs.h @@ -0,0 +1,90 @@ +#include + + +void +activate_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +lock_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +kill_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +restart_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +exit_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +about_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +doc_menu_cb (GtkMenuItem *menuitem, + gpointer user_data); + +void +switch_page_cb (GtkNotebook *notebook, + GtkNotebookPage *page, + gint page_num, + gpointer user_data); + +void +pref_changed_cb (GtkToggleButton *togglebutton, + gpointer user_data); + +void +run_this_cb (GtkButton *button, + gpointer user_data); + +void +settings_cb (GtkButton *button, + gpointer user_data); + +void +run_next_cb (GtkButton *button, + gpointer user_data); + +void +run_prev_cb (GtkButton *button, + gpointer user_data); + +void +browse_image_dir_cb (GtkButton *button, + gpointer user_data); + +void +settings_switch_page_cb (GtkNotebook *notebook, + GtkNotebookPage *page, + gint page_num, + gpointer user_data); + +void +manual_cb (GtkButton *button, + gpointer user_data); + +void +settings_adv_cb (GtkButton *button, + gpointer user_data); + +void +settings_std_cb (GtkButton *button, + gpointer user_data); + +void +settings_reset_cb (GtkButton *button, + gpointer user_data); + +void +settings_ok_cb (GtkButton *button, + gpointer user_data); + +void +settings_cancel_cb (GtkButton *button, + gpointer user_data); diff --git a/driver/demo-Gtk-support.c b/driver/demo-Gtk-support.c new file mode 100644 index 00000000..881129c4 --- /dev/null +++ b/driver/demo-Gtk-support.c @@ -0,0 +1,219 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + WARNING: I did edit this file! Be careful! -jwz + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include + +#include "demo-Gtk-support.h" + +/* jwz -- dumbass Glade1 doesn't emit code that can read PNGs. this does. */ +#ifdef HAVE_GDK_PIXBUF +# ifdef HAVE_GTK2 +# include +# else /* !HAVE_GTK2 */ +# include +# endif /* !HAVE_GTK2 */ +#endif /* HAVE_GDK_PIXBUF */ + + +/* This is an internally used function to check if a pixmap file exists. */ +static gchar* check_file_exists (const gchar *directory, + const gchar *filename); + +/* This is an internally used function to create pixmaps. */ +static GtkWidget* create_dummy_pixmap (GtkWidget *widget); + +GtkWidget* +lookup_widget (GtkWidget *widget, + const gchar *widget_name) +{ + GtkWidget *parent, *found_widget; + + for (;;) + { + if (GTK_IS_MENU (widget)) + parent = gtk_menu_get_attach_widget (GTK_MENU (widget)); + else + parent = widget->parent; + if (parent == NULL) + break; + widget = parent; + } + + found_widget = (GtkWidget*) gtk_object_get_data (GTK_OBJECT (widget), + widget_name); + if (!found_widget) + g_warning ("Widget not found: %s", widget_name); + return found_widget; +} + +/* This is a dummy pixmap we use when a pixmap can't be found. */ +static char *dummy_pixmap_xpm[] = { +/* columns rows colors chars-per-pixel */ +"1 1 1 1", +" c None", +/* pixels */ +" " +}; + +/* This is an internally used function to create pixmaps. */ +static GtkWidget* +create_dummy_pixmap (GtkWidget *widget) +{ + GdkColormap *colormap; + GdkPixmap *gdkpixmap; + GdkBitmap *mask; + GtkWidget *pixmap; + + colormap = gtk_widget_get_colormap (widget); + gdkpixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, + NULL, dummy_pixmap_xpm); + if (gdkpixmap == NULL) + g_error ("Couldn't create replacement pixmap."); + pixmap = gtk_pixmap_new (gdkpixmap, mask); + gdk_pixmap_unref (gdkpixmap); + gdk_bitmap_unref (mask); + return pixmap; +} + +static GList *pixmaps_directories = NULL; + +/* Use this function to set the directory containing installed pixmaps. */ +void +add_pixmap_directory (const gchar *directory) +{ + pixmaps_directories = g_list_prepend (pixmaps_directories, + g_strdup (directory)); +} + +/* This is an internally used function to create pixmaps. */ +/* #### Warning: this version of this function hacked by jwz to + support PNGs. Don't let Glade1 overwrite this! + */ +GtkWidget* +create_pixmap (GtkWidget *widget, + const gchar *filename) +{ + gchar *found_filename = NULL; + GdkColormap *colormap = 0; + GdkPixmap *gdkpixmap = 0; + GdkBitmap *mask = 0; + GtkWidget *pixmap = 0; + GList *elem = 0; + + if (!filename || !filename[0]) + return create_dummy_pixmap (widget); + + /* We first try any pixmaps directories set by the application. */ + elem = pixmaps_directories; + while (elem) + { + found_filename = check_file_exists ((gchar*)elem->data, filename); + if (found_filename) + break; + elem = elem->next; + } + + /* If we haven't found the pixmap, try the source directory. */ + if (!found_filename) + { + found_filename = check_file_exists ("../utils/images", filename); + } + + if (!found_filename) + { + g_warning (_("Couldn't find pixmap file: %s"), filename); + return create_dummy_pixmap (widget); + } + + colormap = gtk_widget_get_colormap (widget); + +# ifndef HAVE_GDK_PIXBUF + + gdkpixmap = gdk_pixmap_colormap_create_from_xpm (NULL, colormap, &mask, + NULL, found_filename); + if (gdkpixmap == NULL) + { + g_warning (_("Error loading pixmap file: %s"), found_filename); + g_free (found_filename); + return create_dummy_pixmap (widget); + } + +# else /* HAVE_GDK_PIXBUF */ + + /* jwz -- dumbass Glade1 doesn't emit code that can read PNGs. + This code does... */ + + /* #### Danger: we aren't using `colormap'... */ + + { + GdkPixbuf *pb; +# ifdef HAVE_GTK2 + GError *gerr = 0; +# endif /* HAVE_GTK2 */ + + pb = gdk_pixbuf_new_from_file (found_filename +# ifdef HAVE_GTK2 + , &gerr +# endif /* HAVE_GTK2 */ + ); + + if (pb) + { + gdkpixmap = 0; + mask = 0; + gdk_pixbuf_render_pixmap_and_mask (pb, &gdkpixmap, &mask, 128); + } + else + { + g_warning (_("Error loading pixmap file: %s"), found_filename); +# ifdef HAVE_GTK2 + if (gerr && gerr->message && *gerr->message) + g_warning (_("reason: %s\n"), gerr->message); +# endif /* HAVE_GTK2 */ + + return create_dummy_pixmap (widget); + } + } +# endif /* HAVE_GDK_PIXBUF */ + + g_free (found_filename); + pixmap = gtk_pixmap_new (gdkpixmap, mask); + gdk_pixmap_unref (gdkpixmap); + gdk_bitmap_unref (mask); + + return pixmap; +} + +/* This is an internally used function to check if a pixmap file exists. */ +static gchar* +check_file_exists (const gchar *directory, + const gchar *filename) +{ + gchar *full_filename; + struct stat s; + gint status; + + full_filename = (gchar*) g_malloc (strlen (directory) + 1 + + strlen (filename) + 1); + strcpy (full_filename, directory); + strcat (full_filename, G_DIR_SEPARATOR_S); + strcat (full_filename, filename); + + status = stat (full_filename, &s); + if (status == 0 && S_ISREG (s.st_mode)) + return full_filename; + g_free (full_filename); + return NULL; +} + diff --git a/driver/demo-Gtk-support.h b/driver/demo-Gtk-support.h new file mode 100644 index 00000000..931bc5ad --- /dev/null +++ b/driver/demo-Gtk-support.h @@ -0,0 +1,61 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +/* + * Standard gettext macros. + */ +#ifdef ENABLE_NLS +# include +# undef _ +# define _(String) dgettext (PACKAGE, String) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#else +# define textdomain(String) (String) +# define gettext(String) (String) +# define dgettext(Domain,Message) (Message) +# define dcgettext(Domain,Message,Type) (Message) +# define bindtextdomain(Domain,Directory) (Domain) +# define _(String) (String) +# define N_(String) (String) +#endif + + +/* + * Public Functions. + */ + +/* + * This function returns a widget in a component created by Glade. + * Call it with the toplevel widget in the component (i.e. a window/dialog), + * or alternatively any widget in the component, and the name of the widget + * you want returned. + */ +GtkWidget* lookup_widget (GtkWidget *widget, + const gchar *widget_name); + +/* get_widget() is deprecated. Use lookup_widget instead. */ +#define get_widget lookup_widget + +/* Use this function to set the directory containing installed pixmaps. */ +void add_pixmap_directory (const gchar *directory); + + +/* + * Private Functions. + */ + +/* This is used to create the pixmaps in the interface. */ +GtkWidget* create_pixmap (GtkWidget *widget, + const gchar *filename); + diff --git a/driver/demo-Gtk-widgets.c b/driver/demo-Gtk-widgets.c new file mode 100644 index 00000000..176eaf68 --- /dev/null +++ b/driver/demo-Gtk-widgets.c @@ -0,0 +1,1764 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include +#include + +#include "demo-Gtk-stubs.h" +#include "demo-Gtk-widgets.h" +#include "demo-Gtk-support.h" + +# ifdef __GNUC__ +# define STFU __extension__ /* ignore gcc -pendantic warnings in next sexp */ +# else +# define STFU /* */ +# endif + +GtkWidget* +create_xscreensaver_demo (void) +{ + GtkWidget *xscreensaver_demo; + GtkWidget *outer_vbox; + GtkWidget *menubar; + guint tmp_key; + GtkWidget *file; + GtkWidget *file_menu; + GtkAccelGroup *file_menu_accels; + GtkWidget *activate_menu; + GtkWidget *lock_menu; + GtkWidget *kill_menu; + GtkWidget *restart; + GtkWidget *separator1; + GtkWidget *exit_menu; + GtkWidget *help; + GtkWidget *help_menu; + GtkAccelGroup *help_menu_accels; + GtkWidget *about_menu; + GtkWidget *doc_menu; + GtkWidget *notebook; + GtkWidget *demos_table; + GtkWidget *blanking_table; + GtkWidget *cycle_label; + GtkWidget *lock_button_eventbox; + GtkWidget *lock_button; + GtkWidget *timeout_label; + GtkObject *timeout_spinbutton_adj; + GtkWidget *timeout_spinbutton; + GtkWidget *timeout_mlabel; + GtkWidget *cycle_mlabel; + GtkWidget *lock_mlabel; + GtkObject *lock_spinbutton_adj; + GtkWidget *lock_spinbutton; + GtkObject *cycle_spinbutton_adj; + GtkWidget *cycle_spinbutton; + GtkWidget *demo_manual_hbbox; + GtkWidget *demo; + GtkWidget *settings; + GtkWidget *list_vbox; + GtkWidget *mode_hbox; + GtkWidget *mode_label; + GtkWidget *mode_menu; + GtkWidget *mode_menu_menu; + GtkWidget *glade_menuitem; + GtkWidget *col_head_hbox; + GtkWidget *use_col_frame; + GtkWidget *use_label; + GtkWidget *saver_col_frame; + GtkWidget *saver_label; + GtkWidget *scroller; + GtkWidget *viewport; + GtkWidget *list; + GtkWidget *centering_hbox; + GtkWidget *next_prev_hbox; + GtkWidget *next; + GtkWidget *prev; + GtkWidget *preview_frame; + GtkWidget *preview_aspectframe; + GtkWidget *preview; + GtkWidget *demo_tab; + GtkWidget *options_table; + GtkWidget *diag_frame; + GtkWidget *diag_hbox; + GtkWidget *diag_logo; + GtkWidget *diag_vbox; + GtkWidget *verbose_button_eventbox; + GtkWidget *verbose_button; + GtkWidget *capture_button_eventbox; + GtkWidget *capture_button; + GtkWidget *splash_button_eventbox; + GtkWidget *splash_button; + GtkWidget *cmap_frame; + GtkWidget *cmap_hbox; + GtkWidget *cmap_logo; + GtkWidget *cmap_vbox; + GtkWidget *install_button_eventbox; + GtkWidget *install_button; + GtkWidget *cmap_hr; + GtkWidget *fade_button_eventbox; + GtkWidget *fade_button; + GtkWidget *unfade_button_eventbox; + GtkWidget *unfade_button; + GtkWidget *fade_hbox; + GtkWidget *fade_dummy; + GtkWidget *fade_label; + GtkObject *fade_spinbutton_adj; + GtkWidget *fade_spinbutton; + GtkWidget *fade_sec_label; + GtkWidget *dpms_frame; + GtkWidget *dpms_hbox; + GtkWidget *dpms_logo; + GtkWidget *dpms_vbox; + GtkWidget *dpms_button_eventbox; + GtkWidget *dpms_button; + GtkWidget *dpms_table; + GtkObject *dpms_standby_spinbutton_adj; + GtkWidget *dpms_standby_spinbutton; + GtkWidget *dpms_standby_mlabel; + GtkWidget *dpms_suspend_mlabel; + GtkWidget *dpms_off_mlabel; + GtkWidget *dpms_off_label; + GtkWidget *dpms_suspend_label; + GtkWidget *dpms_standby_label; + GtkObject *dpms_suspend_spinbutton_adj; + GtkWidget *dpms_suspend_spinbutton; + GtkObject *dpms_off_spinbutton_adj; + GtkWidget *dpms_off_spinbutton; + GtkWidget *grab_frame; + GtkWidget *grab_hbox; + GtkWidget *img_logo; + GtkWidget *grab_vbox; + GtkWidget *grab_desk_eventbox; + GtkWidget *grab_desk_button; + GtkWidget *grab_video_eventbox; + GtkWidget *grab_video_button; + GtkWidget *grab_image_eventbox; + GtkWidget *grab_image_button; + GtkWidget *image_hbox; + GtkWidget *grab_dummy; + GtkWidget *image_text; + GtkWidget *image_browse_button; + GtkWidget *options_tab; + GtkAccelGroup *accel_group; + GtkTooltips *tooltips; + + tooltips = gtk_tooltips_new (); + + accel_group = gtk_accel_group_new (); + + xscreensaver_demo = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_name (xscreensaver_demo, "xscreensaver_demo"); + gtk_object_set_data (GTK_OBJECT (xscreensaver_demo), "xscreensaver_demo", xscreensaver_demo); + gtk_window_set_title (GTK_WINDOW (xscreensaver_demo), _("XScreenSaver")); + gtk_window_set_wmclass (GTK_WINDOW (xscreensaver_demo), "xscreensaver", "XScreenSaver"); + + outer_vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_set_name (outer_vbox, "outer_vbox"); + gtk_widget_ref (outer_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "outer_vbox", outer_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (outer_vbox); + gtk_container_add (GTK_CONTAINER (xscreensaver_demo), outer_vbox); + + menubar = gtk_menu_bar_new (); + gtk_widget_set_name (menubar, "menubar"); + gtk_widget_ref (menubar); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "menubar", menubar, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (menubar); + gtk_box_pack_start (GTK_BOX (outer_vbox), menubar, FALSE, FALSE, 0); + + file = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (file)->child), + _("_File")); + gtk_widget_add_accelerator (file, "activate_item", accel_group, + tmp_key, GDK_MOD1_MASK, (GtkAccelFlags) 0); + gtk_widget_set_name (file, "file"); + gtk_widget_ref (file); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "file", file, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (file); + gtk_container_add (GTK_CONTAINER (menubar), file); + + file_menu = gtk_menu_new (); + gtk_widget_set_name (file_menu, "file_menu"); + gtk_widget_ref (file_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "file_menu", file_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (file), file_menu); + file_menu_accels = gtk_menu_ensure_uline_accel_group (GTK_MENU (file_menu)); + + activate_menu = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (activate_menu)->child), + _("_Blank Screen Now")); + gtk_widget_add_accelerator (activate_menu, "activate_item", file_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (activate_menu, "activate_menu"); + gtk_widget_ref (activate_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "activate_menu", activate_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (activate_menu); + gtk_container_add (GTK_CONTAINER (file_menu), activate_menu); + gtk_tooltips_set_tip (tooltips, activate_menu, _("Activate the XScreenSaver daemon now (locking the screen if so configured.)"), NULL); + + lock_menu = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (lock_menu)->child), + _("_Lock Screen Now")); + gtk_widget_add_accelerator (lock_menu, "activate_item", file_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (lock_menu, "lock_menu"); + gtk_widget_ref (lock_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "lock_menu", lock_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (lock_menu); + gtk_container_add (GTK_CONTAINER (file_menu), lock_menu); + gtk_tooltips_set_tip (tooltips, lock_menu, _("Lock the screen now (even if \"Lock Screen\" is unchecked.)"), NULL); + + kill_menu = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (kill_menu)->child), + _("_Kill Daemon")); + gtk_widget_add_accelerator (kill_menu, "activate_item", file_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (kill_menu, "kill_menu"); + gtk_widget_ref (kill_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "kill_menu", kill_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (kill_menu); + gtk_container_add (GTK_CONTAINER (file_menu), kill_menu); + gtk_tooltips_set_tip (tooltips, kill_menu, _("Tell the running XScreenSaver daemon to exit."), NULL); + + restart = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (restart)->child), + _("_Restart Daemon")); + gtk_widget_add_accelerator (restart, "activate_item", file_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (restart, "restart"); + gtk_widget_ref (restart); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "restart", restart, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (restart); + gtk_container_add (GTK_CONTAINER (file_menu), restart); + gtk_tooltips_set_tip (tooltips, restart, _("Kill and re-launch the XScreenSaver daemon."), NULL); + + separator1 = gtk_menu_item_new (); + gtk_widget_set_name (separator1, "separator1"); + gtk_widget_ref (separator1); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "separator1", separator1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (separator1); + gtk_container_add (GTK_CONTAINER (file_menu), separator1); + gtk_widget_set_sensitive (separator1, FALSE); + + exit_menu = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (exit_menu)->child), + _("_Exit")); + gtk_widget_add_accelerator (exit_menu, "activate_item", file_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (exit_menu, "exit_menu"); + gtk_widget_ref (exit_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "exit_menu", exit_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (exit_menu); + gtk_container_add (GTK_CONTAINER (file_menu), exit_menu); + gtk_tooltips_set_tip (tooltips, exit_menu, _("Exit the xscreensaver-demo program (but leave the XScreenSaver daemon running in the background.)"), NULL); + + help = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (help)->child), + _("_Help")); + gtk_widget_add_accelerator (help, "activate_item", accel_group, + tmp_key, GDK_MOD1_MASK, (GtkAccelFlags) 0); + gtk_widget_set_name (help, "help"); + gtk_widget_ref (help); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "help", help, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (help); + gtk_container_add (GTK_CONTAINER (menubar), help); + + help_menu = gtk_menu_new (); + gtk_widget_set_name (help_menu, "help_menu"); + gtk_widget_ref (help_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "help_menu", help_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), help_menu); + help_menu_accels = gtk_menu_ensure_uline_accel_group (GTK_MENU (help_menu)); + + about_menu = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (about_menu)->child), + _("_About...")); + gtk_widget_add_accelerator (about_menu, "activate_item", help_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (about_menu, "about_menu"); + gtk_widget_ref (about_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "about_menu", about_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (about_menu); + gtk_container_add (GTK_CONTAINER (help_menu), about_menu); + gtk_tooltips_set_tip (tooltips, about_menu, _("Display version information."), NULL); + + doc_menu = gtk_menu_item_new_with_label (""); + tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (doc_menu)->child), + _("_Documentation...")); + gtk_widget_add_accelerator (doc_menu, "activate_item", help_menu_accels, + tmp_key, 0, 0); + gtk_widget_set_name (doc_menu, "doc_menu"); + gtk_widget_ref (doc_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "doc_menu", doc_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (doc_menu); + gtk_container_add (GTK_CONTAINER (help_menu), doc_menu); + gtk_tooltips_set_tip (tooltips, doc_menu, _("Go to the documentation on the XScreenSaver web page."), NULL); + + notebook = gtk_notebook_new (); + gtk_widget_set_name (notebook, "notebook"); + gtk_widget_ref (notebook); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "notebook", notebook, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (notebook); + gtk_box_pack_start (GTK_BOX (outer_vbox), notebook, TRUE, TRUE, 0); + + demos_table = gtk_table_new (2, 2, FALSE); + gtk_widget_set_name (demos_table, "demos_table"); + gtk_widget_ref (demos_table); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "demos_table", demos_table, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (demos_table); + gtk_container_add (GTK_CONTAINER (notebook), demos_table); + gtk_container_set_border_width (GTK_CONTAINER (demos_table), 10); + + blanking_table = gtk_table_new (3, 4, FALSE); + gtk_widget_set_name (blanking_table, "blanking_table"); + gtk_widget_ref (blanking_table); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "blanking_table", blanking_table, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (blanking_table); + gtk_table_attach (GTK_TABLE (demos_table), blanking_table, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_table_set_row_spacings (GTK_TABLE (blanking_table), 2); + + cycle_label = gtk_label_new (_("Cycle After")); + gtk_widget_set_name (cycle_label, "cycle_label"); + gtk_widget_ref (cycle_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cycle_label", cycle_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cycle_label); + gtk_table_attach (GTK_TABLE (blanking_table), cycle_label, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (cycle_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (cycle_label), 1, 0.5); + gtk_misc_set_padding (GTK_MISC (cycle_label), 8, 0); + + lock_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (lock_button_eventbox, "lock_button_eventbox"); + gtk_widget_ref (lock_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "lock_button_eventbox", lock_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (lock_button_eventbox); + gtk_table_attach (GTK_TABLE (blanking_table), lock_button_eventbox, 0, 2, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_tooltips_set_tip (tooltips, lock_button_eventbox, _("Whether a password should be required to un-blank the screen."), NULL); + + lock_button = gtk_check_button_new_with_label (_("Lock Screen After")); + gtk_widget_set_name (lock_button, "lock_button"); + gtk_widget_ref (lock_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "lock_button", lock_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (lock_button); + gtk_container_add (GTK_CONTAINER (lock_button_eventbox), lock_button); + + timeout_label = gtk_label_new (_("Blank After")); + gtk_widget_set_name (timeout_label, "timeout_label"); + gtk_widget_ref (timeout_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "timeout_label", timeout_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (timeout_label); + gtk_table_attach (GTK_TABLE (blanking_table), timeout_label, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (timeout_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (timeout_label), 1, 0.5); + gtk_misc_set_padding (GTK_MISC (timeout_label), 8, 0); + + timeout_spinbutton_adj = gtk_adjustment_new (0, 1, 720, 1, 30, 30); + timeout_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (timeout_spinbutton_adj), 15, 0); + gtk_widget_set_name (timeout_spinbutton, "timeout_spinbutton"); + gtk_widget_ref (timeout_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "timeout_spinbutton", timeout_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (timeout_spinbutton); + gtk_table_attach (GTK_TABLE (blanking_table), timeout_spinbutton, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_tooltips_set_tip (tooltips, timeout_spinbutton, _("How long before the monitor goes completely black."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (timeout_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (timeout_spinbutton), TRUE); + + timeout_mlabel = gtk_label_new (_("minutes")); + gtk_widget_set_name (timeout_mlabel, "timeout_mlabel"); + gtk_widget_ref (timeout_mlabel); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "timeout_mlabel", timeout_mlabel, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (timeout_mlabel); + gtk_table_attach (GTK_TABLE (blanking_table), timeout_mlabel, 3, 4, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (timeout_mlabel), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (timeout_mlabel), 0, 0.5); + + cycle_mlabel = gtk_label_new (_("minutes")); + gtk_widget_set_name (cycle_mlabel, "cycle_mlabel"); + gtk_widget_ref (cycle_mlabel); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cycle_mlabel", cycle_mlabel, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cycle_mlabel); + gtk_table_attach (GTK_TABLE (blanking_table), cycle_mlabel, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (cycle_mlabel), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (cycle_mlabel), 0, 0.5); + + lock_mlabel = gtk_label_new (_("minutes")); + gtk_widget_set_name (lock_mlabel, "lock_mlabel"); + gtk_widget_ref (lock_mlabel); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "lock_mlabel", lock_mlabel, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (lock_mlabel); + gtk_table_attach (GTK_TABLE (blanking_table), lock_mlabel, 3, 4, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (lock_mlabel), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (lock_mlabel), 0, 0.5); + + lock_spinbutton_adj = gtk_adjustment_new (0, 0, 720, 1, 30, 30); + lock_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (lock_spinbutton_adj), 15, 0); + gtk_widget_set_name (lock_spinbutton, "lock_spinbutton"); + gtk_widget_ref (lock_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "lock_spinbutton", lock_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (lock_spinbutton); + gtk_table_attach (GTK_TABLE (blanking_table), lock_spinbutton, 2, 3, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 10); + gtk_tooltips_set_tip (tooltips, lock_spinbutton, _("How long before the monitor goes completely black."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (lock_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (lock_spinbutton), TRUE); + + cycle_spinbutton_adj = gtk_adjustment_new (0, 1, 720, 1, 30, 30); + cycle_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (cycle_spinbutton_adj), 15, 0); + gtk_widget_set_name (cycle_spinbutton, "cycle_spinbutton"); + gtk_widget_ref (cycle_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cycle_spinbutton", cycle_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cycle_spinbutton); + gtk_table_attach (GTK_TABLE (blanking_table), cycle_spinbutton, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_tooltips_set_tip (tooltips, cycle_spinbutton, _("How long before the monitor goes completely black."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (cycle_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (cycle_spinbutton), TRUE); + + demo_manual_hbbox = gtk_hbutton_box_new (); + gtk_widget_set_name (demo_manual_hbbox, "demo_manual_hbbox"); + gtk_widget_ref (demo_manual_hbbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "demo_manual_hbbox", demo_manual_hbbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (demo_manual_hbbox); + gtk_table_attach (GTK_TABLE (demos_table), demo_manual_hbbox, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (demo_manual_hbbox), GTK_BUTTONBOX_SPREAD); + + demo = gtk_button_new_with_label (_("Preview")); + gtk_widget_set_name (demo, "demo"); + gtk_widget_ref (demo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "demo", demo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (demo); + gtk_container_add (GTK_CONTAINER (demo_manual_hbbox), demo); + STFU GTK_WIDGET_SET_FLAGS (demo, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, demo, _("Demo the selected screen saver in full-screen mode (click the mouse to return.)"), NULL); + + settings = gtk_button_new_with_label (_("Settings...")); + gtk_widget_set_name (settings, "settings"); + gtk_widget_ref (settings); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "settings", settings, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (settings); + gtk_container_add (GTK_CONTAINER (demo_manual_hbbox), settings); + STFU GTK_WIDGET_SET_FLAGS (settings, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, settings, _("Customization and explanation of the selected screen saver."), NULL); + + list_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (list_vbox, "list_vbox"); + gtk_widget_ref (list_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "list_vbox", list_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (list_vbox); + gtk_table_attach (GTK_TABLE (demos_table), list_vbox, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + gtk_container_set_border_width (GTK_CONTAINER (list_vbox), 10); + + mode_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (mode_hbox, "mode_hbox"); + gtk_widget_ref (mode_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "mode_hbox", mode_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (mode_hbox); + gtk_box_pack_start (GTK_BOX (list_vbox), mode_hbox, FALSE, TRUE, 10); + + mode_label = gtk_label_new (_("Mode:")); + gtk_widget_set_name (mode_label, "mode_label"); + gtk_widget_ref (mode_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "mode_label", mode_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (mode_label); + gtk_box_pack_start (GTK_BOX (mode_hbox), mode_label, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (mode_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (mode_label), 0, 0.5); + + mode_menu = gtk_option_menu_new (); + gtk_widget_set_name (mode_menu, "mode_menu"); + gtk_widget_ref (mode_menu); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "mode_menu", mode_menu, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (mode_menu); + gtk_box_pack_start (GTK_BOX (mode_hbox), mode_menu, FALSE, FALSE, 4); + mode_menu_menu = gtk_menu_new (); + glade_menuitem = gtk_menu_item_new_with_label (_("Disable Screen Saver")); + gtk_widget_show (glade_menuitem); + gtk_menu_append (GTK_MENU (mode_menu_menu), glade_menuitem); + glade_menuitem = gtk_menu_item_new_with_label (_("Blank Screen Only")); + gtk_widget_show (glade_menuitem); + gtk_menu_append (GTK_MENU (mode_menu_menu), glade_menuitem); + glade_menuitem = gtk_menu_item_new_with_label (_("Only One Screen Saver")); + gtk_widget_show (glade_menuitem); + gtk_menu_append (GTK_MENU (mode_menu_menu), glade_menuitem); + glade_menuitem = gtk_menu_item_new_with_label (_("Random Screen Saver")); + gtk_widget_show (glade_menuitem); + gtk_menu_append (GTK_MENU (mode_menu_menu), glade_menuitem); + gtk_option_menu_set_menu (GTK_OPTION_MENU (mode_menu), mode_menu_menu); + gtk_option_menu_set_history (GTK_OPTION_MENU (mode_menu), 3); + + col_head_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (col_head_hbox, "col_head_hbox"); + gtk_widget_ref (col_head_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "col_head_hbox", col_head_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (col_head_hbox); + gtk_box_pack_start (GTK_BOX (list_vbox), col_head_hbox, FALSE, TRUE, 0); + + use_col_frame = gtk_frame_new (NULL); + gtk_widget_set_name (use_col_frame, "use_col_frame"); + gtk_widget_ref (use_col_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "use_col_frame", use_col_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (use_col_frame); + gtk_box_pack_start (GTK_BOX (col_head_hbox), use_col_frame, FALSE, FALSE, 0); + gtk_frame_set_shadow_type (GTK_FRAME (use_col_frame), GTK_SHADOW_OUT); + + use_label = gtk_label_new (_("Use")); + gtk_widget_set_name (use_label, "use_label"); + gtk_widget_ref (use_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "use_label", use_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (use_label); + gtk_container_add (GTK_CONTAINER (use_col_frame), use_label); + gtk_label_set_justify (GTK_LABEL (use_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (use_label), 0, 0.5); + gtk_misc_set_padding (GTK_MISC (use_label), 3, 0); + + saver_col_frame = gtk_frame_new (NULL); + gtk_widget_set_name (saver_col_frame, "saver_col_frame"); + gtk_widget_ref (saver_col_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "saver_col_frame", saver_col_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (saver_col_frame); + gtk_box_pack_start (GTK_BOX (col_head_hbox), saver_col_frame, TRUE, TRUE, 0); + gtk_frame_set_shadow_type (GTK_FRAME (saver_col_frame), GTK_SHADOW_OUT); + + saver_label = gtk_label_new (_("Screen Saver")); + gtk_widget_set_name (saver_label, "saver_label"); + gtk_widget_ref (saver_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "saver_label", saver_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (saver_label); + gtk_container_add (GTK_CONTAINER (saver_col_frame), saver_label); + gtk_label_set_justify (GTK_LABEL (saver_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (saver_label), 0, 0.5); + gtk_misc_set_padding (GTK_MISC (saver_label), 6, 0); + + scroller = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_name (scroller, "scroller"); + gtk_widget_ref (scroller); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "scroller", scroller, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (scroller); + gtk_box_pack_start (GTK_BOX (list_vbox), scroller, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_widget_set_name (viewport, "viewport"); + gtk_widget_ref (viewport); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "viewport", viewport, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (viewport); + gtk_container_add (GTK_CONTAINER (scroller), viewport); + gtk_container_set_border_width (GTK_CONTAINER (viewport), 1); + + list = gtk_list_new (); + gtk_widget_set_name (list, "list"); + gtk_widget_ref (list); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "list", list, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (list); + gtk_container_add (GTK_CONTAINER (viewport), list); + + centering_hbox = gtk_hbox_new (TRUE, 0); + gtk_widget_set_name (centering_hbox, "centering_hbox"); + gtk_widget_ref (centering_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "centering_hbox", centering_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (centering_hbox); + gtk_box_pack_end (GTK_BOX (list_vbox), centering_hbox, FALSE, TRUE, 0); + + next_prev_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (next_prev_hbox, "next_prev_hbox"); + gtk_widget_ref (next_prev_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "next_prev_hbox", next_prev_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (next_prev_hbox); + gtk_box_pack_start (GTK_BOX (centering_hbox), next_prev_hbox, FALSE, FALSE, 0); + + next = gtk_button_new_with_label (_("\\/")); + gtk_widget_set_name (next, "next"); + gtk_widget_ref (next); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "next", next, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (next); + gtk_box_pack_start (GTK_BOX (next_prev_hbox), next, FALSE, FALSE, 0); + STFU GTK_WIDGET_SET_FLAGS (next, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, next, _("Run the next screen saver in the list in full-screen mode (click the mouse to return.)"), NULL); + + prev = gtk_button_new_with_label (_("/\\")); + gtk_widget_set_name (prev, "prev"); + gtk_widget_ref (prev); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "prev", prev, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prev); + gtk_box_pack_start (GTK_BOX (next_prev_hbox), prev, FALSE, FALSE, 0); + STFU GTK_WIDGET_SET_FLAGS (prev, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, prev, _("Run the previous screen saver in the list in full-screen mode (click the mouse to return.)"), NULL); + + preview_frame = gtk_frame_new (_("Description")); + gtk_widget_set_name (preview_frame, "preview_frame"); + gtk_widget_ref (preview_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "preview_frame", preview_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (preview_frame); + gtk_table_attach (GTK_TABLE (demos_table), preview_frame, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), 0, 6); + + preview_aspectframe = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.33, FALSE); + gtk_widget_set_name (preview_aspectframe, "preview_aspectframe"); + gtk_widget_ref (preview_aspectframe); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "preview_aspectframe", preview_aspectframe, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (preview_aspectframe); + gtk_container_add (GTK_CONTAINER (preview_frame), preview_aspectframe); + gtk_container_set_border_width (GTK_CONTAINER (preview_aspectframe), 8); + + preview = gtk_drawing_area_new (); + gtk_widget_set_name (preview, "preview"); + gtk_widget_ref (preview); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "preview", preview, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (preview); + gtk_container_add (GTK_CONTAINER (preview_aspectframe), preview); + + demo_tab = gtk_label_new (_("Display Modes")); + gtk_widget_set_name (demo_tab, "demo_tab"); + gtk_widget_ref (demo_tab); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "demo_tab", demo_tab, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (demo_tab); + gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), 0), demo_tab); + + options_table = gtk_table_new (2, 2, TRUE); + gtk_widget_set_name (options_table, "options_table"); + gtk_widget_ref (options_table); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "options_table", options_table, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (options_table); + gtk_container_add (GTK_CONTAINER (notebook), options_table); + + diag_frame = gtk_frame_new (_("Diagnostics")); + gtk_widget_set_name (diag_frame, "diag_frame"); + gtk_widget_ref (diag_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "diag_frame", diag_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (diag_frame); + gtk_table_attach (GTK_TABLE (options_table), diag_frame, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + gtk_container_set_border_width (GTK_CONTAINER (diag_frame), 10); + + diag_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_set_name (diag_hbox, "diag_hbox"); + gtk_widget_ref (diag_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "diag_hbox", diag_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (diag_hbox); + gtk_container_add (GTK_CONTAINER (diag_frame), diag_hbox); + gtk_container_set_border_width (GTK_CONTAINER (diag_hbox), 8); + + diag_logo = create_pixmap (xscreensaver_demo, "screensaver-diagnostic.png"); + gtk_widget_set_name (diag_logo, "diag_logo"); + gtk_widget_ref (diag_logo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "diag_logo", diag_logo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (diag_logo); + gtk_box_pack_start (GTK_BOX (diag_hbox), diag_logo, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (diag_logo), 0.5, 0); + + diag_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (diag_vbox, "diag_vbox"); + gtk_widget_ref (diag_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "diag_vbox", diag_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (diag_vbox); + gtk_box_pack_start (GTK_BOX (diag_hbox), diag_vbox, TRUE, TRUE, 0); + + verbose_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (verbose_button_eventbox, "verbose_button_eventbox"); + gtk_widget_ref (verbose_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "verbose_button_eventbox", verbose_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (verbose_button_eventbox); + gtk_box_pack_start (GTK_BOX (diag_vbox), verbose_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, verbose_button_eventbox, _("Whether the daemon should print lots of debugging information."), NULL); + + verbose_button = gtk_check_button_new_with_label (_("Verbose Diagnostics")); + gtk_widget_set_name (verbose_button, "verbose_button"); + gtk_widget_ref (verbose_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "verbose_button", verbose_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (verbose_button); + gtk_container_add (GTK_CONTAINER (verbose_button_eventbox), verbose_button); + + capture_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (capture_button_eventbox, "capture_button_eventbox"); + gtk_widget_ref (capture_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "capture_button_eventbox", capture_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (capture_button_eventbox); + gtk_box_pack_start (GTK_BOX (diag_vbox), capture_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, capture_button_eventbox, _("Whether any error output of the display modes should be redirected to the screen."), NULL); + + capture_button = gtk_check_button_new_with_label (_("Display Subprocess Errors")); + gtk_widget_set_name (capture_button, "capture_button"); + gtk_widget_ref (capture_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "capture_button", capture_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (capture_button); + gtk_container_add (GTK_CONTAINER (capture_button_eventbox), capture_button); + + splash_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (splash_button_eventbox, "splash_button_eventbox"); + gtk_widget_ref (splash_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "splash_button_eventbox", splash_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (splash_button_eventbox); + gtk_box_pack_start (GTK_BOX (diag_vbox), splash_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, splash_button_eventbox, _("Whether the splash screen (with the version number and `Help' button) should be momentarily displayed when the daemon first starts up."), NULL); + + splash_button = gtk_check_button_new_with_label (_("Display Splash Screen at Startup")); + gtk_widget_set_name (splash_button, "splash_button"); + gtk_widget_ref (splash_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "splash_button", splash_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (splash_button); + gtk_container_add (GTK_CONTAINER (splash_button_eventbox), splash_button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (splash_button), TRUE); + + cmap_frame = gtk_frame_new (_("Colormaps")); + gtk_widget_set_name (cmap_frame, "cmap_frame"); + gtk_widget_ref (cmap_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cmap_frame", cmap_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmap_frame); + gtk_table_attach (GTK_TABLE (options_table), cmap_frame, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_container_set_border_width (GTK_CONTAINER (cmap_frame), 10); + + cmap_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_set_name (cmap_hbox, "cmap_hbox"); + gtk_widget_ref (cmap_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cmap_hbox", cmap_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmap_hbox); + gtk_container_add (GTK_CONTAINER (cmap_frame), cmap_hbox); + gtk_container_set_border_width (GTK_CONTAINER (cmap_hbox), 8); + + cmap_logo = create_pixmap (xscreensaver_demo, "screensaver-colorselector.png"); + gtk_widget_set_name (cmap_logo, "cmap_logo"); + gtk_widget_ref (cmap_logo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cmap_logo", cmap_logo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmap_logo); + gtk_box_pack_start (GTK_BOX (cmap_hbox), cmap_logo, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (cmap_logo), 0.5, 0); + + cmap_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (cmap_vbox, "cmap_vbox"); + gtk_widget_ref (cmap_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cmap_vbox", cmap_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmap_vbox); + gtk_box_pack_start (GTK_BOX (cmap_hbox), cmap_vbox, TRUE, TRUE, 0); + + install_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (install_button_eventbox, "install_button_eventbox"); + gtk_widget_ref (install_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "install_button_eventbox", install_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (install_button_eventbox); + gtk_box_pack_start (GTK_BOX (cmap_vbox), install_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, install_button_eventbox, _("Whether to install a private colormap when running in 8-bit mode on the default Visual."), NULL); + + install_button = gtk_check_button_new_with_label (_("Install Colormap")); + gtk_widget_set_name (install_button, "install_button"); + gtk_widget_ref (install_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "install_button", install_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (install_button); + gtk_container_add (GTK_CONTAINER (install_button_eventbox), install_button); + + cmap_hr = gtk_hseparator_new (); + gtk_widget_set_name (cmap_hr, "cmap_hr"); + gtk_widget_ref (cmap_hr); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "cmap_hr", cmap_hr, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmap_hr); + gtk_box_pack_start (GTK_BOX (cmap_vbox), cmap_hr, FALSE, FALSE, 4); + + fade_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (fade_button_eventbox, "fade_button_eventbox"); + gtk_widget_ref (fade_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_button_eventbox", fade_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_button_eventbox); + gtk_box_pack_start (GTK_BOX (cmap_vbox), fade_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, fade_button_eventbox, _("Whether the screen should slowly fade to black when the screen saver activates."), NULL); + + fade_button = gtk_check_button_new_with_label (_("Fade To Black When Blanking")); + gtk_widget_set_name (fade_button, "fade_button"); + gtk_widget_ref (fade_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_button", fade_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_button); + gtk_container_add (GTK_CONTAINER (fade_button_eventbox), fade_button); + + unfade_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (unfade_button_eventbox, "unfade_button_eventbox"); + gtk_widget_ref (unfade_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "unfade_button_eventbox", unfade_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (unfade_button_eventbox); + gtk_box_pack_start (GTK_BOX (cmap_vbox), unfade_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, unfade_button_eventbox, _("Whether the screen should slowly fade in from black when the screen saver deactivates."), NULL); + + unfade_button = gtk_check_button_new_with_label (_("Fade From Black When Unblanking")); + gtk_widget_set_name (unfade_button, "unfade_button"); + gtk_widget_ref (unfade_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "unfade_button", unfade_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (unfade_button); + gtk_container_add (GTK_CONTAINER (unfade_button_eventbox), unfade_button); + + fade_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (fade_hbox, "fade_hbox"); + gtk_widget_ref (fade_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_hbox", fade_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_hbox); + gtk_box_pack_start (GTK_BOX (cmap_vbox), fade_hbox, FALSE, FALSE, 0); + + fade_dummy = gtk_label_new (""); + gtk_widget_set_name (fade_dummy, "fade_dummy"); + gtk_widget_ref (fade_dummy); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_dummy", fade_dummy, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_dummy); + gtk_box_pack_start (GTK_BOX (fade_hbox), fade_dummy, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (fade_dummy), GTK_JUSTIFY_LEFT); + gtk_misc_set_padding (GTK_MISC (fade_dummy), 3, 0); + + fade_label = gtk_label_new (_("Fade Duration")); + gtk_widget_set_name (fade_label, "fade_label"); + gtk_widget_ref (fade_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_label", fade_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_label); + gtk_box_pack_start (GTK_BOX (fade_hbox), fade_label, FALSE, FALSE, 10); + gtk_label_set_justify (GTK_LABEL (fade_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (fade_label), 0, 0.5); + + fade_spinbutton_adj = gtk_adjustment_new (0, 0, 10, 1, 1, 1); + fade_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (fade_spinbutton_adj), 1, 0); + gtk_widget_set_name (fade_spinbutton, "fade_spinbutton"); + gtk_widget_ref (fade_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_spinbutton", fade_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_spinbutton); + gtk_box_pack_start (GTK_BOX (fade_hbox), fade_spinbutton, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, fade_spinbutton, _("How long it should take for the screen to fade in and out."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (fade_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (fade_spinbutton), TRUE); + + fade_sec_label = gtk_label_new (_("seconds")); + gtk_widget_set_name (fade_sec_label, "fade_sec_label"); + gtk_widget_ref (fade_sec_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "fade_sec_label", fade_sec_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (fade_sec_label); + gtk_box_pack_start (GTK_BOX (fade_hbox), fade_sec_label, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (fade_sec_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (fade_sec_label), 0, 0.5); + + dpms_frame = gtk_frame_new (_("Display Power Management")); + gtk_widget_set_name (dpms_frame, "dpms_frame"); + gtk_widget_ref (dpms_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_frame", dpms_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_frame); + gtk_table_attach (GTK_TABLE (options_table), dpms_frame, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_container_set_border_width (GTK_CONTAINER (dpms_frame), 10); + + dpms_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_set_name (dpms_hbox, "dpms_hbox"); + gtk_widget_ref (dpms_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_hbox", dpms_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_hbox); + gtk_container_add (GTK_CONTAINER (dpms_frame), dpms_hbox); + gtk_container_set_border_width (GTK_CONTAINER (dpms_hbox), 8); + + dpms_logo = create_pixmap (xscreensaver_demo, "screensaver-power.png"); + gtk_widget_set_name (dpms_logo, "dpms_logo"); + gtk_widget_ref (dpms_logo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_logo", dpms_logo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_logo); + gtk_box_pack_start (GTK_BOX (dpms_hbox), dpms_logo, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (dpms_logo), 0.5, 0); + + dpms_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (dpms_vbox, "dpms_vbox"); + gtk_widget_ref (dpms_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_vbox", dpms_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_vbox); + gtk_box_pack_start (GTK_BOX (dpms_hbox), dpms_vbox, FALSE, FALSE, 0); + + dpms_button_eventbox = gtk_event_box_new (); + gtk_widget_set_name (dpms_button_eventbox, "dpms_button_eventbox"); + gtk_widget_ref (dpms_button_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_button_eventbox", dpms_button_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_button_eventbox); + gtk_box_pack_start (GTK_BOX (dpms_vbox), dpms_button_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, dpms_button_eventbox, _("Whether the monitor should be powered down after a while."), NULL); + + dpms_button = gtk_check_button_new_with_label (_("Power Management Enabled")); + gtk_widget_set_name (dpms_button, "dpms_button"); + gtk_widget_ref (dpms_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_button", dpms_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_button); + gtk_container_add (GTK_CONTAINER (dpms_button_eventbox), dpms_button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dpms_button), TRUE); + + dpms_table = gtk_table_new (3, 3, FALSE); + gtk_widget_set_name (dpms_table, "dpms_table"); + gtk_widget_ref (dpms_table); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_table", dpms_table, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_table); + gtk_box_pack_start (GTK_BOX (dpms_vbox), dpms_table, FALSE, FALSE, 0); + gtk_table_set_row_spacings (GTK_TABLE (dpms_table), 2); + + dpms_standby_spinbutton_adj = gtk_adjustment_new (0, 0, 1440, 1, 30, 30); + dpms_standby_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (dpms_standby_spinbutton_adj), 15, 0); + gtk_widget_set_name (dpms_standby_spinbutton, "dpms_standby_spinbutton"); + gtk_widget_ref (dpms_standby_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_standby_spinbutton", dpms_standby_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_standby_spinbutton); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_standby_spinbutton, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_tooltips_set_tip (tooltips, dpms_standby_spinbutton, _("How long before the monitor goes completely black."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (dpms_standby_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (dpms_standby_spinbutton), TRUE); + + dpms_standby_mlabel = gtk_label_new (_("minutes")); + gtk_widget_set_name (dpms_standby_mlabel, "dpms_standby_mlabel"); + gtk_widget_ref (dpms_standby_mlabel); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_standby_mlabel", dpms_standby_mlabel, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_standby_mlabel); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_standby_mlabel, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (dpms_standby_mlabel), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (dpms_standby_mlabel), 0, 0.5); + + dpms_suspend_mlabel = gtk_label_new (_("minutes")); + gtk_widget_set_name (dpms_suspend_mlabel, "dpms_suspend_mlabel"); + gtk_widget_ref (dpms_suspend_mlabel); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_suspend_mlabel", dpms_suspend_mlabel, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_suspend_mlabel); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_suspend_mlabel, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (dpms_suspend_mlabel), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (dpms_suspend_mlabel), 0, 0.5); + + dpms_off_mlabel = gtk_label_new (_("minutes")); + gtk_widget_set_name (dpms_off_mlabel, "dpms_off_mlabel"); + gtk_widget_ref (dpms_off_mlabel); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_off_mlabel", dpms_off_mlabel, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_off_mlabel); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_off_mlabel, 2, 3, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (dpms_off_mlabel), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (dpms_off_mlabel), 0, 0.5); + + dpms_off_label = gtk_label_new (_("Off After")); + gtk_widget_set_name (dpms_off_label, "dpms_off_label"); + gtk_widget_ref (dpms_off_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_off_label", dpms_off_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_off_label); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_off_label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (dpms_off_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (dpms_off_label), 1, 0.5); + gtk_misc_set_padding (GTK_MISC (dpms_off_label), 10, 0); + + dpms_suspend_label = gtk_label_new (_("Suspend After")); + gtk_widget_set_name (dpms_suspend_label, "dpms_suspend_label"); + gtk_widget_ref (dpms_suspend_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_suspend_label", dpms_suspend_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_suspend_label); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_suspend_label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (dpms_suspend_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (dpms_suspend_label), 1, 0.5); + gtk_misc_set_padding (GTK_MISC (dpms_suspend_label), 10, 0); + + dpms_standby_label = gtk_label_new (_("Standby After")); + gtk_widget_set_name (dpms_standby_label, "dpms_standby_label"); + gtk_widget_ref (dpms_standby_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_standby_label", dpms_standby_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_standby_label); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_standby_label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (dpms_standby_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (dpms_standby_label), 1, 0.5); + gtk_misc_set_padding (GTK_MISC (dpms_standby_label), 10, 0); + + dpms_suspend_spinbutton_adj = gtk_adjustment_new (0, 0, 1440, 1, 30, 30); + dpms_suspend_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (dpms_suspend_spinbutton_adj), 15, 0); + gtk_widget_set_name (dpms_suspend_spinbutton, "dpms_suspend_spinbutton"); + gtk_widget_ref (dpms_suspend_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_suspend_spinbutton", dpms_suspend_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_suspend_spinbutton); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_suspend_spinbutton, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_tooltips_set_tip (tooltips, dpms_suspend_spinbutton, _("How long until the monitor goes into power-saving mode."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (dpms_suspend_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (dpms_suspend_spinbutton), TRUE); + + dpms_off_spinbutton_adj = gtk_adjustment_new (0, 0, 1440, 1, 30, 30); + dpms_off_spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (dpms_off_spinbutton_adj), 15, 0); + gtk_widget_set_name (dpms_off_spinbutton, "dpms_off_spinbutton"); + gtk_widget_ref (dpms_off_spinbutton); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "dpms_off_spinbutton", dpms_off_spinbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dpms_off_spinbutton); + gtk_table_attach (GTK_TABLE (dpms_table), dpms_off_spinbutton, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_tooltips_set_tip (tooltips, dpms_off_spinbutton, _("How long until the monitor powers down."), NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (dpms_off_spinbutton), TRUE); + gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (dpms_off_spinbutton), TRUE); + + grab_frame = gtk_frame_new (_("Image Manipulation")); + gtk_widget_set_name (grab_frame, "grab_frame"); + gtk_widget_ref (grab_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_frame", grab_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_frame); + gtk_table_attach (GTK_TABLE (options_table), grab_frame, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_container_set_border_width (GTK_CONTAINER (grab_frame), 10); + + grab_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_set_name (grab_hbox, "grab_hbox"); + gtk_widget_ref (grab_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_hbox", grab_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_hbox); + gtk_container_add (GTK_CONTAINER (grab_frame), grab_hbox); + gtk_container_set_border_width (GTK_CONTAINER (grab_hbox), 8); + + img_logo = create_pixmap (xscreensaver_demo, "screensaver-snap.png"); + gtk_widget_set_name (img_logo, "img_logo"); + gtk_widget_ref (img_logo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "img_logo", img_logo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (img_logo); + gtk_box_pack_start (GTK_BOX (grab_hbox), img_logo, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (img_logo), 0.5, 0); + + grab_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (grab_vbox, "grab_vbox"); + gtk_widget_ref (grab_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_vbox", grab_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_vbox); + gtk_box_pack_start (GTK_BOX (grab_hbox), grab_vbox, TRUE, TRUE, 0); + + grab_desk_eventbox = gtk_event_box_new (); + gtk_widget_set_name (grab_desk_eventbox, "grab_desk_eventbox"); + gtk_widget_ref (grab_desk_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_desk_eventbox", grab_desk_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_desk_eventbox); + gtk_box_pack_start (GTK_BOX (grab_vbox), grab_desk_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, grab_desk_eventbox, _("Whether the image-manipulating modes should be allowed to operate on an image of your desktop."), NULL); + + grab_desk_button = gtk_check_button_new_with_label (_("Grab Desktop Images")); + gtk_widget_set_name (grab_desk_button, "grab_desk_button"); + gtk_widget_ref (grab_desk_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_desk_button", grab_desk_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_desk_button); + gtk_container_add (GTK_CONTAINER (grab_desk_eventbox), grab_desk_button); + + grab_video_eventbox = gtk_event_box_new (); + gtk_widget_set_name (grab_video_eventbox, "grab_video_eventbox"); + gtk_widget_ref (grab_video_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_video_eventbox", grab_video_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_video_eventbox); + gtk_box_pack_start (GTK_BOX (grab_vbox), grab_video_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, grab_video_eventbox, _("Whether the image-manipulating modes should operate on images captured from the system's video input (if there is one)."), NULL); + + grab_video_button = gtk_check_button_new_with_label (_("Grab Video Frames")); + gtk_widget_set_name (grab_video_button, "grab_video_button"); + gtk_widget_ref (grab_video_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_video_button", grab_video_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_video_button); + gtk_container_add (GTK_CONTAINER (grab_video_eventbox), grab_video_button); + + grab_image_eventbox = gtk_event_box_new (); + gtk_widget_set_name (grab_image_eventbox, "grab_image_eventbox"); + gtk_widget_ref (grab_image_eventbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_image_eventbox", grab_image_eventbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_image_eventbox); + gtk_box_pack_start (GTK_BOX (grab_vbox), grab_image_eventbox, FALSE, FALSE, 0); + gtk_tooltips_set_tip (tooltips, grab_image_eventbox, _("Whether the image-manipulating modes should operate on random images loaded from disk."), NULL); + + grab_image_button = gtk_check_button_new_with_label (_("Choose Random Image:")); + gtk_widget_set_name (grab_image_button, "grab_image_button"); + gtk_widget_ref (grab_image_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_image_button", grab_image_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_image_button); + gtk_container_add (GTK_CONTAINER (grab_image_eventbox), grab_image_button); + + image_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (image_hbox, "image_hbox"); + gtk_widget_ref (image_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "image_hbox", image_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (image_hbox); + gtk_box_pack_start (GTK_BOX (grab_vbox), image_hbox, FALSE, FALSE, 0); + + grab_dummy = gtk_label_new (""); + gtk_widget_set_name (grab_dummy, "grab_dummy"); + gtk_widget_ref (grab_dummy); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "grab_dummy", grab_dummy, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (grab_dummy); + gtk_box_pack_start (GTK_BOX (image_hbox), grab_dummy, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (grab_dummy), GTK_JUSTIFY_LEFT); + gtk_misc_set_padding (GTK_MISC (grab_dummy), 8, 0); + + image_text = gtk_entry_new (); + gtk_widget_set_name (image_text, "image_text"); + gtk_widget_ref (image_text); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "image_text", image_text, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (image_text); + gtk_box_pack_start (GTK_BOX (image_hbox), image_text, TRUE, TRUE, 0); + gtk_tooltips_set_tip (tooltips, image_text, _("The directory from which images will be randomly chosen."), NULL); + + image_browse_button = gtk_button_new_with_label (_("Browse")); + gtk_widget_set_name (image_browse_button, "image_browse_button"); + gtk_widget_ref (image_browse_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "image_browse_button", image_browse_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (image_browse_button); + gtk_box_pack_start (GTK_BOX (image_hbox), image_browse_button, FALSE, FALSE, 4); + + options_tab = gtk_label_new (_("Advanced")); + gtk_widget_set_name (options_tab, "options_tab"); + gtk_widget_ref (options_tab); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_demo), "options_tab", options_tab, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (options_tab); + gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), 1), options_tab); + + gtk_signal_connect (GTK_OBJECT (activate_menu), "activate", + GTK_SIGNAL_FUNC (activate_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (lock_menu), "activate", + GTK_SIGNAL_FUNC (lock_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (kill_menu), "activate", + GTK_SIGNAL_FUNC (kill_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (restart), "activate", + GTK_SIGNAL_FUNC (restart_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (exit_menu), "activate", + GTK_SIGNAL_FUNC (exit_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (about_menu), "activate", + GTK_SIGNAL_FUNC (about_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (doc_menu), "activate", + GTK_SIGNAL_FUNC (doc_menu_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (notebook), "switch_page", + GTK_SIGNAL_FUNC (switch_page_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (lock_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (timeout_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (timeout_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (timeout_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (lock_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (lock_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (lock_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (cycle_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (cycle_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (cycle_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (demo), "clicked", + GTK_SIGNAL_FUNC (run_this_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (settings), "clicked", + GTK_SIGNAL_FUNC (settings_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (next), "clicked", + GTK_SIGNAL_FUNC (run_next_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (prev), "clicked", + GTK_SIGNAL_FUNC (run_prev_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (verbose_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (capture_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (splash_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (install_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (fade_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (unfade_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (fade_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (fade_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (fade_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_standby_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_standby_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_standby_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_suspend_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_suspend_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_suspend_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_off_spinbutton), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_off_spinbutton), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (dpms_off_spinbutton), "changed", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (grab_desk_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (grab_video_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (grab_image_button), "toggled", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (image_text), "activate", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (image_text), "focus_out_event", + GTK_SIGNAL_FUNC (pref_changed_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (image_browse_button), "clicked", + GTK_SIGNAL_FUNC (browse_image_dir_cb), + NULL); + + gtk_widget_grab_default (next); + gtk_object_set_data (GTK_OBJECT (xscreensaver_demo), "tooltips", tooltips); + + gtk_window_add_accel_group (GTK_WINDOW (xscreensaver_demo), accel_group); + + return xscreensaver_demo; +} + +GtkWidget* +create_xscreensaver_settings_dialog (void) +{ + GtkWidget *xscreensaver_settings_dialog; + GtkWidget *dialog_vbox; + GtkWidget *dialog_top_table; + GtkWidget *opt_frame; + GtkWidget *opt_notebook; + GtkWidget *settings_vbox; + GtkWidget *std_label; + GtkWidget *opt_table; + GtkWidget *cmd_logo; + GtkWidget *visual_hbox; + GtkWidget *visual; + GtkWidget *visual_combo; + GList *visual_combo_items = NULL; + GtkWidget *combo_entry1; + GtkWidget *cmd_label; + GtkWidget *cmd_text; + GtkWidget *adv_label; + GtkWidget *doc_frame; + GtkWidget *doc_vbox; + GtkWidget *doc; + GtkWidget *doc_hbuttonbox; + GtkWidget *manual; + GtkWidget *dialog_action_area; + GtkWidget *actionarea_hbox; + GtkWidget *dialog_hbuttonbox; + GtkWidget *adv_button; + GtkWidget *std_button; + GtkWidget *reset_button; + GtkWidget *ok_cancel_hbuttonbox; + GtkWidget *ok_button; + GtkWidget *cancel_button; + GtkTooltips *tooltips; + + tooltips = gtk_tooltips_new (); + + xscreensaver_settings_dialog = gtk_dialog_new (); + gtk_widget_set_name (xscreensaver_settings_dialog, "xscreensaver_settings_dialog"); + gtk_object_set_data (GTK_OBJECT (xscreensaver_settings_dialog), "xscreensaver_settings_dialog", xscreensaver_settings_dialog); + gtk_window_set_title (GTK_WINDOW (xscreensaver_settings_dialog), _("XScreenSaver: Mode-Specific Settings")); + GTK_WINDOW (xscreensaver_settings_dialog)->type = GTK_WINDOW_DIALOG; + gtk_window_set_modal (GTK_WINDOW (xscreensaver_settings_dialog), TRUE); + gtk_window_set_policy (GTK_WINDOW (xscreensaver_settings_dialog), TRUE, TRUE, FALSE); + gtk_window_set_wmclass (GTK_WINDOW (xscreensaver_settings_dialog), "settings", "XScreenSaver"); + + dialog_vbox = GTK_DIALOG (xscreensaver_settings_dialog)->vbox; + gtk_widget_set_name (dialog_vbox, "dialog_vbox"); + gtk_object_set_data (GTK_OBJECT (xscreensaver_settings_dialog), "dialog_vbox", dialog_vbox); + gtk_widget_show (dialog_vbox); + + dialog_top_table = gtk_table_new (1, 2, FALSE); + gtk_widget_set_name (dialog_top_table, "dialog_top_table"); + gtk_widget_ref (dialog_top_table); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "dialog_top_table", dialog_top_table, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dialog_top_table); + gtk_box_pack_start (GTK_BOX (dialog_vbox), dialog_top_table, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (dialog_top_table), 8); + gtk_table_set_row_spacings (GTK_TABLE (dialog_top_table), 8); + gtk_table_set_col_spacings (GTK_TABLE (dialog_top_table), 8); + + opt_frame = gtk_frame_new (_("Settings")); + gtk_widget_set_name (opt_frame, "opt_frame"); + gtk_widget_ref (opt_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "opt_frame", opt_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (opt_frame); + gtk_table_attach (GTK_TABLE (dialog_top_table), opt_frame, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 4, 8); + + opt_notebook = gtk_notebook_new (); + gtk_widget_set_name (opt_notebook, "opt_notebook"); + gtk_widget_ref (opt_notebook); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "opt_notebook", opt_notebook, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (opt_notebook); + gtk_container_add (GTK_CONTAINER (opt_frame), opt_notebook); + gtk_container_set_border_width (GTK_CONTAINER (opt_notebook), 12); + gtk_notebook_set_show_border (GTK_NOTEBOOK (opt_notebook), FALSE); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (opt_notebook), GTK_POS_BOTTOM); + + settings_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (settings_vbox, "settings_vbox"); + gtk_widget_ref (settings_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "settings_vbox", settings_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (settings_vbox); + gtk_container_add (GTK_CONTAINER (opt_notebook), settings_vbox); + + std_label = gtk_label_new (_("Standard")); + gtk_widget_set_name (std_label, "std_label"); + gtk_widget_ref (std_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "std_label", std_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (std_label); + gtk_notebook_set_tab_label (GTK_NOTEBOOK (opt_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (opt_notebook), 0), std_label); + + opt_table = gtk_table_new (4, 2, FALSE); + gtk_widget_set_name (opt_table, "opt_table"); + gtk_widget_ref (opt_table); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "opt_table", opt_table, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (opt_table); + gtk_container_add (GTK_CONTAINER (opt_notebook), opt_table); + + cmd_logo = create_pixmap (xscreensaver_settings_dialog, "screensaver-cmndln.png"); + gtk_widget_set_name (cmd_logo, "cmd_logo"); + gtk_widget_ref (cmd_logo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "cmd_logo", cmd_logo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmd_logo); + gtk_table_attach (GTK_TABLE (opt_table), cmd_logo, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_padding (GTK_MISC (cmd_logo), 4, 8); + + visual_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (visual_hbox, "visual_hbox"); + gtk_widget_ref (visual_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "visual_hbox", visual_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (visual_hbox); + gtk_table_attach (GTK_TABLE (opt_table), visual_hbox, 1, 2, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + visual = gtk_label_new (_("Visual:")); + gtk_widget_set_name (visual, "visual"); + gtk_widget_ref (visual); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "visual", visual, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (visual); + gtk_box_pack_start (GTK_BOX (visual_hbox), visual, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (visual), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (visual), 1, 0.5); + gtk_misc_set_padding (GTK_MISC (visual), 4, 0); + + visual_combo = gtk_combo_new (); + gtk_widget_set_name (visual_combo, "visual_combo"); + gtk_widget_ref (visual_combo); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "visual_combo", visual_combo, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (visual_combo); + gtk_box_pack_start (GTK_BOX (visual_hbox), visual_combo, FALSE, FALSE, 0); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Any")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Best")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Default")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Default-N")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("GL")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("TrueColor")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("PseudoColor")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("StaticGray")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("GrayScale")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("DirectColor")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Color")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Gray")); + visual_combo_items = g_list_append (visual_combo_items, (gpointer) _("Mono")); + gtk_combo_set_popdown_strings (GTK_COMBO (visual_combo), visual_combo_items); + g_list_free (visual_combo_items); + + combo_entry1 = GTK_COMBO (visual_combo)->entry; + gtk_widget_set_name (combo_entry1, "combo_entry1"); + gtk_widget_ref (combo_entry1); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "combo_entry1", combo_entry1, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (combo_entry1); + gtk_tooltips_set_tip (tooltips, combo_entry1, _("The X visual type that this demo will require. If that visual is available it will be used, otherwise, this demo will not be run."), NULL); + gtk_entry_set_text (GTK_ENTRY (combo_entry1), _("Any")); + + cmd_label = gtk_label_new (_("Command Line:")); + gtk_widget_set_name (cmd_label, "cmd_label"); + gtk_widget_ref (cmd_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "cmd_label", cmd_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmd_label); + gtk_table_attach (GTK_TABLE (opt_table), cmd_label, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_label_set_justify (GTK_LABEL (cmd_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (cmd_label), 0, 1); + gtk_misc_set_padding (GTK_MISC (cmd_label), 0, 2); + + cmd_text = gtk_entry_new (); + gtk_widget_set_name (cmd_text, "cmd_text"); + gtk_widget_ref (cmd_text); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "cmd_text", cmd_text, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cmd_text); + gtk_table_attach (GTK_TABLE (opt_table), cmd_text, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize (cmd_text, 80, -2); + + adv_label = gtk_label_new (_("Advanced")); + gtk_widget_set_name (adv_label, "adv_label"); + gtk_widget_ref (adv_label); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "adv_label", adv_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (adv_label); + gtk_notebook_set_tab_label (GTK_NOTEBOOK (opt_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (opt_notebook), 1), adv_label); + + doc_frame = gtk_frame_new (_("Description")); + gtk_widget_set_name (doc_frame, "doc_frame"); + gtk_widget_ref (doc_frame); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "doc_frame", doc_frame, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (doc_frame); + gtk_table_attach (GTK_TABLE (dialog_top_table), doc_frame, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), 4, 8); + + doc_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (doc_vbox, "doc_vbox"); + gtk_widget_ref (doc_vbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "doc_vbox", doc_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (doc_vbox); + gtk_container_add (GTK_CONTAINER (doc_frame), doc_vbox); + + doc = gtk_label_new (""); + gtk_widget_set_name (doc, "doc"); + gtk_widget_ref (doc); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "doc", doc, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (doc); + gtk_box_pack_start (GTK_BOX (doc_vbox), doc, TRUE, TRUE, 0); + gtk_label_set_justify (GTK_LABEL (doc), GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap (GTK_LABEL (doc), TRUE); + gtk_misc_set_alignment (GTK_MISC (doc), 0, 0); + gtk_misc_set_padding (GTK_MISC (doc), 10, 10); + + doc_hbuttonbox = gtk_hbutton_box_new (); + gtk_widget_set_name (doc_hbuttonbox, "doc_hbuttonbox"); + gtk_widget_ref (doc_hbuttonbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "doc_hbuttonbox", doc_hbuttonbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (doc_hbuttonbox); + gtk_box_pack_end (GTK_BOX (doc_vbox), doc_hbuttonbox, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (doc_hbuttonbox), 4); + gtk_button_box_set_layout (GTK_BUTTON_BOX (doc_hbuttonbox), GTK_BUTTONBOX_END); + + manual = gtk_button_new_with_label (_("Documentation...")); + gtk_widget_set_name (manual, "manual"); + gtk_widget_ref (manual); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "manual", manual, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (manual); + gtk_container_add (GTK_CONTAINER (doc_hbuttonbox), manual); + STFU GTK_WIDGET_SET_FLAGS (manual, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, manual, _("Click here to read the manual for this display mode, if it has one."), NULL); + + dialog_action_area = GTK_DIALOG (xscreensaver_settings_dialog)->action_area; + gtk_widget_set_name (dialog_action_area, "dialog_action_area"); + gtk_object_set_data (GTK_OBJECT (xscreensaver_settings_dialog), "dialog_action_area", dialog_action_area); + gtk_widget_show (dialog_action_area); + gtk_container_set_border_width (GTK_CONTAINER (dialog_action_area), 10); + + actionarea_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_set_name (actionarea_hbox, "actionarea_hbox"); + gtk_widget_ref (actionarea_hbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "actionarea_hbox", actionarea_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (actionarea_hbox); + gtk_box_pack_start (GTK_BOX (dialog_action_area), actionarea_hbox, TRUE, TRUE, 0); + + dialog_hbuttonbox = gtk_hbutton_box_new (); + gtk_widget_set_name (dialog_hbuttonbox, "dialog_hbuttonbox"); + gtk_widget_ref (dialog_hbuttonbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "dialog_hbuttonbox", dialog_hbuttonbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (dialog_hbuttonbox); + gtk_box_pack_start (GTK_BOX (actionarea_hbox), dialog_hbuttonbox, TRUE, TRUE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_hbuttonbox), GTK_BUTTONBOX_SPREAD); + + adv_button = gtk_button_new_with_label (_("Advanced >>")); + gtk_widget_set_name (adv_button, "adv_button"); + gtk_widget_ref (adv_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "adv_button", adv_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (adv_button); + gtk_container_add (GTK_CONTAINER (dialog_hbuttonbox), adv_button); + STFU GTK_WIDGET_SET_FLAGS (adv_button, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, adv_button, _("Edit the command line directly."), NULL); + + std_button = gtk_button_new_with_label (_("Standard <<")); + gtk_widget_set_name (std_button, "std_button"); + gtk_widget_ref (std_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "std_button", std_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (std_button); + gtk_container_add (GTK_CONTAINER (dialog_hbuttonbox), std_button); + STFU GTK_WIDGET_SET_FLAGS (std_button, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, std_button, _("Back to the graphical configuration options."), NULL); + + reset_button = gtk_button_new_with_label (_("Reset to Defaults")); + gtk_widget_set_name (reset_button, "reset_button"); + gtk_widget_ref (reset_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "reset_button", reset_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (reset_button); + gtk_container_add (GTK_CONTAINER (dialog_hbuttonbox), reset_button); + STFU GTK_WIDGET_SET_FLAGS (reset_button, GTK_CAN_DEFAULT); + gtk_tooltips_set_tip (tooltips, reset_button, _("Reset this screen saver to the default settings."), NULL); + + ok_cancel_hbuttonbox = gtk_hbutton_box_new (); + gtk_widget_set_name (ok_cancel_hbuttonbox, "ok_cancel_hbuttonbox"); + gtk_widget_ref (ok_cancel_hbuttonbox); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "ok_cancel_hbuttonbox", ok_cancel_hbuttonbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (ok_cancel_hbuttonbox); + gtk_box_pack_start (GTK_BOX (actionarea_hbox), ok_cancel_hbuttonbox, TRUE, TRUE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (ok_cancel_hbuttonbox), GTK_BUTTONBOX_END); + + ok_button = gtk_button_new_with_label (_("OK")); + gtk_widget_set_name (ok_button, "ok_button"); + gtk_widget_ref (ok_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "ok_button", ok_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (ok_button); + gtk_container_add (GTK_CONTAINER (ok_cancel_hbuttonbox), ok_button); + STFU GTK_WIDGET_SET_FLAGS (ok_button, GTK_CAN_DEFAULT); + + cancel_button = gtk_button_new_with_label (_("Cancel")); + gtk_widget_set_name (cancel_button, "cancel_button"); + gtk_widget_ref (cancel_button); + gtk_object_set_data_full (GTK_OBJECT (xscreensaver_settings_dialog), "cancel_button", cancel_button, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (cancel_button); + gtk_container_add (GTK_CONTAINER (ok_cancel_hbuttonbox), cancel_button); + STFU GTK_WIDGET_SET_FLAGS (cancel_button, GTK_CAN_DEFAULT); + + gtk_signal_connect (GTK_OBJECT (opt_notebook), "switch_page", + GTK_SIGNAL_FUNC (settings_switch_page_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (manual), "clicked", + GTK_SIGNAL_FUNC (manual_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (adv_button), "clicked", + GTK_SIGNAL_FUNC (settings_adv_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (std_button), "clicked", + GTK_SIGNAL_FUNC (settings_std_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (reset_button), "clicked", + GTK_SIGNAL_FUNC (settings_reset_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (ok_button), "clicked", + GTK_SIGNAL_FUNC (settings_ok_cb), + NULL); + gtk_signal_connect (GTK_OBJECT (cancel_button), "clicked", + GTK_SIGNAL_FUNC (settings_cancel_cb), + NULL); + + gtk_object_set_data (GTK_OBJECT (xscreensaver_settings_dialog), "tooltips", tooltips); + + return xscreensaver_settings_dialog; +} + diff --git a/driver/demo-Gtk-widgets.h b/driver/demo-Gtk-widgets.h new file mode 100644 index 00000000..298c5171 --- /dev/null +++ b/driver/demo-Gtk-widgets.h @@ -0,0 +1,6 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +GtkWidget* create_xscreensaver_demo (void); +GtkWidget* create_xscreensaver_settings_dialog (void); diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c new file mode 100644 index 00000000..a62d0b77 --- /dev/null +++ b/driver/demo-Gtk.c @@ -0,0 +1,5261 @@ +/* demo-Gtk.c --- implements the interactive demo-mode and options dialogs. + * xscreensaver, Copyright (c) 1993-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_GTK /* whole file */ + +#include + +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +# ifdef __GNUC__ +# define STFU __extension__ /* ignore gcc -pendantic warnings in next sexp */ +# else +# define STFU /* */ +# endif + + +#ifdef ENABLE_NLS +# include +#endif /* ENABLE_NLS */ + +#ifndef VMS +# include /* for getpwuid() */ +#else /* VMS */ +# include "vms-pwd.h" +#endif /* VMS */ + +#ifdef HAVE_UNAME +# include /* for uname() */ +#endif /* HAVE_UNAME */ + +#include +#include +#include +#include + + +#include +#include +#ifdef HAVE_SYS_WAIT_H +# include /* for waitpid() and associated macros */ +#endif + + +#include /* for CARD32 */ +#include /* for XA_INTEGER */ +#include +#include + +/* We don't actually use any widget internals, but these are included + so that gdb will have debug info for the widgets... */ +#include +#include + +#ifdef HAVE_XMU +# ifndef VMS +# include +# else /* VMS */ +# include +# endif +#else +# include "xmu.h" +#endif + +#ifdef HAVE_XINERAMA +# include +#endif /* HAVE_XINERAMA */ + +#include + +#ifdef HAVE_CRAPPLET +# include +# include +#endif + +#include + +#ifdef HAVE_GTK2 +# include +# include +#else /* !HAVE_GTK2 */ +# define G_MODULE_EXPORT /**/ +#endif /* !HAVE_GTK2 */ + +#if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR) +# define GLADE_DIR DEFAULT_ICONDIR +#endif +#if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR) +# define DEFAULT_ICONDIR GLADE_DIR +#endif + +#ifndef HAVE_XML + /* Kludge: this is defined in demo-Gtk-conf.c when HAVE_XML. + It is unused otherwise, so in that case, stub it out. */ + static const char *hack_configuration_path = 0; +#endif + + + +#include "version.h" +#include "prefs.h" +#include "resources.h" /* for parse_time() */ +#include "visual.h" /* for has_writable_cells() */ +#include "remote.h" /* for xscreensaver_command() */ +#include "usleep.h" + +#include "logo-50.xpm" +#include "logo-180.xpm" + +#undef dgettext /* else these are defined twice... */ +#undef dcgettext + +#include "demo-Gtk-widgets.h" +#include "demo-Gtk-support.h" +#include "demo-Gtk-conf.h" + +#include +#include +#include + +#ifdef HAVE_GTK2 +enum { + COL_ENABLED, + COL_NAME, + COL_LAST +}; +#endif /* HAVE_GTK2 */ + +/* from exec.c */ +extern void exec_command (const char *shell, const char *command, int nice); +extern int on_path_p (const char *program); + +static void hack_subproc_environment (Window preview_window_id, Bool debug_p); + +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + + +/* You might think that to read an array of 32-bit quantities out of a + server-side property, you would pass an array of 32-bit data quantities + into XGetWindowProperty(). You would be wrong. You have to use an array + of longs, even if long is 64 bits (using 32 of each 64.) + */ +typedef long PROP32; + +char *progname = 0; +char *progclass = "XScreenSaver"; +XrmDatabase db; + +/* The order of the items in the mode menu. */ +static int mode_menu_order[] = { + DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME }; + + +typedef struct { + + char *short_version; /* version number of this xscreensaver build */ + + GtkWidget *toplevel_widget; /* the main window */ + GtkWidget *base_widget; /* root of our hierarchy (for name lookups) */ + GtkWidget *popup_widget; /* the "Settings" dialog */ + conf_data *cdata; /* private data for per-hack configuration */ + +#ifdef HAVE_GTK2 + GladeXML *glade_ui; /* Glade UI file */ +#endif /* HAVE_GTK2 */ + + Bool debug_p; /* whether to print diagnostics */ + Bool initializing_p; /* flag for breaking recursion loops */ + Bool saving_p; /* flag for breaking recursion loops */ + + char *desired_preview_cmd; /* subprocess we intend to run */ + char *running_preview_cmd; /* subprocess we are currently running */ + pid_t running_preview_pid; /* pid of forked subproc (might be dead) */ + Bool running_preview_error_p; /* whether the pid died abnormally */ + + Bool preview_suppressed_p; /* flag meaning "don't launch subproc" */ + int subproc_timer_id; /* timer to delay subproc launch */ + int subproc_check_timer_id; /* timer to check whether it started up */ + int subproc_check_countdown; /* how many more checks left */ + + int *list_elt_to_hack_number; /* table for sorting the hack list */ + int *hack_number_to_list_elt; /* the inverse table */ + Bool *hacks_available_p; /* whether hacks are on $PATH */ + int total_available; /* how many are on $PATH */ + int list_count; /* how many items are in the list: this may be + less than p->screenhacks_count, if some are + suppressed. */ + + int _selected_list_element; /* don't use this: call + selected_list_element() instead */ + + int nscreens; /* How many X or Xinerama screens there are */ + + saver_preferences prefs; + +} state; + + +/* Total fucking evilness due to the fact that it's rocket science to get + a closure object of our own down into the various widget callbacks. */ +static state *global_state_kludge; + +Atom XA_VROOT; +Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION; +Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO; +Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT; + + +static void populate_demo_window (state *, int list_elt); +static void populate_prefs_page (state *); +static void populate_popup_window (state *); + +static Bool flush_dialog_changes_and_save (state *); +static Bool flush_popup_changes_and_save (state *); + +static int maybe_reload_init_file (state *); +static void await_xscreensaver (state *); +static Bool xscreensaver_running_p (state *); +static void sensitize_menu_items (state *s, Bool force_p); +static void force_dialog_repaint (state *s); + +static void schedule_preview (state *, const char *cmd); +static void kill_preview_subproc (state *, Bool reset_p); +static void schedule_preview_check (state *); + + +/* Prototypes of functions used by the Glade-generated code, + to avoid warnings. + */ +void exit_menu_cb (GtkMenuItem *, gpointer user_data); +void about_menu_cb (GtkMenuItem *, gpointer user_data); +void doc_menu_cb (GtkMenuItem *, gpointer user_data); +void file_menu_cb (GtkMenuItem *, gpointer user_data); +void activate_menu_cb (GtkMenuItem *, gpointer user_data); +void lock_menu_cb (GtkMenuItem *, gpointer user_data); +void kill_menu_cb (GtkMenuItem *, gpointer user_data); +void restart_menu_cb (GtkWidget *, gpointer user_data); +void run_this_cb (GtkButton *, gpointer user_data); +void manual_cb (GtkButton *, gpointer user_data); +void run_next_cb (GtkButton *, gpointer user_data); +void run_prev_cb (GtkButton *, gpointer user_data); +void pref_changed_cb (GtkWidget *, gpointer user_data); +gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer user_data); +void mode_menu_item_cb (GtkWidget *, gpointer user_data); +void switch_page_cb (GtkNotebook *, GtkNotebookPage *, + gint page_num, gpointer user_data); +void browse_image_dir_cb (GtkButton *, gpointer user_data); +void browse_text_file_cb (GtkButton *, gpointer user_data); +void browse_text_program_cb (GtkButton *, gpointer user_data); +void settings_cb (GtkButton *, gpointer user_data); +void settings_adv_cb (GtkButton *, gpointer user_data); +void settings_std_cb (GtkButton *, gpointer user_data); +void settings_reset_cb (GtkButton *, gpointer user_data); +void settings_switch_page_cb (GtkNotebook *, GtkNotebookPage *, + gint page_num, gpointer user_data); +void settings_cancel_cb (GtkButton *, gpointer user_data); +void settings_ok_cb (GtkButton *, gpointer user_data); + +static void kill_gnome_screensaver (void); +static void kill_kde_screensaver (void); + + +/* Some random utility functions + */ + +const char *blurb (void); + +const char * +blurb (void) +{ + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + static char buf[255]; + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + + +static GtkWidget * +name_to_widget (state *s, const char *name) +{ + GtkWidget *w; + if (!s) abort(); + if (!name) abort(); + if (!*name) abort(); + +#ifdef HAVE_GTK2 + if (!s->glade_ui) + { + /* First try to load the Glade file from the current directory; + if there isn't one there, check the installed directory. + */ +# define GLADE_FILE_NAME "xscreensaver-demo.glade2" + const char * const files[] = { GLADE_FILE_NAME, + GLADE_DIR "/" GLADE_FILE_NAME }; + int i; + for (i = 0; i < countof (files); i++) + { + struct stat st; + if (!stat (files[i], &st)) + { + s->glade_ui = glade_xml_new (files[i], NULL, NULL); + break; + } + } + if (!s->glade_ui) + { + fprintf (stderr, + "%s: could not load \"" GLADE_FILE_NAME "\"\n" + "\tfrom " GLADE_DIR "/ or current directory.\n", + blurb()); + exit (-1); + } +# undef GLADE_FILE_NAME + + glade_xml_signal_autoconnect (s->glade_ui); + } + + w = glade_xml_get_widget (s->glade_ui, name); + +#else /* !HAVE_GTK2 */ + + w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget), + name); + if (w) return w; + w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget), + name); +#endif /* HAVE_GTK2 */ + if (w) return w; + + fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n", + blurb(), name); + abort(); +} + + +/* Why this behavior isn't automatic in *either* toolkit, I'll never know. + Takes a scroller, viewport, or list as an argument. + */ +static void +ensure_selected_item_visible (GtkWidget *widget) +{ +#ifdef HAVE_GTK2 + GtkTreePath *path; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreeModel *model; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + path = gtk_tree_path_new_first (); + else + path = gtk_tree_model_get_path (model, &iter); + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE); + + gtk_tree_path_free (path); + +#else /* !HAVE_GTK2 */ + + GtkScrolledWindow *scroller = 0; + GtkViewport *vp = 0; + GtkList *list_widget = 0; + GList *slist; + GList *kids; + int nkids = 0; + GtkWidget *selected = 0; + int list_elt = -1; + GtkAdjustment *adj; + gint parent_h, child_y, child_h, children_h, ignore; + double ratio_t, ratio_b; + + if (GTK_IS_SCROLLED_WINDOW (widget)) + { + scroller = GTK_SCROLLED_WINDOW (widget); + vp = GTK_VIEWPORT (GTK_BIN (scroller)->child); + list_widget = GTK_LIST (GTK_BIN(vp)->child); + } + else if (GTK_IS_VIEWPORT (widget)) + { + vp = GTK_VIEWPORT (widget); + scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); + list_widget = GTK_LIST (GTK_BIN(vp)->child); + } + else if (GTK_IS_LIST (widget)) + { + list_widget = GTK_LIST (widget); + vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent); + scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); + } + else + abort(); + + slist = list_widget->selection; + selected = (slist ? GTK_WIDGET (slist->data) : 0); + if (!selected) + return; + + list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected)); + + for (kids = gtk_container_children (GTK_CONTAINER (list_widget)); + kids; kids = kids->next) + nkids++; + + adj = gtk_scrolled_window_get_vadjustment (scroller); + + gdk_window_get_geometry (GTK_WIDGET(vp)->window, + &ignore, &ignore, &ignore, &parent_h, &ignore); + gdk_window_get_geometry (GTK_WIDGET(selected)->window, + &ignore, &child_y, &ignore, &child_h, &ignore); + children_h = nkids * child_h; + + ratio_t = ((double) child_y) / ((double) children_h); + ratio_b = ((double) child_y + child_h) / ((double) children_h); + + if (adj->upper == 0.0) /* no items in list */ + return; + + if (ratio_t < (adj->value / adj->upper) || + ratio_b > ((adj->value + adj->page_size) / adj->upper)) + { + double target; + int slop = parent_h * 0.75; /* how much to overshoot by */ + + if (ratio_t < (adj->value / adj->upper)) + { + double ratio_w = ((double) parent_h) / ((double) children_h); + double ratio_l = (ratio_b - ratio_t); + target = ((ratio_t - ratio_w + ratio_l) * adj->upper); + target += slop; + } + else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/ + { + target = ratio_t * adj->upper; + target -= slop; + } + + if (target > adj->upper - adj->page_size) + target = adj->upper - adj->page_size; + if (target < 0) + target = 0; + + gtk_adjustment_set_value (adj, target); + } +#endif /* !HAVE_GTK2 */ +} + +static void +warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data) +{ + GtkWidget *shell = GTK_WIDGET (user_data); + while (shell->parent) + shell = shell->parent; + gtk_widget_destroy (GTK_WIDGET (shell)); +} + + +void restart_menu_cb (GtkWidget *widget, gpointer user_data); + +static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data) +{ + restart_menu_cb (widget, user_data); + warning_dialog_dismiss_cb (widget, user_data); +} + +static void warning_dialog_killg_cb (GtkWidget *widget, gpointer user_data) +{ + kill_gnome_screensaver (); + warning_dialog_dismiss_cb (widget, user_data); +} + +static void warning_dialog_killk_cb (GtkWidget *widget, gpointer user_data) +{ + kill_kde_screensaver (); + warning_dialog_dismiss_cb (widget, user_data); +} + +typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button; + +static void +warning_dialog (GtkWidget *parent, const char *message, + dialog_button button_type, int center) +{ + char *msg = strdup (message); + char *head; + + GtkWidget *dialog = gtk_dialog_new (); + GtkWidget *label = 0; + GtkWidget *ok = 0; + GtkWidget *cancel = 0; + int i = 0; + + while (parent && !parent->window) + parent = parent->parent; + + if (!parent || + !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */ + { + fprintf (stderr, "%s: too early for dialog?\n", progname); + return; + } + + head = msg; + while (head) + { + char name[20]; + char *s = strchr (head, '\n'); + if (s) *s = 0; + + sprintf (name, "label%d", i++); + + { + label = gtk_label_new (head); +#ifdef HAVE_GTK2 + gtk_label_set_selectable (GTK_LABEL (label), TRUE); +#endif /* HAVE_GTK2 */ + +#ifndef HAVE_GTK2 + if (i == 1) + { + GTK_WIDGET (label)->style = + gtk_style_copy (GTK_WIDGET (label)->style); + GTK_WIDGET (label)->style->font = + gdk_font_load (get_string_resource("warning_dialog.headingFont", + "Dialog.Font")); + gtk_widget_set_style (GTK_WIDGET (label), + GTK_WIDGET (label)->style); + } +#endif /* !HAVE_GTK2 */ + if (center <= 0) + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + label, TRUE, TRUE, 0); + gtk_widget_show (label); + } + + if (s) + head = s+1; + else + head = 0; + + center--; + } + + label = gtk_label_new (""); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + label, TRUE, TRUE, 0); + gtk_widget_show (label); + + label = gtk_hbutton_box_new (); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), + label, TRUE, TRUE, 0); + +#ifdef HAVE_GTK2 + if (button_type != D_NONE) + { + cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_container_add (GTK_CONTAINER (label), cancel); + } + + ok = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_container_add (GTK_CONTAINER (label), ok); + +#else /* !HAVE_GTK2 */ + + ok = gtk_button_new_with_label ("OK"); + gtk_container_add (GTK_CONTAINER (label), ok); + + if (button_type != D_NONE) + { + cancel = gtk_button_new_with_label ("Cancel"); + gtk_container_add (GTK_CONTAINER (label), cancel); + } + +#endif /* !HAVE_GTK2 */ + + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); + gtk_window_set_title (GTK_WINDOW (dialog), progclass); + GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT); + gtk_widget_show (ok); + gtk_widget_grab_focus (ok); + + if (cancel) + { + GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); + gtk_widget_show (cancel); + } + gtk_widget_show (label); + gtk_widget_show (dialog); + + if (button_type != D_NONE) + { + GtkSignalFunc fn; + switch (button_type) { + case D_LAUNCH: fn = GTK_SIGNAL_FUNC (warning_dialog_restart_cb); break; + case D_GNOME: fn = GTK_SIGNAL_FUNC (warning_dialog_killg_cb); break; + case D_KDE: fn = GTK_SIGNAL_FUNC (warning_dialog_killk_cb); break; + default: abort(); break; + } + gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", fn, + (gpointer) dialog); + gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), + (gpointer) dialog); + } + else + { + gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), + (gpointer) dialog); + } + + gdk_window_set_transient_for (GTK_WIDGET (dialog)->window, + GTK_WIDGET (parent)->window); + +#ifdef HAVE_GTK2 + gtk_window_present (GTK_WINDOW (dialog)); +#else /* !HAVE_GTK2 */ + gdk_window_show (GTK_WIDGET (dialog)->window); + gdk_window_raise (GTK_WIDGET (dialog)->window); +#endif /* !HAVE_GTK2 */ + + free (msg); +} + + +static void +run_cmd (state *s, Atom command, int arg) +{ + char *err = 0; + int status; + + flush_dialog_changes_and_save (s); + status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err); + + /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */ + if (status < 0 && err && strstr (err, "unexpectedly deleted")) + status = 0; + + if (status < 0) + { + char buf [255]; + if (err) + sprintf (buf, "Error:\n\n%s", err); + else + strcpy (buf, "Unknown error!"); + warning_dialog (s->toplevel_widget, buf, D_NONE, 100); + } + if (err) free (err); + + sensitize_menu_items (s, True); + force_dialog_repaint (s); +} + + +static void +run_hack (state *s, int list_elt, Bool report_errors_p) +{ + int hack_number; + char *err = 0; + int status; + + if (list_elt < 0) return; + hack_number = s->list_elt_to_hack_number[list_elt]; + + flush_dialog_changes_and_save (s); + schedule_preview (s, 0); + + status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1, + False, &err); + + if (status < 0 && report_errors_p) + { + if (xscreensaver_running_p (s)) + { + /* Kludge: ignore the spurious "window unexpectedly deleted" + errors... */ + if (err && strstr (err, "unexpectedly deleted")) + status = 0; + + if (status < 0) + { + char buf [255]; + if (err) + sprintf (buf, "Error:\n\n%s", err); + else + strcpy (buf, "Unknown error!"); + warning_dialog (s->toplevel_widget, buf, D_NONE, 100); + } + } + else + { + /* The error is that the daemon isn't running; + offer to restart it. + */ + const char *d = DisplayString (GDK_DISPLAY()); + char msg [1024]; + sprintf (msg, + _("Warning:\n\n" + "The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%s\". Launch it now?"), + d); + warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1); + } + } + + if (err) free (err); + + sensitize_menu_items (s, False); +} + + + +/* Button callbacks + + According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make + libglade work on Cygwin; apparently all Glade callbacks need this magic + extra declaration. I do not pretend to understand. + */ + +G_MODULE_EXPORT void +exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + flush_dialog_changes_and_save (s); + kill_preview_subproc (s, False); + gtk_main_quit (); +} + +static gboolean +wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + state *s = (state *) data; + flush_dialog_changes_and_save (s); + gtk_main_quit (); + return TRUE; +} + + +G_MODULE_EXPORT void +about_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + char msg [2048]; + char *vers = strdup (screensaver_id + 4); + char *s; + char copy[1024]; + char *desc = _("For updates, check http://www.jwz.org/xscreensaver/"); + + s = strchr (vers, ','); + *s = 0; + s += 2; + + /* Ole Laursen says "don't use _() here because + non-ASCII characters aren't allowed in localizable string keys." + (I don't want to just use (c) instead of © because that doesn't + look as good in the plain-old default Latin1 "C" locale.) + */ +#ifdef HAVE_GTK2 + sprintf(copy, ("Copyright \xC2\xA9 1991-2008 %s"), s); +#else /* !HAVE_GTK2 */ + sprintf(copy, ("Copyright \251 1991-2008 %s"), s); +#endif /* !HAVE_GTK2 */ + + sprintf (msg, "%s\n\n%s", copy, desc); + + /* I can't make gnome_about_new() work here -- it starts dying in + gdk_imlib_get_visual() under gnome_about_new(). If this worked, + then this might be the thing to do: + + #ifdef HAVE_CRAPPLET + { + const gchar *auth[] = { 0 }; + GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc, + "xscreensaver.xpm"); + gtk_widget_show (about); + } + #else / * GTK but not GNOME * / + ... + */ + { + GdkColormap *colormap; + GdkPixmap *gdkpixmap; + GdkBitmap *mask; + + GtkWidget *dialog = gtk_dialog_new (); + GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok; + GtkWidget *parent = GTK_WIDGET (menuitem); + while (parent->parent) + parent = parent->parent; + + hbox = gtk_hbox_new (FALSE, 20); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + hbox, TRUE, TRUE, 0); + + colormap = gtk_widget_get_colormap (parent); + gdkpixmap = + gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL, + (gchar **) logo_180_xpm); + icon = gtk_pixmap_new (gdkpixmap, mask); + gtk_misc_set_padding (GTK_MISC (icon), 10, 10); + + gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + + label1 = gtk_label_new (vers); + gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0); + gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75); + +#ifndef HAVE_GTK2 + GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style); + GTK_WIDGET (label1)->style->font = + gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font")); + gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style); +#endif /* HAVE_GTK2 */ + + label2 = gtk_label_new (msg); + gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0); + gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25); + +#ifndef HAVE_GTK2 + GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style); + GTK_WIDGET (label2)->style->font = + gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font")); + gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style); +#endif /* HAVE_GTK2 */ + + hb = gtk_hbutton_box_new (); + + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), + hb, TRUE, TRUE, 0); + +#ifdef HAVE_GTK2 + ok = gtk_button_new_from_stock (GTK_STOCK_OK); +#else /* !HAVE_GTK2 */ + ok = gtk_button_new_with_label (_("OK")); +#endif /* !HAVE_GTK2 */ + gtk_container_add (GTK_CONTAINER (hb), ok); + + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); + gtk_window_set_title (GTK_WINDOW (dialog), progclass); + + gtk_widget_show (hbox); + gtk_widget_show (icon); + gtk_widget_show (vbox); + gtk_widget_show (label1); + gtk_widget_show (label2); + gtk_widget_show (hb); + gtk_widget_show (ok); + gtk_widget_show (dialog); + + gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), + (gpointer) dialog); + gdk_window_set_transient_for (GTK_WIDGET (dialog)->window, + GTK_WIDGET (parent)->window); + gdk_window_show (GTK_WIDGET (dialog)->window); + gdk_window_raise (GTK_WIDGET (dialog)->window); + } +} + + +G_MODULE_EXPORT void +doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + char *help_command; + + if (!p->help_url || !*p->help_url) + { + warning_dialog (s->toplevel_widget, + _("Error:\n\n" + "No Help URL has been specified.\n"), D_NONE, 100); + return; + } + + help_command = (char *) malloc (strlen (p->load_url_command) + + (strlen (p->help_url) * 4) + 20); + strcpy (help_command, "( "); + sprintf (help_command + strlen(help_command), + p->load_url_command, + p->help_url, p->help_url, p->help_url, p->help_url); + strcat (help_command, " ) &"); + if (system (help_command) < 0) + fprintf (stderr, "%s: fork error\n", blurb()); + free (help_command); +} + + +G_MODULE_EXPORT void +file_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + sensitize_menu_items (s, False); +} + + +G_MODULE_EXPORT void +activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + run_cmd (s, XA_ACTIVATE, 0); +} + + +G_MODULE_EXPORT void +lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + run_cmd (s, XA_LOCK, 0); +} + + +G_MODULE_EXPORT void +kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + run_cmd (s, XA_EXIT, 0); +} + + +G_MODULE_EXPORT void +restart_menu_cb (GtkWidget *widget, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + flush_dialog_changes_and_save (s); + xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL); + sleep (1); + if (system ("xscreensaver -nosplash &") < 0) + fprintf (stderr, "%s: fork error\n", blurb()); + + await_xscreensaver (s); +} + +static Bool +xscreensaver_running_p (state *s) +{ + Display *dpy = GDK_DISPLAY(); + char *rversion = 0; + server_xscreensaver_version (dpy, &rversion, 0, 0); + if (!rversion) + return False; + free (rversion); + return True; +} + +static void +await_xscreensaver (state *s) +{ + int countdown = 5; + Bool ok = False; + + while (!ok && (--countdown > 0)) + if (xscreensaver_running_p (s)) + ok = True; + else + sleep (1); /* If it's not there yet, wait a second... */ + + sensitize_menu_items (s, True); + + if (! ok) + { + /* Timed out, no screensaver running. */ + + char buf [1024]; + Bool root_p = (geteuid () == 0); + + strcpy (buf, + _("Error:\n\n" + "The xscreensaver daemon did not start up properly.\n" + "\n")); + + if (root_p) + +# ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than + the length ISO C89 compilers are required to + support" in the following expression... */ +# endif + strcat (buf, STFU + _("You are running as root. This usually means that xscreensaver\n" + "was unable to contact your X server because access control is\n" + "turned on. Try running this command:\n" + "\n" + " xhost +localhost\n" + "\n" + "and then selecting `File / Restart Daemon'.\n" + "\n" + "Note that turning off access control will allow anyone logged\n" + "on to this machine to access your screen, which might be\n" + "considered a security problem. Please read the xscreensaver\n" + "manual and FAQ for more information.\n" + "\n" + "You shouldn't run X as root. Instead, you should log in as a\n" + "normal user, and `su' as necessary.")); + else + strcat (buf, _("Please check your $PATH and permissions.")); + + warning_dialog (s->toplevel_widget, buf, D_NONE, 1); + } + + force_dialog_repaint (s); +} + + +static int +selected_list_element (state *s) +{ + return s->_selected_list_element; +} + + +static int +demo_write_init_file (state *s, saver_preferences *p) +{ + Display *dpy = GDK_DISPLAY(); + +#if 0 + /* #### try to figure out why shit keeps getting reordered... */ + if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow")) + abort(); +#endif + + if (!write_init_file (dpy, p, s->short_version, False)) + { + if (s->debug_p) + fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name()); + return 0; + } + else + { + const char *f = init_file_name(); + if (!f || !*f) + warning_dialog (s->toplevel_widget, + _("Error:\n\nCouldn't determine init file name!\n"), + D_NONE, 100); + else + { + char *b = (char *) malloc (strlen(f) + 1024); + sprintf (b, _("Error:\n\nCouldn't write %s\n"), f); + warning_dialog (s->toplevel_widget, b, D_NONE, 100); + free (b); + } + return -1; + } +} + + +G_MODULE_EXPORT void +run_this_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + int list_elt = selected_list_element (s); + if (list_elt < 0) return; + if (!flush_dialog_changes_and_save (s)) + run_hack (s, list_elt, True); +} + + +G_MODULE_EXPORT void +manual_cb (GtkButton *button, gpointer user_data) +{ + Display *dpy = GDK_DISPLAY(); + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + GtkWidget *list_widget = name_to_widget (s, "list"); + int list_elt = selected_list_element (s); + int hack_number; + char *name, *name2, *cmd, *str; + char *oname = 0; + if (list_elt < 0) return; + hack_number = s->list_elt_to_hack_number[list_elt]; + + flush_dialog_changes_and_save (s); + ensure_selected_item_visible (list_widget); + + name = strdup (p->screenhacks[hack_number]->command); + name2 = name; + oname = name; + while (isspace (*name2)) name2++; + str = name2; + while (*str && !isspace (*str)) str++; + *str = 0; + str = strrchr (name2, '/'); + if (str) name2 = str+1; + + cmd = get_string_resource (dpy, "manualCommand", "ManualCommand"); + if (cmd) + { + char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100); + strcpy (cmd2, "( "); + sprintf (cmd2 + strlen (cmd2), + cmd, + name2, name2, name2, name2); + strcat (cmd2, " ) &"); + if (system (cmd2) < 0) + fprintf (stderr, "%s: fork error\n", blurb()); + free (cmd2); + } + else + { + warning_dialog (GTK_WIDGET (button), + _("Error:\n\nno `manualCommand' resource set."), + D_NONE, 100); + } + + free (oname); +} + + +static void +force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p) +{ + GtkWidget *parent = name_to_widget (s, "scroller"); + Bool was = GTK_WIDGET_IS_SENSITIVE (parent); +#ifdef HAVE_GTK2 + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeSelection *selection; +#endif /* HAVE_GTK2 */ + + if (!was) gtk_widget_set_sensitive (parent, True); +#ifdef HAVE_GTK2 + model = gtk_tree_view_get_model (GTK_TREE_VIEW (list)); + g_assert (model); + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt)) + { + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list)); + gtk_tree_selection_select_iter (selection, &iter); + } +#else /* !HAVE_GTK2 */ + gtk_list_select_item (GTK_LIST (list), list_elt); +#endif /* !HAVE_GTK2 */ + if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list)); + if (!was) gtk_widget_set_sensitive (parent, False); +} + + +G_MODULE_EXPORT void +run_next_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + /* saver_preferences *p = &s->prefs; */ + Bool ops = s->preview_suppressed_p; + + GtkWidget *list_widget = name_to_widget (s, "list"); + int list_elt = selected_list_element (s); + + if (list_elt < 0) + list_elt = 0; + else + list_elt++; + + if (list_elt >= s->list_count) + list_elt = 0; + + s->preview_suppressed_p = True; + + flush_dialog_changes_and_save (s); + force_list_select_item (s, list_widget, list_elt, True); + populate_demo_window (s, list_elt); + run_hack (s, list_elt, False); + + s->preview_suppressed_p = ops; +} + + +G_MODULE_EXPORT void +run_prev_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + /* saver_preferences *p = &s->prefs; */ + Bool ops = s->preview_suppressed_p; + + GtkWidget *list_widget = name_to_widget (s, "list"); + int list_elt = selected_list_element (s); + + if (list_elt < 0) + list_elt = s->list_count - 1; + else + list_elt--; + + if (list_elt < 0) + list_elt = s->list_count - 1; + + s->preview_suppressed_p = True; + + flush_dialog_changes_and_save (s); + force_list_select_item (s, list_widget, list_elt, True); + populate_demo_window (s, list_elt); + run_hack (s, list_elt, False); + + s->preview_suppressed_p = ops; +} + + +/* Writes the given settings into prefs. + Returns true if there was a change, False otherwise. + command and/or visual may be 0, or enabled_p may be -1, meaning "no change". + */ +static Bool +flush_changes (state *s, + int list_elt, + int enabled_p, + const char *command, + const char *visual) +{ + saver_preferences *p = &s->prefs; + Bool changed = False; + screenhack *hack; + int hack_number; + if (list_elt < 0 || list_elt >= s->list_count) + abort(); + + hack_number = s->list_elt_to_hack_number[list_elt]; + hack = p->screenhacks[hack_number]; + + if (enabled_p != -1 && + enabled_p != hack->enabled_p) + { + hack->enabled_p = enabled_p; + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: \"%s\": enabled => %d\n", + blurb(), hack->name, enabled_p); + } + + if (command) + { + if (!hack->command || !!strcmp (command, hack->command)) + { + if (hack->command) free (hack->command); + hack->command = strdup (command); + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: \"%s\": command => \"%s\"\n", + blurb(), hack->name, command); + } + } + + if (visual) + { + const char *ov = hack->visual; + if (!ov || !*ov) ov = "any"; + if (!*visual) visual = "any"; + if (!!strcasecmp (visual, ov)) + { + if (hack->visual) free (hack->visual); + hack->visual = strdup (visual); + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n", + blurb(), hack->name, visual); + } + } + + return changed; +} + + +/* Helper for the text fields that contain time specifications: + this parses the text, and does error checking. + */ +static void +hack_time_text (state *s, const char *line, Time *store, Bool sec_p) +{ + if (*line) + { + int value; + if (!sec_p || strchr (line, ':')) + value = parse_time ((char *) line, sec_p, True); + else + { + char c; + if (sscanf (line, "%d%c", &value, &c) != 1) + value = -1; + if (!sec_p) + value *= 60; + } + + value *= 1000; /* Time measures in microseconds */ + if (value < 0) + { + char b[255]; + sprintf (b, + _("Error:\n\n" + "Unparsable time format: \"%s\"\n"), + line); + warning_dialog (s->toplevel_widget, b, D_NONE, 100); + } + else + *store = value; + } +} + + +static Bool +directory_p (const char *path) +{ + struct stat st; + if (!path || !*path) + return False; + else if (stat (path, &st)) + return False; + else if (!S_ISDIR (st.st_mode)) + return False; + else + return True; +} + +static Bool +file_p (const char *path) +{ + struct stat st; + if (!path || !*path) + return False; + else if (stat (path, &st)) + return False; + else if (S_ISDIR (st.st_mode)) + return False; + else + return True; +} + +static char * +normalize_directory (const char *path) +{ + int L; + char *p2, *s; + if (!path || !*path) return 0; + L = strlen (path); + p2 = (char *) malloc (L + 2); + strcpy (p2, path); + if (p2[L-1] == '/') /* remove trailing slash */ + p2[--L] = 0; + + for (s = p2; s && *s; s++) + { + if (*s == '/' && + (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */ + !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */ + { + char *s0 = s; + while (s0 > p2 && s0[-1] != '/') + s0--; + if (s0 > p2) + { + s0--; + s += 3; + strcpy (s0, s); + s = s0-1; + } + } + else if (*s == '/' && !strncmp (s, "/./", 3)) /* delete "/./" */ + strcpy (s, s+2), s--; + else if (*s == '/' && !strncmp (s, "/.\000", 3)) /* delete "/.$" */ + *s = 0, s--; + } + + for (s = p2; s && *s; s++) /* normalize consecutive slashes */ + while (s[0] == '/' && s[1] == '/') + strcpy (s, s+1); + + /* and strip trailing whitespace for good measure. */ + L = strlen(p2); + while (isspace(p2[L-1])) + p2[--L] = 0; + + return p2; +} + + +#ifdef HAVE_GTK2 + +typedef struct { + state *s; + int i; + Bool *changed; +} FlushForeachClosure; + +static gboolean +flush_checkbox (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + FlushForeachClosure *closure = data; + gboolean checked; + + gtk_tree_model_get (model, iter, + COL_ENABLED, &checked, + -1); + + if (flush_changes (closure->s, closure->i, + checked, 0, 0)) + *closure->changed = True; + + closure->i++; + + /* don't remove row */ + return FALSE; +} + +#endif /* HAVE_GTK2 */ + +/* Flush out any changes made in the main dialog window (where changes + take place immediately: clicking on a checkbox causes the init file + to be written right away.) + */ +static Bool +flush_dialog_changes_and_save (state *s) +{ + saver_preferences *p = &s->prefs; + saver_preferences P2, *p2 = &P2; +#ifdef HAVE_GTK2 + GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkTreeModel *model = gtk_tree_view_get_model (list_widget); + FlushForeachClosure closure; +#else /* !HAVE_GTK2 */ + GtkList *list_widget = GTK_LIST (name_to_widget (s, "list")); + GList *kids = gtk_container_children (GTK_CONTAINER (list_widget)); + int i; +#endif /* !HAVE_GTK2 */ + + Bool changed = False; + GtkWidget *w; + + if (s->saving_p) return False; + s->saving_p = True; + + *p2 = *p; + + /* Flush any checkbox changes in the list down into the prefs struct. + */ +#ifdef HAVE_GTK2 + closure.s = s; + closure.changed = &changed; + closure.i = 0; + gtk_tree_model_foreach (model, flush_checkbox, &closure); + +#else /* !HAVE_GTK2 */ + + for (i = 0; kids; kids = kids->next, i++) + { + GtkWidget *line = GTK_WIDGET (kids->data); + GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child); + GtkWidget *line_check = + GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data); + Bool checked = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check)); + + if (flush_changes (s, i, (checked ? 1 : 0), 0, 0)) + changed = True; + } +#endif /* ~HAVE_GTK2 */ + + /* Flush the non-hack-specific settings down into the prefs struct. + */ + +# define SECONDS(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True) + +# define MINUTES(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False) + +# define CHECKBOX(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)) + +# define PATHNAME(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w))) + +# define TEXT(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + (FIELD) = (char *) gtk_entry_get_text (GTK_ENTRY (w)) + + MINUTES (&p2->timeout, "timeout_spinbutton"); + MINUTES (&p2->cycle, "cycle_spinbutton"); + CHECKBOX (p2->lock_p, "lock_button"); + MINUTES (&p2->lock_timeout, "lock_spinbutton"); + + CHECKBOX (p2->dpms_enabled_p, "dpms_button"); + MINUTES (&p2->dpms_standby, "dpms_standby_spinbutton"); + MINUTES (&p2->dpms_suspend, "dpms_suspend_spinbutton"); + MINUTES (&p2->dpms_off, "dpms_off_spinbutton"); + + CHECKBOX (p2->grab_desktop_p, "grab_desk_button"); + CHECKBOX (p2->grab_video_p, "grab_video_button"); + CHECKBOX (p2->random_image_p, "grab_image_button"); + PATHNAME (p2->image_directory, "image_text"); + +#if 0 + CHECKBOX (p2->verbose_p, "verbose_button"); + CHECKBOX (p2->capture_stderr_p, "capture_button"); + CHECKBOX (p2->splash_p, "splash_button"); +#endif + + { + Bool v = False; + CHECKBOX (v, "text_host_radio"); if (v) p2->tmode = TEXT_DATE; + CHECKBOX (v, "text_radio"); if (v) p2->tmode = TEXT_LITERAL; + CHECKBOX (v, "text_file_radio"); if (v) p2->tmode = TEXT_FILE; + CHECKBOX (v, "text_program_radio"); if (v) p2->tmode = TEXT_PROGRAM; + CHECKBOX (v, "text_url_radio"); if (v) p2->tmode = TEXT_URL; + TEXT (p2->text_literal, "text_entry"); + PATHNAME (p2->text_file, "text_file_entry"); + PATHNAME (p2->text_program, "text_program_entry"); + PATHNAME (p2->text_program, "text_program_entry"); + TEXT (p2->text_url, "text_url_entry"); + } + + CHECKBOX (p2->install_cmap_p, "install_button"); + CHECKBOX (p2->fade_p, "fade_button"); + CHECKBOX (p2->unfade_p, "unfade_button"); + SECONDS (&p2->fade_seconds, "fade_spinbutton"); + +# undef SECONDS +# undef MINUTES +# undef CHECKBOX +# undef PATHNAME +# undef TEXT + + /* Warn if the image directory doesn't exist. + */ + if (p2->image_directory && + *p2->image_directory && + !directory_p (p2->image_directory)) + { + char b[255]; + sprintf (b, "Error:\n\n" "Directory does not exist: \"%s\"\n", + p2->image_directory); + warning_dialog (s->toplevel_widget, b, D_NONE, 100); + } + + + /* Map the mode menu to `saver_mode' enum values. */ + { + GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); + GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt)); + GtkWidget *selected = gtk_menu_get_active (menu); + GList *kids = gtk_container_children (GTK_CONTAINER (menu)); + int menu_elt = g_list_index (kids, (gpointer) selected); + if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort(); + p2->mode = mode_menu_order[menu_elt]; + } + + if (p2->mode == ONE_HACK) + { + int list_elt = selected_list_element (s); + p2->selected_hack = (list_elt >= 0 + ? s->list_elt_to_hack_number[list_elt] + : -1); + } + +# define COPY(field, name) \ + if (p->field != p2->field) { \ + changed = True; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \ + } \ + p->field = p2->field + + COPY(mode, "mode"); + COPY(selected_hack, "selected_hack"); + + COPY(timeout, "timeout"); + COPY(cycle, "cycle"); + COPY(lock_p, "lock_p"); + COPY(lock_timeout, "lock_timeout"); + + COPY(dpms_enabled_p, "dpms_enabled_p"); + COPY(dpms_standby, "dpms_standby"); + COPY(dpms_suspend, "dpms_suspend"); + COPY(dpms_off, "dpms_off"); + +#if 0 + COPY(verbose_p, "verbose_p"); + COPY(capture_stderr_p, "capture_stderr_p"); + COPY(splash_p, "splash_p"); +#endif + + COPY(tmode, "tmode"); + + COPY(install_cmap_p, "install_cmap_p"); + COPY(fade_p, "fade_p"); + COPY(unfade_p, "unfade_p"); + COPY(fade_seconds, "fade_seconds"); + + COPY(grab_desktop_p, "grab_desktop_p"); + COPY(grab_video_p, "grab_video_p"); + COPY(random_image_p, "random_image_p"); + +# undef COPY + +# define COPYSTR(FIELD,NAME) \ + if (!p->FIELD || \ + !p2->FIELD || \ + strcmp(p->FIELD, p2->FIELD)) \ + { \ + changed = True; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \ + } \ + if (p->FIELD && p->FIELD != p2->FIELD) \ + free (p->FIELD); \ + p->FIELD = p2->FIELD; \ + p2->FIELD = 0 + + COPYSTR(image_directory, "image_directory"); + COPYSTR(text_literal, "text_literal"); + COPYSTR(text_file, "text_file"); + COPYSTR(text_program, "text_program"); + COPYSTR(text_url, "text_url"); +# undef COPYSTR + + populate_prefs_page (s); + + if (changed) + { + Display *dpy = GDK_DISPLAY(); + Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK); + sync_server_dpms_settings (dpy, enabled_p, + p->dpms_standby / 1000, + p->dpms_suspend / 1000, + p->dpms_off / 1000, + False); + + changed = demo_write_init_file (s, p); + } + + s->saving_p = False; + return changed; +} + + +/* Flush out any changes made in the popup dialog box (where changes + take place only when the OK button is clicked.) + */ +static Bool +flush_popup_changes_and_save (state *s) +{ + Bool changed = False; + saver_preferences *p = &s->prefs; + int list_elt = selected_list_element (s); + + GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text")); + GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo")); + + const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry)); + const char *command = gtk_entry_get_text (cmd); + + char c; + unsigned long id; + + if (s->saving_p) return False; + s->saving_p = True; + + if (list_elt < 0) + goto DONE; + + if (maybe_reload_init_file (s) != 0) + { + changed = True; + goto DONE; + } + + /* Sanity-check and canonicalize whatever the user typed into the combo box. + */ + if (!strcasecmp (visual, "")) visual = ""; + else if (!strcasecmp (visual, "any")) visual = ""; + else if (!strcasecmp (visual, "default")) visual = "Default"; + else if (!strcasecmp (visual, "default-n")) visual = "Default-N"; + else if (!strcasecmp (visual, "default-i")) visual = "Default-I"; + else if (!strcasecmp (visual, "best")) visual = "Best"; + else if (!strcasecmp (visual, "mono")) visual = "Mono"; + else if (!strcasecmp (visual, "monochrome")) visual = "Mono"; + else if (!strcasecmp (visual, "gray")) visual = "Gray"; + else if (!strcasecmp (visual, "grey")) visual = "Gray"; + else if (!strcasecmp (visual, "color")) visual = "Color"; + else if (!strcasecmp (visual, "gl")) visual = "GL"; + else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray"; + else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor"; + else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor"; + else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor"; + else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor"; + else if (1 == sscanf (visual, " %lu %c", &id, &c)) ; + else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; + else + { + gdk_beep (); /* unparsable */ + visual = ""; + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any")); + } + + changed = flush_changes (s, list_elt, -1, command, visual); + if (changed) + { + changed = demo_write_init_file (s, p); + + /* Do this to re-launch the hack if (and only if) the command line + has changed. */ + populate_demo_window (s, selected_list_element (s)); + } + + DONE: + s->saving_p = False; + return changed; +} + + +G_MODULE_EXPORT void +pref_changed_cb (GtkWidget *widget, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + if (! s->initializing_p) + { + s->initializing_p = True; + flush_dialog_changes_and_save (s); + s->initializing_p = False; + } +} + +G_MODULE_EXPORT gboolean +pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + pref_changed_cb (widget, user_data); + return FALSE; +} + +/* Callback on menu items in the "mode" options menu. + */ +G_MODULE_EXPORT void +mode_menu_item_cb (GtkWidget *widget, gpointer user_data) +{ + state *s = (state *) user_data; + saver_preferences *p = &s->prefs; + GtkWidget *list = name_to_widget (s, "list"); + int list_elt; + + GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent)); + int menu_index = 0; + saver_mode new_mode; + + while (menu_items) + { + if (menu_items->data == widget) + break; + menu_index++; + menu_items = menu_items->next; + } + if (!menu_items) abort(); + + new_mode = mode_menu_order[menu_index]; + + /* Keep the same list element displayed as before; except if we're + switching *to* "one screensaver" mode from any other mode, set + "the one" to be that which is currently selected. + */ + list_elt = selected_list_element (s); + if (new_mode == ONE_HACK) + p->selected_hack = s->list_elt_to_hack_number[list_elt]; + + { + saver_mode old_mode = p->mode; + p->mode = new_mode; + populate_demo_window (s, list_elt); + force_list_select_item (s, list, list_elt, True); + p->mode = old_mode; /* put it back, so the init file gets written */ + } + + pref_changed_cb (widget, user_data); +} + + +G_MODULE_EXPORT void +switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, + gint page_num, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + pref_changed_cb (GTK_WIDGET (notebook), user_data); + + /* If we're switching to page 0, schedule the current hack to be run. + Otherwise, schedule it to stop. */ + if (page_num == 0) + populate_demo_window (s, selected_list_element (s)); + else + schedule_preview (s, 0); +} + +#ifdef HAVE_GTK2 +static void +list_activated_cb (GtkTreeView *list, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer data) +{ + state *s = data; + char *str; + int list_elt; + + g_return_if_fail (!gdk_pointer_is_grabbed ()); + + str = gtk_tree_path_to_string (path); + list_elt = strtol (str, NULL, 10); + g_free (str); + + if (list_elt >= 0) + run_hack (s, list_elt, True); +} + +static void +list_select_changed_cb (GtkTreeSelection *selection, gpointer data) +{ + state *s = (state *)data; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + char *str; + int list_elt; + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return; + + path = gtk_tree_model_get_path (model, &iter); + str = gtk_tree_path_to_string (path); + list_elt = strtol (str, NULL, 10); + + gtk_tree_path_free (path); + g_free (str); + + populate_demo_window (s, list_elt); + flush_dialog_changes_and_save (s); + + /* Re-populate the Settings window any time a new item is selected + in the list, in case both windows are currently visible. + */ + populate_popup_window (s); +} + +#else /* !HAVE_GTK2 */ + +static time_t last_doubleclick_time = 0; /* FMH! This is to suppress the + list_select_cb that comes in + *after* we've double-clicked. + */ + +static gint +list_doubleclick_cb (GtkWidget *button, GdkEventButton *event, + gpointer data) +{ + state *s = (state *) data; + if (event->type == GDK_2BUTTON_PRESS) + { + GtkList *list = GTK_LIST (name_to_widget (s, "list")); + int list_elt = gtk_list_child_position (list, GTK_WIDGET (button)); + + last_doubleclick_time = time ((time_t *) 0); + + if (list_elt >= 0) + run_hack (s, list_elt, True); + } + + return FALSE; +} + + +static void +list_select_cb (GtkList *list, GtkWidget *child, gpointer data) +{ + state *s = (state *) data; + time_t now = time ((time_t *) 0); + + if (now >= last_doubleclick_time + 2) + { + int list_elt = gtk_list_child_position (list, GTK_WIDGET (child)); + populate_demo_window (s, list_elt); + flush_dialog_changes_and_save (s); + } +} + +static void +list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data) +{ + state *s = (state *) data; + populate_demo_window (s, -1); + flush_dialog_changes_and_save (s); +} + +#endif /* !HAVE_GTK2 */ + + +/* Called when the checkboxes that are in the left column of the + scrolling list are clicked. This both populates the right pane + (just as clicking on the label (really, listitem) does) and + also syncs this checkbox with the right pane Enabled checkbox. + */ +static void +list_checkbox_cb ( +#ifdef HAVE_GTK2 + GtkCellRendererToggle *toggle, + gchar *path_string, +#else /* !HAVE_GTK2 */ + GtkWidget *cb, +#endif /* !HAVE_GTK2 */ + gpointer data) +{ + state *s = (state *) data; + +#ifdef HAVE_GTK2 + GtkScrolledWindow *scroller = + GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller")); + GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkTreeModel *model = gtk_tree_view_get_model (list); + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + gboolean active; +#else /* !HAVE_GTK2 */ + GtkWidget *line_hbox = GTK_WIDGET (cb)->parent; + GtkWidget *line = GTK_WIDGET (line_hbox)->parent; + + GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent); + GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent); + GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); +#endif /* !HAVE_GTK2 */ + GtkAdjustment *adj; + double scroll_top; + + int list_elt; + +#ifdef HAVE_GTK2 + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + g_warning ("bad path: %s", path_string); + return; + } + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + COL_ENABLED, &active, + -1); + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + COL_ENABLED, !active, + -1); + + list_elt = strtol (path_string, NULL, 10); +#else /* !HAVE_GTK2 */ + list_elt = gtk_list_child_position (list, line); +#endif /* !HAVE_GTK2 */ + + /* remember previous scroll position of the top of the list */ + adj = gtk_scrolled_window_get_vadjustment (scroller); + scroll_top = adj->value; + + flush_dialog_changes_and_save (s); + force_list_select_item (s, GTK_WIDGET (list), list_elt, False); + populate_demo_window (s, list_elt); + + /* restore the previous scroll position of the top of the list. + this is weak, but I don't really know why it's moving... */ + gtk_adjustment_set_value (adj, scroll_top); +} + + +typedef struct { + state *state; + GtkFileSelection *widget; +} file_selection_data; + + + +static void +store_image_directory (GtkWidget *button, gpointer user_data) +{ + file_selection_data *fsd = (file_selection_data *) user_data; + state *s = fsd->state; + GtkFileSelection *selector = fsd->widget; + GtkWidget *top = s->toplevel_widget; + saver_preferences *p = &s->prefs; + const char *path = gtk_file_selection_get_filename (selector); + + if (p->image_directory && !strcmp(p->image_directory, path)) + return; /* no change */ + + if (!directory_p (path)) + { + char b[255]; + sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path); + warning_dialog (GTK_WIDGET (top), b, D_NONE, 100); + return; + } + + if (p->image_directory) free (p->image_directory); + p->image_directory = normalize_directory (path); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")), + (p->image_directory ? p->image_directory : "")); + demo_write_init_file (s, p); +} + + +static void +store_text_file (GtkWidget *button, gpointer user_data) +{ + file_selection_data *fsd = (file_selection_data *) user_data; + state *s = fsd->state; + GtkFileSelection *selector = fsd->widget; + GtkWidget *top = s->toplevel_widget; + saver_preferences *p = &s->prefs; + const char *path = gtk_file_selection_get_filename (selector); + + if (p->text_file && !strcmp(p->text_file, path)) + return; /* no change */ + + if (!file_p (path)) + { + char b[255]; + sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path); + warning_dialog (GTK_WIDGET (top), b, D_NONE, 100); + return; + } + + if (p->text_file) free (p->text_file); + p->text_file = normalize_directory (path); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")), + (p->text_file ? p->text_file : "")); + demo_write_init_file (s, p); +} + + +static void +store_text_program (GtkWidget *button, gpointer user_data) +{ + file_selection_data *fsd = (file_selection_data *) user_data; + state *s = fsd->state; + GtkFileSelection *selector = fsd->widget; + /*GtkWidget *top = s->toplevel_widget;*/ + saver_preferences *p = &s->prefs; + const char *path = gtk_file_selection_get_filename (selector); + + if (p->text_program && !strcmp(p->text_program, path)) + return; /* no change */ + +# if 0 + if (!file_p (path)) + { + char b[255]; + sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path); + warning_dialog (GTK_WIDGET (top), b, D_NONE, 100); + return; + } +# endif + + if (p->text_program) free (p->text_program); + p->text_program = normalize_directory (path); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")), + (p->text_program ? p->text_program : "")); + demo_write_init_file (s, p); +} + + + +static void +browse_image_dir_cancel (GtkWidget *button, gpointer user_data) +{ + file_selection_data *fsd = (file_selection_data *) user_data; + gtk_widget_hide (GTK_WIDGET (fsd->widget)); +} + +static void +browse_image_dir_ok (GtkWidget *button, gpointer user_data) +{ + browse_image_dir_cancel (button, user_data); + store_image_directory (button, user_data); +} + +static void +browse_text_file_ok (GtkWidget *button, gpointer user_data) +{ + browse_image_dir_cancel (button, user_data); + store_text_file (button, user_data); +} + +static void +browse_text_program_ok (GtkWidget *button, gpointer user_data) +{ + browse_image_dir_cancel (button, user_data); + store_text_program (button, user_data); +} + +static void +browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + browse_image_dir_cancel (widget, user_data); +} + + +G_MODULE_EXPORT void +browse_image_dir_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + static file_selection_data *fsd = 0; + + GtkFileSelection *selector = GTK_FILE_SELECTION( + gtk_file_selection_new ("Please select the image directory.")); + + if (!fsd) + fsd = (file_selection_data *) malloc (sizeof (*fsd)); + + fsd->widget = selector; + fsd->state = s; + + if (p->image_directory && *p->image_directory) + gtk_file_selection_set_filename (selector, p->image_directory); + + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector->cancel_button), + "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector), "delete_event", + GTK_SIGNAL_FUNC (browse_image_dir_close), + (gpointer *) fsd); + + gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False); + + gtk_window_set_modal (GTK_WINDOW (selector), True); + gtk_widget_show (GTK_WIDGET (selector)); +} + + +G_MODULE_EXPORT void +browse_text_file_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + static file_selection_data *fsd = 0; + + GtkFileSelection *selector = GTK_FILE_SELECTION( + gtk_file_selection_new ("Please select a text file.")); + + if (!fsd) + fsd = (file_selection_data *) malloc (sizeof (*fsd)); + + fsd->widget = selector; + fsd->state = s; + + if (p->text_file && *p->text_file) + gtk_file_selection_set_filename (selector, p->text_file); + + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector->cancel_button), + "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector), "delete_event", + GTK_SIGNAL_FUNC (browse_image_dir_close), + (gpointer *) fsd); + + gtk_window_set_modal (GTK_WINDOW (selector), True); + gtk_widget_show (GTK_WIDGET (selector)); +} + + +G_MODULE_EXPORT void +browse_text_program_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + static file_selection_data *fsd = 0; + + GtkFileSelection *selector = GTK_FILE_SELECTION( + gtk_file_selection_new ("Please select a text-generating program.")); + + if (!fsd) + fsd = (file_selection_data *) malloc (sizeof (*fsd)); + + fsd->widget = selector; + fsd->state = s; + + if (p->text_program && *p->text_program) + gtk_file_selection_set_filename (selector, p->text_program); + + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector->cancel_button), + "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector), "delete_event", + GTK_SIGNAL_FUNC (browse_image_dir_close), + (gpointer *) fsd); + + gtk_window_set_modal (GTK_WINDOW (selector), True); + gtk_widget_show (GTK_WIDGET (selector)); +} + + + + + +G_MODULE_EXPORT void +settings_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + int list_elt = selected_list_element (s); + + populate_demo_window (s, list_elt); /* reset the widget */ + populate_popup_window (s); /* create UI on popup window */ + gtk_widget_show (s->popup_widget); +} + +static void +settings_sync_cmd_text (state *s) +{ +# ifdef HAVE_XML + GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); + char *cmd_line = get_configurator_command_line (s->cdata, False); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line)); + free (cmd_line); +# endif /* HAVE_XML */ +} + +G_MODULE_EXPORT void +settings_adv_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + + settings_sync_cmd_text (s); + gtk_notebook_set_page (notebook, 1); +} + +G_MODULE_EXPORT void +settings_std_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + + /* Re-create UI to reflect the in-progress command-line settings. */ + populate_popup_window (s); + + gtk_notebook_set_page (notebook, 0); +} + +G_MODULE_EXPORT void +settings_reset_cb (GtkButton *button, gpointer user_data) +{ +# ifdef HAVE_XML + state *s = global_state_kludge; /* I hate C so much... */ + GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); + char *cmd_line = get_configurator_command_line (s->cdata, True); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line)); + free (cmd_line); + populate_popup_window (s); +# endif /* HAVE_XML */ +} + +G_MODULE_EXPORT void +settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, + gint page_num, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkWidget *adv = name_to_widget (s, "adv_button"); + GtkWidget *std = name_to_widget (s, "std_button"); + + if (page_num == 0) + { + gtk_widget_show (adv); + gtk_widget_hide (std); + } + else if (page_num == 1) + { + gtk_widget_hide (adv); + gtk_widget_show (std); + } + else + abort(); +} + + + +G_MODULE_EXPORT void +settings_cancel_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + gtk_widget_hide (s->popup_widget); +} + +G_MODULE_EXPORT void +settings_ok_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + int page = gtk_notebook_get_current_page (notebook); + + if (page == 0) + /* Regenerate the command-line from the widget contents before saving. + But don't do this if we're looking at the command-line page already, + or we will blow away what they typed... */ + settings_sync_cmd_text (s); + + flush_popup_changes_and_save (s); + gtk_widget_hide (s->popup_widget); +} + +static gboolean +wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + state *s = (state *) data; + settings_cancel_cb (0, (gpointer) s); + return TRUE; +} + + + +/* Populating the various widgets + */ + + +/* Returns the number of the last hack run by the server. + */ +static int +server_current_hack (void) +{ + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + Display *dpy = GDK_DISPLAY(); + int hack_number = -1; + + if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + XA_SCREENSAVER_STATUS, + 0, 3, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) + { + PROP32 *data = (PROP32 *) dataP; + hack_number = (int) data[2] - 1; + } + + if (dataP) XFree (dataP); + + return hack_number; +} + + +/* Finds the number of the last hack that was run, and makes that item be + selected by default. + */ +static void +scroll_to_current_hack (state *s) +{ + saver_preferences *p = &s->prefs; + int hack_number = -1; + + if (p->mode == ONE_HACK) /* in "one" mode, use the one */ + hack_number = p->selected_hack; + if (hack_number < 0) /* otherwise, use the last-run */ + hack_number = server_current_hack (); + if (hack_number < 0) /* failing that, last "one mode" */ + hack_number = p->selected_hack; + if (hack_number < 0) /* failing that, newest hack. */ + { + /* We should only get here if the user does not have a .xscreensaver + file, and the screen has not been blanked with a hack since X + started up: in other words, this is probably a fresh install. + + Instead of just defaulting to hack #0 (in either "programs" or + "alphabetical" order) let's try to default to the last runnable + hack in the "programs" list: this is probably the hack that was + most recently added to the xscreensaver distribution (and so + it's probably the currently-coolest one!) + */ + hack_number = p->screenhacks_count-1; + while (hack_number > 0 && + ! (s->hacks_available_p[hack_number] && + p->screenhacks[hack_number]->enabled_p)) + hack_number--; + } + + if (hack_number >= 0 && hack_number < p->screenhacks_count) + { + int list_elt = s->hack_number_to_list_elt[hack_number]; + GtkWidget *list = name_to_widget (s, "list"); + force_list_select_item (s, list, list_elt, True); + populate_demo_window (s, list_elt); + } +} + + +static void +populate_hack_list (state *s) +{ + Display *dpy = GDK_DISPLAY(); +#ifdef HAVE_GTK2 + saver_preferences *p = &s->prefs; + GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkListStore *model; + GtkTreeSelection *selection; + GtkCellRenderer *ren; + GtkTreeIter iter; + int i; + + g_object_get (G_OBJECT (list), + "model", &model, + NULL); + if (!model) + { + model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING); + g_object_set (G_OBJECT (list), "model", model, NULL); + g_object_unref (model); + + ren = gtk_cell_renderer_toggle_new (); + gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED, + _("Use"), ren, + "active", COL_ENABLED, + NULL); + + g_signal_connect (ren, "toggled", + G_CALLBACK (list_checkbox_cb), + s); + + ren = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (list, COL_NAME, + _("Screen Saver"), ren, + "markup", COL_NAME, + NULL); + + g_signal_connect_after (list, "row_activated", + G_CALLBACK (list_activated_cb), + s); + + selection = gtk_tree_view_get_selection (list); + g_signal_connect (selection, "changed", + G_CALLBACK (list_select_changed_cb), + s); + + } + + for (i = 0; i < s->list_count; i++) + { + int hack_number = s->list_elt_to_hack_number[i]; + screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]); + char *pretty_name; + Bool available_p = (hack && s->hacks_available_p [hack_number]); + + if (!hack) continue; + + /* If we're to suppress uninstalled hacks, check $PATH now. */ + if (p->ignore_uninstalled_p && !available_p) + continue; + + pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (dpy, hack->command)); + + if (!available_p) + { + /* Make the text foreground be the color of insensitive widgets + (but don't actually make it be insensitive, since we still + want to be able to click on it.) + */ + GtkStyle *style = GTK_WIDGET (list)->style; + GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE]; + /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */ + char *buf = (char *) malloc (strlen (pretty_name) + 100); + + sprintf (buf, "%s", + fg->red >> 8, fg->green >> 8, fg->blue >> 8, + /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */ + pretty_name); + free (pretty_name); + pretty_name = buf; + } + + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + COL_ENABLED, hack->enabled_p, + COL_NAME, pretty_name, + -1); + free (pretty_name); + } + +#else /* !HAVE_GTK2 */ + + saver_preferences *p = &s->prefs; + GtkList *list = GTK_LIST (name_to_widget (s, "list")); + int i; + for (i = 0; i < s->list_count; i++) + { + int hack_number = s->list_elt_to_hack_number[i]; + screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]); + + /* A GtkList must contain only GtkListItems, but those can contain + an arbitrary widget. We add an Hbox, and inside that, a Checkbox + and a Label. We handle single and double click events on the + line itself, for clicking on the text, but the interior checkbox + also handles its own events. + */ + GtkWidget *line; + GtkWidget *line_hbox; + GtkWidget *line_check; + GtkWidget *line_label; + char *pretty_name; + Bool available_p = (hack && s->hacks_available_p [hack_number]); + + if (!hack) continue; + + /* If we're to suppress uninstalled hacks, check $PATH now. */ + if (p->ignore_uninstalled_p && !available_p) + continue; + + pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (hack->command)); + + line = gtk_list_item_new (); + line_hbox = gtk_hbox_new (FALSE, 0); + line_check = gtk_check_button_new (); + line_label = gtk_label_new (pretty_name); + + gtk_container_add (GTK_CONTAINER (line), line_hbox); + gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check), + hack->enabled_p); + gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT); + + gtk_widget_show (line_check); + gtk_widget_show (line_label); + gtk_widget_show (line_hbox); + gtk_widget_show (line); + + free (pretty_name); + + gtk_container_add (GTK_CONTAINER (list), line); + gtk_signal_connect (GTK_OBJECT (line), "button_press_event", + GTK_SIGNAL_FUNC (list_doubleclick_cb), + (gpointer) s); + + gtk_signal_connect (GTK_OBJECT (line_check), "toggled", + GTK_SIGNAL_FUNC (list_checkbox_cb), + (gpointer) s); + + gtk_widget_show (line); + + if (!available_p) + { + /* Make the widget be colored like insensitive widgets + (but don't actually make it be insensitive, since we + still want to be able to click on it.) + */ + GtkRcStyle *rc_style; + GdkColor fg, bg; + + gtk_widget_realize (GTK_WIDGET (line_label)); + + fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE]; + bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE]; + + rc_style = gtk_rc_style_new (); + rc_style->fg[GTK_STATE_NORMAL] = fg; + rc_style->bg[GTK_STATE_NORMAL] = bg; + rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG; + + gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style); + gtk_rc_style_unref (rc_style); + } + } + + gtk_signal_connect (GTK_OBJECT (list), "select_child", + GTK_SIGNAL_FUNC (list_select_cb), + (gpointer) s); + gtk_signal_connect (GTK_OBJECT (list), "unselect_child", + GTK_SIGNAL_FUNC (list_unselect_cb), + (gpointer) s); +#endif /* !HAVE_GTK2 */ +} + +static void +update_list_sensitivity (state *s) +{ + saver_preferences *p = &s->prefs; + Bool sensitive = (p->mode == RANDOM_HACKS || + p->mode == RANDOM_HACKS_SAME || + p->mode == ONE_HACK); + Bool checkable = (p->mode == RANDOM_HACKS || + p->mode == RANDOM_HACKS_SAME); + Bool blankable = (p->mode != DONT_BLANK); + +#ifndef HAVE_GTK2 + GtkWidget *head = name_to_widget (s, "col_head_hbox"); + GtkWidget *use = name_to_widget (s, "use_col_frame"); +#endif /* HAVE_GTK2 */ + GtkWidget *scroller = name_to_widget (s, "scroller"); + GtkWidget *buttons = name_to_widget (s, "next_prev_hbox"); + GtkWidget *blanker = name_to_widget (s, "blanking_table"); + +#ifdef HAVE_GTK2 + GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED); +#else /* !HAVE_GTK2 */ + GtkList *list = GTK_LIST (name_to_widget (s, "list")); + GList *kids = gtk_container_children (GTK_CONTAINER (list)); + + gtk_widget_set_sensitive (GTK_WIDGET (head), sensitive); +#endif /* !HAVE_GTK2 */ + gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET (buttons), sensitive); + + gtk_widget_set_sensitive (GTK_WIDGET (blanker), blankable); + +#ifdef HAVE_GTK2 + gtk_tree_view_column_set_visible (use, checkable); +#else /* !HAVE_GTK2 */ + if (checkable) + gtk_widget_show (use); /* the "Use" column header */ + else + gtk_widget_hide (use); + + while (kids) + { + GtkBin *line = GTK_BIN (kids->data); + GtkContainer *line_hbox = GTK_CONTAINER (line->child); + GtkWidget *line_check = + GTK_WIDGET (gtk_container_children (line_hbox)->data); + + if (checkable) + gtk_widget_show (line_check); + else + gtk_widget_hide (line_check); + + kids = kids->next; + } +#endif /* !HAVE_GTK2 */ +} + + +static void +populate_prefs_page (state *s) +{ + saver_preferences *p = &s->prefs; + + Bool can_lock_p = True; + + /* Disable all the "lock" controls if locking support was not provided + at compile-time, or if running on MacOS. */ +# if defined(NO_LOCKING) || defined(__APPLE__) + can_lock_p = False; +# endif + + + /* If there is only one screen, the mode menu contains + "random" but not "random-same". + */ + if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME) + p->mode = RANDOM_HACKS; + + + /* The file supports timeouts of less than a minute, but the GUI does + not, so throttle the values to be at least one minute (since "0" is + a bad rounding choice...) + */ +# define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000 + THROTTLE (timeout); + THROTTLE (cycle); + /* THROTTLE (passwd_timeout); */ /* GUI doesn't set this; leave it alone */ +# undef THROTTLE + +# define FMT_MINUTES(NAME,N) \ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000)) + +# define FMT_SECONDS(NAME,N) \ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000)) + + FMT_MINUTES ("timeout_spinbutton", p->timeout); + FMT_MINUTES ("cycle_spinbutton", p->cycle); + FMT_MINUTES ("lock_spinbutton", p->lock_timeout); + FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby); + FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend); + FMT_MINUTES ("dpms_off_spinbutton", p->dpms_off); + FMT_SECONDS ("fade_spinbutton", p->fade_seconds); + +# undef FMT_MINUTES +# undef FMT_SECONDS + +# define TOGGLE_ACTIVE(NAME,ACTIVEP) \ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\ + (ACTIVEP)) + + TOGGLE_ACTIVE ("lock_button", p->lock_p); +#if 0 + TOGGLE_ACTIVE ("verbose_button", p->verbose_p); + TOGGLE_ACTIVE ("capture_button", p->capture_stderr_p); + TOGGLE_ACTIVE ("splash_button", p->splash_p); +#endif + TOGGLE_ACTIVE ("dpms_button", p->dpms_enabled_p); + TOGGLE_ACTIVE ("grab_desk_button", p->grab_desktop_p); + TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p); + TOGGLE_ACTIVE ("grab_image_button", p->random_image_p); + TOGGLE_ACTIVE ("install_button", p->install_cmap_p); + TOGGLE_ACTIVE ("fade_button", p->fade_p); + TOGGLE_ACTIVE ("unfade_button", p->unfade_p); + + switch (p->tmode) + { + case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio", True); break; + case TEXT_FILE: TOGGLE_ACTIVE ("text_file_radio", True); break; + case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break; + case TEXT_URL: TOGGLE_ACTIVE ("text_url_radio", True); break; + default: TOGGLE_ACTIVE ("text_host_radio", True); break; + } + +# undef TOGGLE_ACTIVE + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")), + (p->image_directory ? p->image_directory : "")); + gtk_widget_set_sensitive (name_to_widget (s, "image_text"), + p->random_image_p); + gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"), + p->random_image_p); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")), + (p->text_literal ? p->text_literal : "")); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")), + (p->text_file ? p->text_file : "")); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")), + (p->text_program ? p->text_program : "")); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")), + (p->text_url ? p->text_url : "")); + + gtk_widget_set_sensitive (name_to_widget (s, "text_entry"), + p->tmode == TEXT_LITERAL); + gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"), + p->tmode == TEXT_FILE); + gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"), + p->tmode == TEXT_FILE); + gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"), + p->tmode == TEXT_PROGRAM); + gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"), + p->tmode == TEXT_PROGRAM); + gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"), + p->tmode == TEXT_URL); + + + /* Map the `saver_mode' enum to mode menu to values. */ + { + GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); + + int i; + for (i = 0; i < countof(mode_menu_order); i++) + if (mode_menu_order[i] == p->mode) + break; + gtk_option_menu_set_history (opt, i); + update_list_sensitivity (s); + } + + { + Bool found_any_writable_cells = False; + Bool fading_possible = False; + Bool dpms_supported = False; + + Display *dpy = GDK_DISPLAY(); + int nscreens = ScreenCount(dpy); /* real screens, not Xinerama */ + int i; + for (i = 0; i < nscreens; i++) + { + Screen *s = ScreenOfDisplay (dpy, i); + if (has_writable_cells (s, DefaultVisualOfScreen (s))) + { + found_any_writable_cells = True; + break; + } + } + + fading_possible = found_any_writable_cells; +#ifdef HAVE_XF86VMODE_GAMMA + fading_possible = True; +#endif + +#ifdef HAVE_DPMS_EXTENSION + { + int op = 0, event = 0, error = 0; + if (XQueryExtension (dpy, "DPMS", &op, &event, &error)) + dpms_supported = True; + } +#endif /* HAVE_DPMS_EXTENSION */ + + +# define SENSITIZE(NAME,SENSITIVEP) \ + gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP)) + + /* Blanking and Locking + */ + SENSITIZE ("lock_button", can_lock_p); + SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p); + SENSITIZE ("lock_mlabel", can_lock_p && p->lock_p); + + /* DPMS + */ + SENSITIZE ("dpms_frame", dpms_supported); + SENSITIZE ("dpms_button", dpms_supported); + SENSITIZE ("dpms_standby_label", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_standby_mlabel", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_suspend_label", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_suspend_mlabel", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_off_label", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_off_mlabel", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_off_spinbutton", dpms_supported && p->dpms_enabled_p); + + /* Colormaps + */ + SENSITIZE ("cmap_frame", found_any_writable_cells || fading_possible); + SENSITIZE ("install_button", found_any_writable_cells); + SENSITIZE ("fade_button", fading_possible); + SENSITIZE ("unfade_button", fading_possible); + + SENSITIZE ("fade_label", (fading_possible && + (p->fade_p || p->unfade_p))); + SENSITIZE ("fade_spinbutton", (fading_possible && + (p->fade_p || p->unfade_p))); + +# undef SENSITIZE + } +} + + +static void +populate_popup_window (state *s) +{ + GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc")); + char *doc_string = 0; + + /* #### not in Gtk 1.2 + gtk_label_set_selectable (doc); + */ + +# ifdef HAVE_XML + if (s->cdata) + { + free_conf_data (s->cdata); + s->cdata = 0; + } + + { + saver_preferences *p = &s->prefs; + int list_elt = selected_list_element (s); + int hack_number = (list_elt >= 0 && list_elt < s->list_count + ? s->list_elt_to_hack_number[list_elt] + : -1); + screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0); + if (hack) + { + GtkWidget *parent = name_to_widget (s, "settings_vbox"); + GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); + const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd)); + s->cdata = load_configurator (cmd_line, s->debug_p); + if (s->cdata && s->cdata->widget) + gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget, + TRUE, TRUE, 0); + } + } + + doc_string = (s->cdata + ? s->cdata->description + : 0); +# else /* !HAVE_XML */ + doc_string = _("Descriptions not available: no XML support compiled in."); +# endif /* !HAVE_XML */ + + gtk_label_set_text (doc, (doc_string + ? _(doc_string) + : _("No description available."))); +} + + +static void +sensitize_demo_widgets (state *s, Bool sensitive_p) +{ + const char *names[] = { "demo", "settings", + "cmd_label", "cmd_text", "manual", + "visual", "visual_combo" }; + int i; + for (i = 0; i < countof(names); i++) + { + GtkWidget *w = name_to_widget (s, names[i]); + gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + } +} + + +static void +sensitize_menu_items (state *s, Bool force_p) +{ + static Bool running_p = False; + static time_t last_checked = 0; + time_t now = time ((time_t *) 0); + const char *names[] = { "activate_menu", "lock_menu", "kill_menu", + /* "demo" */ }; + int i; + + if (force_p || now > last_checked + 10) /* check every 10 seconds */ + { + running_p = xscreensaver_running_p (s); + last_checked = time ((time_t *) 0); + } + + for (i = 0; i < countof(names); i++) + { + GtkWidget *w = name_to_widget (s, names[i]); + gtk_widget_set_sensitive (GTK_WIDGET(w), running_p); + } +} + + +/* When the File menu is de-posted after a "Restart Daemon" command, + the window underneath doesn't repaint for some reason. I guess this + is a bug in exposure handling in GTK or GDK. This works around it. + */ +static void +force_dialog_repaint (state *s) +{ +#if 1 + /* Tell GDK to invalidate and repaint the whole window. + */ + GdkWindow *w = s->toplevel_widget->window; + GdkRegion *region = gdk_region_new (); + GdkRectangle rect; + rect.x = rect.y = 0; + rect.width = rect.height = 32767; + gdk_region_union_with_rect (region, &rect); + gdk_window_invalidate_region (w, region, True); + gdk_region_destroy (region); + gdk_window_process_updates (w, True); +#else + /* Force the server to send an exposure event by creating and then + destroying a window as a child of the top level shell. + */ + Display *dpy = GDK_DISPLAY(); + Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window); + Window w; + XWindowAttributes xgwa; + XGetWindowAttributes (dpy, parent, &xgwa); + w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0); + XMapRaised (dpy, w); + XDestroyWindow (dpy, w); + XSync (dpy, False); +#endif +} + + +/* Even though we've given these text fields a maximum number of characters, + their default size is still about 30 characters wide -- so measure out + a string in their font, and resize them to just fit that. + */ +static void +fix_text_entry_sizes (state *s) +{ + GtkWidget *w; + +# if 0 /* appears no longer necessary with Gtk 1.2.10 */ + const char * const spinbuttons[] = { + "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton", + "dpms_standby_spinbutton", "dpms_suspend_spinbutton", + "dpms_off_spinbutton", + "-fade_spinbutton" }; + int i; + int width = 0; + + for (i = 0; i < countof(spinbuttons); i++) + { + const char *n = spinbuttons[i]; + int cols = 4; + while (*n == '-') n++, cols--; + w = GTK_WIDGET (name_to_widget (s, n)); + width = gdk_text_width (w->style->font, "MMMMMMMM", cols); + gtk_widget_set_usize (w, width, -2); + } + + /* Now fix the width of the combo box. + */ + w = GTK_WIDGET (name_to_widget (s, "visual_combo")); + w = GTK_COMBO (w)->entry; + width = gdk_string_width (w->style->font, "PseudoColor___"); + gtk_widget_set_usize (w, width, -2); + + /* Now fix the width of the file entry text. + */ + w = GTK_WIDGET (name_to_widget (s, "image_text")); + width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm"); + gtk_widget_set_usize (w, width, -2); + + /* Now fix the width of the command line text. + */ + w = GTK_WIDGET (name_to_widget (s, "cmd_text")); + width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm"); + gtk_widget_set_usize (w, width, -2); + +# endif /* 0 */ + + /* Now fix the height of the list widget: + make it default to being around 10 text-lines high instead of 4. + */ + w = GTK_WIDGET (name_to_widget (s, "list")); + { + int lines = 10; + int height; + int leading = 3; /* approximate is ok... */ + int border = 2; + +#ifdef HAVE_GTK2 + PangoFontMetrics *pain = + pango_context_get_metrics (gtk_widget_get_pango_context (w), + w->style->font_desc, + gtk_get_default_language ()); + height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) + + pango_font_metrics_get_descent (pain)); +#else /* !HAVE_GTK2 */ + height = w->style->font->ascent + w->style->font->descent; +#endif /* !HAVE_GTK2 */ + + height += leading; + height *= lines; + height += border * 2; + w = GTK_WIDGET (name_to_widget (s, "scroller")); + gtk_widget_set_usize (w, -2, height); + } +} + + +#ifndef HAVE_GTK2 + +/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...) + */ + +static char *up_arrow_xpm[] = { + "15 15 4 1", + " c None", + "- c #FFFFFF", + "+ c #D6D6D6", + "@ c #000000", + + " @ ", + " @ ", + " -+@ ", + " -+@ ", + " -+++@ ", + " -+++@ ", + " -+++++@ ", + " -+++++@ ", + " -+++++++@ ", + " -+++++++@ ", + " -+++++++++@ ", + " -+++++++++@ ", + " -+++++++++++@ ", + " @@@@@@@@@@@@@ ", + " ", + + /* Need these here because gdk_pixmap_create_from_xpm_d() walks off + the end of the array (Gtk 1.2.5.) */ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" +}; + +static char *down_arrow_xpm[] = { + "15 15 4 1", + " c None", + "- c #FFFFFF", + "+ c #D6D6D6", + "@ c #000000", + + " ", + " ------------- ", + " -+++++++++++@ ", + " -+++++++++@ ", + " -+++++++++@ ", + " -+++++++@ ", + " -+++++++@ ", + " -+++++@ ", + " -+++++@ ", + " -+++@ ", + " -+++@ ", + " -+@ ", + " -+@ ", + " @ ", + " @ ", + + /* Need these here because gdk_pixmap_create_from_xpm_d() walks off + the end of the array (Gtk 1.2.5.) */ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" +}; + +static void +pixmapify_button (state *s, int down_p) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkWidget *pixmapwid; + GtkStyle *style; + GtkWidget *w; + + w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev"))); + style = gtk_widget_get_style (w); + mask = 0; + pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask, + &style->bg[GTK_STATE_NORMAL], + (down_p + ? (gchar **) down_arrow_xpm + : (gchar **) up_arrow_xpm)); + pixmapwid = gtk_pixmap_new (pixmap, mask); + gtk_widget_show (pixmapwid); + gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child); + gtk_container_add (GTK_CONTAINER (w), pixmapwid); +} + +static void +map_next_button_cb (GtkWidget *w, gpointer user_data) +{ + state *s = (state *) user_data; + pixmapify_button (s, 1); +} + +static void +map_prev_button_cb (GtkWidget *w, gpointer user_data) +{ + state *s = (state *) user_data; + pixmapify_button (s, 0); +} +#endif /* !HAVE_GTK2 */ + + +#ifndef HAVE_GTK2 +/* Work around a Gtk bug that causes label widgets to wrap text too early. + */ + +static void +you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label, + GtkAllocation *allocation, + void *foo) +{ + GtkRequisition req; + GtkWidgetAuxInfo *aux_info; + + aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info"); + + aux_info->width = allocation->width; + aux_info->height = -2; + aux_info->x = -1; + aux_info->y = -1; + + gtk_widget_size_request (label, &req); +} + +/* Feel the love. Thanks to Nat Friedman for finding this workaround. + */ +static void +eschew_gtk_lossage (GtkLabel *label) +{ + GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1); + aux_info->width = GTK_WIDGET (label)->allocation.width; + aux_info->height = -2; + aux_info->x = -1; + aux_info->y = -1; + + gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info); + + gtk_signal_connect (GTK_OBJECT (label), "size_allocate", + GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake), + 0); + + gtk_widget_set_usize (GTK_WIDGET (label), -2, -2); + + gtk_widget_queue_resize (GTK_WIDGET (label)); +} +#endif /* !HAVE_GTK2 */ + + +static void +populate_demo_window (state *s, int list_elt) +{ + Display *dpy = GDK_DISPLAY(); + saver_preferences *p = &s->prefs; + screenhack *hack; + char *pretty_name; + GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame")); + GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "opt_frame")); + GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text")); + GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo")); + GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list")); + + if (p->mode == BLANK_ONLY) + { + hack = 0; + pretty_name = strdup (_("Blank Screen")); + schedule_preview (s, 0); + } + else if (p->mode == DONT_BLANK) + { + hack = 0; + pretty_name = strdup (_("Screen Saver Disabled")); + schedule_preview (s, 0); + } + else + { + int hack_number = (list_elt >= 0 && list_elt < s->list_count + ? s->list_elt_to_hack_number[list_elt] + : -1); + hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0); + + pretty_name = (hack + ? (hack->name + ? strdup (hack->name) + : make_hack_name (dpy, hack->command)) + : 0); + + if (hack) + schedule_preview (s, hack->command); + else + schedule_preview (s, 0); + } + + if (!pretty_name) + pretty_name = strdup (_("Preview")); + + gtk_frame_set_label (frame1, _(pretty_name)); + gtk_frame_set_label (frame2, _(pretty_name)); + + gtk_entry_set_text (cmd, (hack ? hack->command : "")); + gtk_entry_set_position (cmd, 0); + + { + char title[255]; + sprintf (title, _("%s: %.100s Settings"), + progclass, (pretty_name ? pretty_name : "???")); + gtk_window_set_title (GTK_WINDOW (s->popup_widget), title); + } + + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), + (hack + ? (hack->visual && *hack->visual + ? hack->visual + : _("Any")) + : "")); + + sensitize_demo_widgets (s, (hack ? True : False)); + + if (pretty_name) free (pretty_name); + + ensure_selected_item_visible (list); + + s->_selected_list_element = list_elt; +} + + +static void +widget_deleter (GtkWidget *widget, gpointer data) +{ + /* #### Well, I want to destroy these widgets, but if I do that, they get + referenced again, and eventually I get a SEGV. So instead of + destroying them, I'll just hide them, and leak a bunch of memory + every time the disk file changes. Go go go Gtk! + + #### Ok, that's a lie, I get a crash even if I just hide the widget + and don't ever delete it. Fuck! + */ +#if 0 + gtk_widget_destroy (widget); +#else + gtk_widget_hide (widget); +#endif +} + + +static char **sort_hack_cmp_names_kludge; +static int +sort_hack_cmp (const void *a, const void *b) +{ + if (a == b) + return 0; + else + { + int aa = *(int *) a; + int bb = *(int *) b; + const char last[] = "\377\377\377\377\377\377\377\377\377\377\377"; + return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]), + (bb < 0 ? last : sort_hack_cmp_names_kludge[bb])); + } +} + + +static void +initialize_sort_map (state *s) +{ + Display *dpy = GDK_DISPLAY(); + saver_preferences *p = &s->prefs; + int i, j; + + if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number); + if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt); + if (s->hacks_available_p) free (s->hacks_available_p); + + s->list_elt_to_hack_number = (int *) + calloc (sizeof(int), p->screenhacks_count + 1); + s->hack_number_to_list_elt = (int *) + calloc (sizeof(int), p->screenhacks_count + 1); + s->hacks_available_p = (Bool *) + calloc (sizeof(Bool), p->screenhacks_count + 1); + s->total_available = 0; + + /* Check which hacks actually exist on $PATH + */ + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + int on = on_path_p (hack->command) ? 1 : 0; + s->hacks_available_p[i] = on; + s->total_available += on; + } + + /* Initialize list->hack table to unsorted mapping, omitting nonexistent + hacks, if desired. + */ + j = 0; + for (i = 0; i < p->screenhacks_count; i++) + { + if (!p->ignore_uninstalled_p || + s->hacks_available_p[i]) + s->list_elt_to_hack_number[j++] = i; + } + s->list_count = j; + + for (; j < p->screenhacks_count; j++) + s->list_elt_to_hack_number[j] = -1; + + + /* Generate list of sortable names (once) + */ + sort_hack_cmp_names_kludge = (char **) + calloc (sizeof(char *), p->screenhacks_count); + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + char *name = (hack->name && *hack->name + ? strdup (hack->name) + : make_hack_name (dpy, hack->command)); + char *str; + for (str = name; *str; str++) + *str = tolower(*str); + sort_hack_cmp_names_kludge[i] = name; + } + + /* Sort list->hack map alphabetically + */ + qsort (s->list_elt_to_hack_number, + p->screenhacks_count, + sizeof(*s->list_elt_to_hack_number), + sort_hack_cmp); + + /* Free names + */ + for (i = 0; i < p->screenhacks_count; i++) + free (sort_hack_cmp_names_kludge[i]); + free (sort_hack_cmp_names_kludge); + sort_hack_cmp_names_kludge = 0; + + /* Build inverse table */ + for (i = 0; i < p->screenhacks_count; i++) + { + int n = s->list_elt_to_hack_number[i]; + if (n != -1) + s->hack_number_to_list_elt[n] = i; + } +} + + +static int +maybe_reload_init_file (state *s) +{ + Display *dpy = GDK_DISPLAY(); + saver_preferences *p = &s->prefs; + int status = 0; + + static Bool reentrant_lock = False; + if (reentrant_lock) return 0; + reentrant_lock = True; + + if (init_file_changed_p (p)) + { + const char *f = init_file_name(); + char *b; + int list_elt; + GtkWidget *list; + + if (!f || !*f) return 0; + b = (char *) malloc (strlen(f) + 1024); + sprintf (b, + _("Warning:\n\n" + "file \"%s\" has changed, reloading.\n"), + f); + warning_dialog (s->toplevel_widget, b, D_NONE, 100); + free (b); + + load_init_file (dpy, p); + initialize_sort_map (s); + + list_elt = selected_list_element (s); + list = name_to_widget (s, "list"); + gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL); + populate_hack_list (s); + force_list_select_item (s, list, list_elt, True); + populate_prefs_page (s); + populate_demo_window (s, list_elt); + ensure_selected_item_visible (list); + + status = 1; + } + + reentrant_lock = False; + return status; +} + + + +/* Making the preview window have the right X visual (so that GL works.) + */ + +static Visual *get_best_gl_visual (state *); + +static GdkVisual * +x_visual_to_gdk_visual (Visual *xv) +{ + GList *gvs = gdk_list_visuals(); + if (!xv) return gdk_visual_get_system(); + for (; gvs; gvs = gvs->next) + { + GdkVisual *gv = (GdkVisual *) gvs->data; + if (xv == GDK_VISUAL_XVISUAL (gv)) + return gv; + } + fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n", + blurb(), (unsigned long) xv->visualid); + abort(); +} + +static void +clear_preview_window (state *s) +{ + GtkWidget *p; + GdkWindow *window; + + if (!s->toplevel_widget) return; /* very early */ + p = name_to_widget (s, "preview"); + window = p->window; + + if (!window) return; + + /* Flush the widget background down into the window, in case a subproc + has changed it. */ + gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]); + gdk_window_clear (window); + + { + int list_elt = selected_list_element (s); + int hack_number = (list_elt >= 0 + ? s->list_elt_to_hack_number[list_elt] + : -1); + Bool available_p = (hack_number >= 0 + ? s->hacks_available_p [hack_number] + : True); + Bool nothing_p = (s->total_available < 5); + +#ifdef HAVE_GTK2 + GtkWidget *notebook = name_to_widget (s, "preview_notebook"); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), + (s->running_preview_error_p + ? (available_p ? 1 : + nothing_p ? 3 : 2) + : 0)); +#else /* !HAVE_GTK2 */ + if (s->running_preview_error_p) + { + const char * const lines1[] = { N_("No Preview"), N_("Available") }; + const char * const lines2[] = { N_("Not"), N_("Installed") }; + int nlines = countof(lines1); + int lh = p->style->font->ascent + p->style->font->descent; + int y, i; + gint w, h; + + const char * const *lines = (available_p ? lines1 : lines2); + + gdk_window_get_size (window, &w, &h); + y = (h - (lh * nlines)) / 2; + y += p->style->font->ascent; + for (i = 0; i < nlines; i++) + { + int sw = gdk_string_width (p->style->font, _(lines[i])); + int x = (w - sw) / 2; + gdk_draw_string (window, p->style->font, + p->style->fg_gc[GTK_STATE_NORMAL], + x, y, _(lines[i])); + y += lh; + } + } +#endif /* !HAVE_GTK2 */ + } + + gdk_flush (); +} + + +static void +reset_preview_window (state *s) +{ + /* On some systems (most recently, MacOS X) OpenGL programs get confused + when you kill one and re-start another on the same window. So maybe + it's best to just always destroy and recreate the preview window + when changing hacks, instead of always trying to reuse the same one? + */ + GtkWidget *pr = name_to_widget (s, "preview"); + if (GTK_WIDGET_REALIZED (pr)) + { + Window oid = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0); + Window id; + gtk_widget_hide (pr); + gtk_widget_unrealize (pr); + gtk_widget_realize (pr); + gtk_widget_show (pr); + id = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0); + if (s->debug_p) + fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(), + (unsigned int) oid, + (unsigned int) id); + } +} + + +static void +fix_preview_visual (state *s) +{ + GtkWidget *widget = name_to_widget (s, "preview"); + Visual *xvisual = get_best_gl_visual (s); + GdkVisual *visual = x_visual_to_gdk_visual (xvisual); + GdkVisual *dvisual = gdk_visual_get_system(); + GdkColormap *cmap = (visual == dvisual + ? gdk_colormap_get_system () + : gdk_colormap_new (visual, False)); + + if (s->debug_p) + fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(), + (visual == dvisual ? "default" : "non-default"), + (xvisual ? (unsigned long) xvisual->visualid : 0L)); + + if (!GTK_WIDGET_REALIZED (widget) || + gtk_widget_get_visual (widget) != visual) + { + gtk_widget_unrealize (widget); + gtk_widget_set_visual (widget, visual); + gtk_widget_set_colormap (widget, cmap); + gtk_widget_realize (widget); + } + + /* Set the Widget colors to be white-on-black. */ + { + GdkWindow *window = widget->window; + GtkStyle *style = gtk_style_copy (widget->style); + GdkColormap *cmap = gtk_widget_get_colormap (widget); + GdkColor *fg = &style->fg[GTK_STATE_NORMAL]; + GdkColor *bg = &style->bg[GTK_STATE_NORMAL]; + GdkGC *fgc = gdk_gc_new(window); + GdkGC *bgc = gdk_gc_new(window); + if (!gdk_color_white (cmap, fg)) abort(); + if (!gdk_color_black (cmap, bg)) abort(); + gdk_gc_set_foreground (fgc, fg); + gdk_gc_set_background (fgc, bg); + gdk_gc_set_foreground (bgc, bg); + gdk_gc_set_background (bgc, fg); + style->fg_gc[GTK_STATE_NORMAL] = fgc; + style->bg_gc[GTK_STATE_NORMAL] = fgc; + gtk_widget_set_style (widget, style); + + /* For debugging purposes, put a title on the window (so that + it can be easily found in the output of "xwininfo -tree".) + */ + gdk_window_set_title (window, "Preview"); + } + + gtk_widget_show (widget); +} + + +/* Subprocesses + */ + +static char * +subproc_pretty_name (state *s) +{ + if (s->running_preview_cmd) + { + char *ps = strdup (s->running_preview_cmd); + char *ss = strchr (ps, ' '); + if (ss) *ss = 0; + ss = strrchr (ps, '/'); + if (!ss) + ss = ps; + else + { + ss = strdup (ss+1); + free (ps); + } + return ss; + } + else + return strdup ("???"); +} + + +static void +reap_zombies (state *s) +{ + int wait_status = 0; + pid_t pid; + while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0) + { + if (s->debug_p) + { + if (pid == s->running_preview_pid) + { + char *ss = subproc_pretty_name (s); + fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(), + (unsigned long) pid, ss); + free (ss); + } + else + fprintf (stderr, "%s: pid %lu died\n", blurb(), + (unsigned long) pid); + } + } +} + + +/* Mostly lifted from driver/subprocs.c */ +static Visual * +get_best_gl_visual (state *s) +{ + Display *dpy = GDK_DISPLAY(); + pid_t forked; + int fds [2]; + int in, out; + char buf[1024]; + + char *av[10]; + int ac = 0; + + av[ac++] = "xscreensaver-gl-helper"; + av[ac] = 0; + + if (pipe (fds)) + { + perror ("error creating pipe:"); + return 0; + } + + in = fds [0]; + out = fds [1]; + + switch ((int) (forked = fork ())) + { + case -1: + { + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + exit (1); + } + case 0: + { + int stdout_fd = 1; + + close (in); /* don't need this one */ + close (ConnectionNumber (dpy)); /* close display fd */ + + if (dup2 (out, stdout_fd) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stdout:"); + return 0; + } + + execvp (av[0], av); /* shouldn't return. */ + + if (errno != ENOENT) + { + /* Ignore "no such file or directory" errors, unless verbose. + Issue all other exec errors, though. */ + sprintf (buf, "%s: running %s", blurb(), av[0]); + perror (buf); + } + + /* Note that one must use _exit() instead of exit() in procs forked + off of Gtk programs -- Gtk installs an atexit handler that has a + copy of the X connection (which we've already closed, for safety.) + If one uses exit() instead of _exit(), then one sometimes gets a + spurious "Gdk-ERROR: Fatal IO error on X server" error message. + */ + _exit (1); /* exits fork */ + break; + } + default: + { + int result = 0; + int wait_status = 0; + + FILE *f = fdopen (in, "r"); + unsigned int v = 0; + char c; + + close (out); /* don't need this one */ + + *buf = 0; + if (!fgets (buf, sizeof(buf)-1, f)) + *buf = 0; + fclose (f); + + /* Wait for the child to die. */ + waitpid (-1, &wait_status, 0); + + if (1 == sscanf (buf, "0x%x %c", &v, &c)) + result = (int) v; + + if (result == 0) + { + if (s->debug_p) + fprintf (stderr, "%s: %s did not report a GL visual!\n", + blurb(), av[0]); + return 0; + } + else + { + Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result); + if (s->debug_p) + fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n", + blurb(), av[0], result); + if (!v) abort(); + return v; + } + } + } + + abort(); +} + + +static void +kill_preview_subproc (state *s, Bool reset_p) +{ + s->running_preview_error_p = False; + + reap_zombies (s); + clear_preview_window (s); + + if (s->subproc_check_timer_id) + { + gtk_timeout_remove (s->subproc_check_timer_id); + s->subproc_check_timer_id = 0; + s->subproc_check_countdown = 0; + } + + if (s->running_preview_pid) + { + int status = kill (s->running_preview_pid, SIGTERM); + char *ss = subproc_pretty_name (s); + + if (status < 0) + { + if (errno == ESRCH) + { + if (s->debug_p) + fprintf (stderr, "%s: pid %lu (%s) was already dead.\n", + blurb(), (unsigned long) s->running_preview_pid, ss); + } + else + { + char buf [1024]; + sprintf (buf, "%s: couldn't kill pid %lu (%s)", + blurb(), (unsigned long) s->running_preview_pid, ss); + perror (buf); + } + } + else { + int endstatus; + waitpid(s->running_preview_pid, &endstatus, 0); + if (s->debug_p) + fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(), + (unsigned long) s->running_preview_pid, ss); + } + + free (ss); + s->running_preview_pid = 0; + if (s->running_preview_cmd) free (s->running_preview_cmd); + s->running_preview_cmd = 0; + } + + reap_zombies (s); + + if (reset_p) + { + reset_preview_window (s); + clear_preview_window (s); + } +} + + +/* Immediately and unconditionally launches the given process, + after appending the -window-id option; sets running_preview_pid. + */ +static void +launch_preview_subproc (state *s) +{ + saver_preferences *p = &s->prefs; + Window id; + char *new_cmd = 0; + pid_t forked; + const char *cmd = s->desired_preview_cmd; + + GtkWidget *pr = name_to_widget (s, "preview"); + GdkWindow *window; + + reset_preview_window (s); + + window = pr->window; + + s->running_preview_error_p = False; + + if (s->preview_suppressed_p) + { + kill_preview_subproc (s, False); + goto DONE; + } + + new_cmd = malloc (strlen (cmd) + 40); + + id = (window ? GDK_WINDOW_XWINDOW (window) : 0); + if (id == 0) + { + /* No window id? No command to run. */ + free (new_cmd); + new_cmd = 0; + } + else + { + strcpy (new_cmd, cmd); + sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", + (unsigned int) id); + } + + kill_preview_subproc (s, False); + if (! new_cmd) + { + s->running_preview_error_p = True; + clear_preview_window (s); + goto DONE; + } + + switch ((int) (forked = fork ())) + { + case -1: + { + char buf[255]; + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + s->running_preview_error_p = True; + goto DONE; + break; + } + case 0: + { + close (ConnectionNumber (GDK_DISPLAY())); + + hack_subproc_environment (id, s->debug_p); + + usleep (250000); /* pause for 1/4th second before launching, to give + the previous program time to die and flush its X + buffer, so we don't get leftover turds on the + window. */ + + exec_command (p->shell, new_cmd, p->nice_inferior); + /* Don't bother printing an error message when we are unable to + exec subprocesses; we handle that by polling the pid later. + + Note that one must use _exit() instead of exit() in procs forked + off of Gtk programs -- Gtk installs an atexit handler that has a + copy of the X connection (which we've already closed, for safety.) + If one uses exit() instead of _exit(), then one sometimes gets a + spurious "Gdk-ERROR: Fatal IO error on X server" error message. + */ + _exit (1); /* exits child fork */ + break; + + default: + + if (s->running_preview_cmd) free (s->running_preview_cmd); + s->running_preview_cmd = strdup (s->desired_preview_cmd); + s->running_preview_pid = forked; + + if (s->debug_p) + { + char *ss = subproc_pretty_name (s); + fprintf (stderr, "%s: forked %lu (%s)\n", blurb(), + (unsigned long) forked, ss); + free (ss); + } + break; + } + } + + schedule_preview_check (s); + + DONE: + if (new_cmd) free (new_cmd); + new_cmd = 0; +} + + +/* Modify $DISPLAY and $PATH for the benefit of subprocesses. + */ +static void +hack_environment (state *s) +{ + static const char *def_path = +# ifdef DEFAULT_PATH_PREFIX + DEFAULT_PATH_PREFIX; +# else + ""; +# endif + + Display *dpy = GDK_DISPLAY(); + const char *odpy = DisplayString (dpy); + char *ndpy = (char *) malloc(strlen(odpy) + 20); + strcpy (ndpy, "DISPLAY="); + strcat (ndpy, odpy); + if (putenv (ndpy)) + abort (); + + if (s->debug_p) + fprintf (stderr, "%s: %s\n", blurb(), ndpy); + + /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc + 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not. + So we must leak it (and/or the previous setting). Yay. + */ + + if (def_path && *def_path) + { + const char *opath = getenv("PATH"); + char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20); + strcpy (npath, "PATH="); + strcat (npath, def_path); + strcat (npath, ":"); + strcat (npath, opath); + + if (putenv (npath)) + abort (); + /* do not free(npath) -- see above */ + + if (s->debug_p) + fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path); + } +} + + +static void +hack_subproc_environment (Window preview_window_id, Bool debug_p) +{ + /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly + necessary yet, but it will make programs work if we had invoked + them with "-root" and not with "-window-id" -- which, of course, + doesn't happen. + */ + char *nssw = (char *) malloc (40); + sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id); + + /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems + any more, right? It's not Posix, but everyone seems to have it. */ + if (putenv (nssw)) + abort (); + + if (debug_p) + fprintf (stderr, "%s: %s\n", blurb(), nssw); + + /* do not free(nssw) -- see above */ +} + + +/* Called from a timer: + Launches the currently-chosen subprocess, if it's not already running. + If there's a different process running, kills it. + */ +static int +update_subproc_timer (gpointer data) +{ + state *s = (state *) data; + if (! s->desired_preview_cmd) + kill_preview_subproc (s, True); + else if (!s->running_preview_cmd || + !!strcmp (s->desired_preview_cmd, s->running_preview_cmd)) + launch_preview_subproc (s); + + s->subproc_timer_id = 0; + return FALSE; /* do not re-execute timer */ +} + +static int +settings_timer (gpointer data) +{ + settings_cb (0, 0); + return FALSE; +} + + +/* Call this when you think you might want a preview process running. + It will set a timer that will actually launch that program a second + from now, if you haven't changed your mind (to avoid double-click + spazzing, etc.) `cmd' may be null meaning "no process". + */ +static void +schedule_preview (state *s, const char *cmd) +{ + int delay = 1000 * 0.5; /* 1/2 second hysteresis */ + + if (s->debug_p) + { + if (cmd) + fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd); + else + fprintf (stderr, "%s: scheduling preview death\n", blurb()); + } + + if (s->desired_preview_cmd) free (s->desired_preview_cmd); + s->desired_preview_cmd = (cmd ? strdup (cmd) : 0); + + if (s->subproc_timer_id) + gtk_timeout_remove (s->subproc_timer_id); + s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s); +} + + +/* Called from a timer: + Checks to see if the subproc that should be running, actually is. + */ +static int +check_subproc_timer (gpointer data) +{ + state *s = (state *) data; + Bool again_p = True; + + if (s->running_preview_error_p || /* already dead */ + s->running_preview_pid <= 0) + { + again_p = False; + } + else + { + int status; + reap_zombies (s); + status = kill (s->running_preview_pid, 0); + if (status < 0 && errno == ESRCH) + s->running_preview_error_p = True; + + if (s->debug_p) + { + char *ss = subproc_pretty_name (s); + fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(), + (unsigned long) s->running_preview_pid, ss, + (s->running_preview_error_p ? "dead" : "alive")); + free (ss); + } + + if (s->running_preview_error_p) + { + clear_preview_window (s); + again_p = False; + } + } + + /* Otherwise, it's currently alive. We might be checking again, or we + might be satisfied. */ + + if (--s->subproc_check_countdown <= 0) + again_p = False; + + if (again_p) + return TRUE; /* re-execute timer */ + else + { + s->subproc_check_timer_id = 0; + s->subproc_check_countdown = 0; + return FALSE; /* do not re-execute timer */ + } +} + + +/* Call this just after launching a subprocess. + This sets a timer that will, five times a second for two seconds, + check whether the program is still running. The assumption here + is that if the process didn't stay up for more than a couple of + seconds, then either the program doesn't exist, or it doesn't + take a -window-id argument. + */ +static void +schedule_preview_check (state *s) +{ + int seconds = 2; + int ticks = 5; + + if (s->debug_p) + fprintf (stderr, "%s: scheduling check\n", blurb()); + + if (s->subproc_check_timer_id) + gtk_timeout_remove (s->subproc_check_timer_id); + s->subproc_check_timer_id = + gtk_timeout_add (1000 / ticks, + check_subproc_timer, (gpointer) s); + s->subproc_check_countdown = ticks * seconds; +} + + +static Bool +screen_blanked_p (void) +{ + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + Display *dpy = GDK_DISPLAY(); + Bool blanked_p = False; + + if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + XA_SCREENSAVER_STATUS, + 0, 3, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) + { + Atom *data = (Atom *) dataP; + blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK); + } + + if (dataP) XFree (dataP); + + return blanked_p; +} + +/* Wake up every now and then and see if the screen is blanked. + If it is, kill off the small-window demo -- no point in wasting + cycles by running two screensavers at once... + */ +static int +check_blanked_timer (gpointer data) +{ + state *s = (state *) data; + Bool blanked_p = screen_blanked_p (); + if (blanked_p && s->running_preview_pid) + { + if (s->debug_p) + fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb()); + kill_preview_subproc (s, True); + } + + return True; /* re-execute timer */ +} + + +/* How many screens are there (including Xinerama.) + */ +static int +screen_count (Display *dpy) +{ + int nscreens = ScreenCount(dpy); +# ifdef HAVE_XINERAMA + if (nscreens <= 1) + { + int event_number, error_number; + if (XineramaQueryExtension (dpy, &event_number, &error_number) && + XineramaIsActive (dpy)) + { + XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens); + if (xsi) XFree (xsi); + } + } +# endif /* HAVE_XINERAMA */ + + return nscreens; +} + + +/* Setting window manager icon + */ + +static void +init_icon (GdkWindow *window) +{ + GdkBitmap *mask = 0; + GdkColor transp; + GdkPixmap *pixmap = + gdk_pixmap_create_from_xpm_d (window, &mask, &transp, + (gchar **) logo_50_xpm); + if (pixmap) + gdk_window_set_icon (window, 0, pixmap, mask); +} + + +/* The main demo-mode command loop. + */ + +#if 0 +static Bool +mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, + XrmRepresentation *type, XrmValue *value, XPointer closure) +{ + int i; + for (i = 0; quarks[i]; i++) + { + if (bindings[i] == XrmBindTightly) + fprintf (stderr, (i == 0 ? "" : ".")); + else if (bindings[i] == XrmBindLoosely) + fprintf (stderr, "*"); + else + fprintf (stderr, " ??? "); + fprintf(stderr, "%s", XrmQuarkToString (quarks[i])); + } + + fprintf (stderr, ": %s\n", (char *) value->addr); + + return False; +} +#endif + + +static Window +gnome_screensaver_window (Screen *screen) +{ + Display *dpy = DisplayOfScreen (screen); + Window root = RootWindowOfScreen (screen); + Window parent, *kids; + unsigned int nkids; + Window gnome_window = 0; + int i; + + if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids)) + abort (); + for (i = 0; i < nkids; i++) + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *name; + if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &name) + == Success + && type != None + && !strcmp ((char *) name, "gnome-screensaver")) + { + gnome_window = kids[i]; + break; + } + } + + if (kids) XFree ((char *) kids); + return gnome_window; +} + +static Bool +gnome_screensaver_active_p (void) +{ + Display *dpy = GDK_DISPLAY(); + Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy)); + return (w ? True : False); +} + +static void +kill_gnome_screensaver (void) +{ + Display *dpy = GDK_DISPLAY(); + Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy)); + if (w) XKillClient (dpy, (XID) w); +} + +static Bool +kde_screensaver_active_p (void) +{ + FILE *p = popen ("dcop kdesktop KScreensaverIface isEnabled 2>/dev/null", + "r"); + char buf[255]; + fgets (buf, sizeof(buf)-1, p); + pclose (p); + if (!strcmp (buf, "true\n")) + return True; + else + return False; +} + +static void +kill_kde_screensaver (void) +{ + system ("dcop kdesktop KScreensaverIface enable false"); +} + + +static void +the_network_is_not_the_computer (state *s) +{ + Display *dpy = GDK_DISPLAY(); + char *rversion = 0, *ruser = 0, *rhost = 0; + char *luser, *lhost; + char *msg = 0; + struct passwd *p = getpwuid (getuid ()); + const char *d = DisplayString (dpy); + +# if defined(HAVE_UNAME) + struct utsname uts; + if (uname (&uts) < 0) + lhost = ""; + else + lhost = uts.nodename; +# elif defined(VMS) + strcpy (lhost, getenv("SYS$NODE")); +# else /* !HAVE_UNAME && !VMS */ + strcat (lhost, ""); +# endif /* !HAVE_UNAME && !VMS */ + + if (p && p->pw_name) + luser = p->pw_name; + else + luser = "???"; + + server_xscreensaver_version (dpy, &rversion, &ruser, &rhost); + + /* Make a buffer that's big enough for a number of copies of all the + strings, plus some. */ + msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) + + (ruser ? strlen(ruser) : 0) + + (rhost ? strlen(rhost) : 0) + + strlen(lhost) + + strlen(luser) + + strlen(d) + + 1024)); + *msg = 0; + + if (!rversion || !*rversion) + { + sprintf (msg, + _("Warning:\n\n" + "The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%s\". Launch it now?"), + d); + } + else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name)) + { + /* Warn that the two processes are running as different users. + */ + sprintf(msg, + _("Warning:\n\n" + "%s is running as user \"%s\" on host \"%s\".\n" + "But the xscreensaver managing display \"%s\"\n" + "is running as user \"%s\" on host \"%s\".\n" + "\n" + "Since they are different users, they won't be reading/writing\n" + "the same ~/.xscreensaver file, so %s isn't\n" + "going to work right.\n" + "\n" + "You should either re-run %s as \"%s\", or re-run\n" + "xscreensaver as \"%s\".\n" + "\n" + "Restart the xscreensaver daemon now?\n"), + progname, luser, lhost, + d, + (ruser ? ruser : "???"), (rhost ? rhost : "???"), + progname, + progname, (ruser ? ruser : "???"), + luser); + } + else if (rhost && *rhost && !!strcmp (rhost, lhost)) + { + /* Warn that the two processes are running on different hosts. + */ + sprintf (msg, + _("Warning:\n\n" + "%s is running as user \"%s\" on host \"%s\".\n" + "But the xscreensaver managing display \"%s\"\n" + "is running as user \"%s\" on host \"%s\".\n" + "\n" + "If those two machines don't share a file system (that is,\n" + "if they don't see the same ~%s/.xscreensaver file) then\n" + "%s won't work right.\n" + "\n" + "Restart the daemon on \"%s\" as \"%s\" now?\n"), + progname, luser, lhost, + d, + (ruser ? ruser : "???"), (rhost ? rhost : "???"), + luser, + progname, + lhost, luser); + } + else if (!!strcmp (rversion, s->short_version)) + { + /* Warn that the version numbers don't match. + */ + sprintf (msg, + _("Warning:\n\n" + "This is %s version %s.\n" + "But the xscreensaver managing display \"%s\"\n" + "is version %s. This could cause problems.\n" + "\n" + "Restart the xscreensaver daemon now?\n"), + progname, s->short_version, + d, + rversion); + } + + + if (*msg) + warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1); + + if (rversion) free (rversion); + if (ruser) free (ruser); + if (rhost) free (rhost); + free (msg); + msg = 0; + + /* Note: since these dialogs are not modal, they will stack up. + So we do this check *after* popping up the "xscreensaver is not + running" dialog so that these are on top. Good enough. + */ + + if (gnome_screensaver_active_p ()) + warning_dialog (s->toplevel_widget, + _("Warning:\n\n" + "The GNOME screensaver daemon appears to be running.\n" + "It must be stopped for XScreenSaver to work properly.\n" + "\n" + "Stop the GNOME screen saver daemon now?\n"), + D_GNOME, 1); + + if (kde_screensaver_active_p ()) + warning_dialog (s->toplevel_widget, + _("Warning:\n\n" + "The KDE screen saver daemon appears to be running.\n" + "It must be stopped for XScreenSaver to work properly.\n" + "\n" + "Stop the KDE screen saver daemon now?\n"), + D_KDE, 1); +} + + +/* We use this error handler so that X errors are preceeded by the name + of the program that generated them. + */ +static int +demo_ehandler (Display *dpy, XErrorEvent *error) +{ + state *s = global_state_kludge; /* I hate C so much... */ + fprintf (stderr, "\nX error in %s:\n", blurb()); + XmuPrintDefaultErrorMessage (dpy, error, stderr); + kill_preview_subproc (s, False); + exit (-1); + return 0; +} + + +/* We use this error handler so that Gtk/Gdk errors are preceeded by the name + of the program that generated them; and also that we can ignore one + particular bogus error message that Gdk madly spews. + */ +static void +g_log_handler (const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data) +{ + /* Ignore the message "Got event for unknown window: 0x...". + Apparently some events are coming in for the xscreensaver window + (presumably reply events related to the ClientMessage) and Gdk + feels the need to complain about them. So, just suppress any + messages that look like that one. + */ + if (strstr (message, "unknown window")) + return; + + fprintf (stderr, "%s: %s-%s: %s%s", blurb(), + (log_domain ? log_domain : progclass), + (log_level == G_LOG_LEVEL_ERROR ? "error" : + log_level == G_LOG_LEVEL_CRITICAL ? "critical" : + log_level == G_LOG_LEVEL_WARNING ? "warning" : + log_level == G_LOG_LEVEL_MESSAGE ? "message" : + log_level == G_LOG_LEVEL_INFO ? "info" : + log_level == G_LOG_LEVEL_DEBUG ? "debug" : "???"), + message, + ((!*message || message[strlen(message)-1] != '\n') + ? "\n" : "")); +} + + +#ifdef __GNUC__ + __extension__ /* shut up about "string length is greater than the length + ISO C89 compilers are required to support" when including + the .ad file... */ +#endif + +STFU +static char *defaults[] = { +#include "XScreenSaver_ad.h" + 0 +}; + +#if 0 +#ifdef HAVE_CRAPPLET +static struct poptOption crapplet_options[] = { + {NULL, '\0', 0, NULL, 0} +}; +#endif /* HAVE_CRAPPLET */ +#endif /* 0 */ + +const char *usage = "[--display dpy] [--prefs | --settings]" +# ifdef HAVE_CRAPPLET + " [--crapplet]" +# endif + "\n\t\t [--debug] [--sync] [--no-xshm] [--configdir dir]"; + +static void +map_popup_window_cb (GtkWidget *w, gpointer user_data) +{ + state *s = (state *) user_data; + Boolean oi = s->initializing_p; +#ifndef HAVE_GTK2 + GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc")); +#endif + s->initializing_p = True; +#ifndef HAVE_GTK2 + eschew_gtk_lossage (label); +#endif + s->initializing_p = oi; +} + + +#if 0 +static void +print_widget_tree (GtkWidget *w, int depth) +{ + int i; + for (i = 0; i < depth; i++) + fprintf (stderr, " "); + fprintf (stderr, "%s\n", gtk_widget_get_name (w)); + + if (GTK_IS_LIST (w)) + { + for (i = 0; i < depth+1; i++) + fprintf (stderr, " "); + fprintf (stderr, "...list kids...\n"); + } + else if (GTK_IS_CONTAINER (w)) + { + GList *kids = gtk_container_children (GTK_CONTAINER (w)); + while (kids) + { + print_widget_tree (GTK_WIDGET (kids->data), depth+1); + kids = kids->next; + } + } +} +#endif /* 0 */ + +static int +delayed_scroll_kludge (gpointer data) +{ + state *s = (state *) data; + GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list")); + ensure_selected_item_visible (w); + + /* Oh, this is just fucking lovely, too. */ + w = GTK_WIDGET (name_to_widget (s, "preview")); + gtk_widget_hide (w); + gtk_widget_show (w); + + return FALSE; /* do not re-execute timer */ +} + +#ifdef HAVE_GTK2 + +GtkWidget * +create_xscreensaver_demo (void) +{ + GtkWidget *nb; + + nb = name_to_widget (global_state_kludge, "preview_notebook"); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE); + + return name_to_widget (global_state_kludge, "xscreensaver_demo"); +} + +GtkWidget * +create_xscreensaver_settings_dialog (void) +{ + GtkWidget *w, *box; + + box = name_to_widget (global_state_kludge, "dialog_action_area"); + + w = name_to_widget (global_state_kludge, "adv_button"); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); + + w = name_to_widget (global_state_kludge, "std_button"); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); + + return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog"); +} + +#endif /* HAVE_GTK2 */ + +int +main (int argc, char **argv) +{ + XtAppContext app; + state S, *s; + saver_preferences *p; + Bool prefs_p = False; + Bool settings_p = False; + int i; + Display *dpy; + Widget toplevel_shell; + char *real_progname = argv[0]; + char *window_title; + char *geom = 0; + Bool crapplet_p = False; + char *str; + +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + textdomain (GETTEXT_PACKAGE); + +# ifdef HAVE_GTK2 + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +# else /* !HAVE_GTK2 */ + if (!setlocale (LC_ALL, "")) + fprintf (stderr, "%s: locale not supported by C library\n", real_progname); +# endif /* !HAVE_GTK2 */ + +#endif /* ENABLE_NLS */ + + str = strrchr (real_progname, '/'); + if (str) real_progname = str+1; + + s = &S; + memset (s, 0, sizeof(*s)); + s->initializing_p = True; + p = &s->prefs; + + global_state_kludge = s; /* I hate C so much... */ + + progname = real_progname; + + s->short_version = (char *) malloc (5); + memcpy (s->short_version, screensaver_id + 17, 4); + s->short_version [4] = 0; + + + /* Register our error message logger for every ``log domain'' known. + There's no way to do this globally, so I grepped the Gtk/Gdk sources + for all of the domains that seem to be in use. + */ + { + const char * const domains[] = { 0, + "Gtk", "Gdk", "GLib", "GModule", + "GThread", "Gnome", "GnomeUI" }; + for (i = 0; i < countof(domains); i++) + g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0); + } + +#ifdef DEFAULT_ICONDIR /* from -D on compile line */ +# ifndef HAVE_GTK2 + { + const char *dir = DEFAULT_ICONDIR; + if (*dir) add_pixmap_directory (dir); + } +# endif /* !HAVE_GTK2 */ +#endif /* DEFAULT_ICONDIR */ + + /* This is gross, but Gtk understands --display and not -display... + */ + for (i = 1; i < argc; i++) + if (argv[i][0] && argv[i][1] && + !strncmp(argv[i], "-display", strlen(argv[i]))) + argv[i] = "--display"; + + + /* We need to parse this arg really early... Sigh. */ + for (i = 1; i < argc; i++) + { + if (argv[i] && + (!strcmp(argv[i], "--crapplet") || + !strcmp(argv[i], "--capplet"))) + { +# if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2) + int j; + crapplet_p = True; + for (j = i; j < argc; j++) /* remove it from the list */ + argv[j] = argv[j+1]; + argc--; +# else /* !HAVE_CRAPPLET && !HAVE_GTK2 */ + fprintf (stderr, "%s: not compiled with --crapplet support\n", + real_progname); + fprintf (stderr, "%s: %s\n", real_progname, usage); + exit (1); +# endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */ + } + else if (argv[i] && + (!strcmp(argv[i], "--debug") || + !strcmp(argv[i], "-debug") || + !strcmp(argv[i], "-d"))) + { + int j; + s->debug_p = True; + for (j = i; j < argc; j++) /* remove it from the list */ + argv[j] = argv[j+1]; + argc--; + i--; + } + else if (argv[i] && + argc > i+1 && + *argv[i+1] && + (!strcmp(argv[i], "-geometry") || + !strcmp(argv[i], "-geom") || + !strcmp(argv[i], "-geo") || + !strcmp(argv[i], "-g"))) + { + int j; + geom = argv[i+1]; + for (j = i; j < argc; j++) /* remove them from the list */ + argv[j] = argv[j+2]; + argc -= 2; + i -= 2; + } + else if (argv[i] && + argc > i+1 && + *argv[i+1] && + (!strcmp(argv[i], "--configdir"))) + { + int j; + struct stat st; + hack_configuration_path = argv[i+1]; + for (j = i; j < argc; j++) /* remove them from the list */ + argv[j] = argv[j+2]; + argc -= 2; + i -= 2; + + if (0 != stat (hack_configuration_path, &st)) + { + char buf[255]; + sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path); + perror (buf); + exit (1); + } + else if (!S_ISDIR (st.st_mode)) + { + fprintf (stderr, "%s: not a directory: %s\n", + blurb(), hack_configuration_path); + exit (1); + } + } + } + + + if (s->debug_p) + fprintf (stderr, "%s: using config directory \"%s\"\n", + progname, hack_configuration_path); + + + /* Let Gtk open the X connection, then initialize Xt to use that + same connection. Doctor Frankenstein would be proud. + */ +# ifdef HAVE_CRAPPLET + if (crapplet_p) + { + GnomeClient *client; + GnomeClientFlags flags = 0; + + int init_results = gnome_capplet_init ("screensaver-properties", + s->short_version, + argc, argv, NULL, 0, NULL); + /* init_results is: + 0 upon successful initialization; + 1 if --init-session-settings was passed on the cmdline; + 2 if --ignore was passed on the cmdline; + -1 on error. + + So the 1 signifies just to init the settings, and quit, basically. + (Meaning launch the xscreensaver daemon.) + */ + + if (init_results < 0) + { +# if 0 + g_error ("An initialization error occurred while " + "starting xscreensaver-capplet.\n"); +# else /* !0 */ + fprintf (stderr, "%s: gnome_capplet_init failed: %d\n", + real_progname, init_results); + exit (1); +# endif /* !0 */ + } + + client = gnome_master_client (); + + if (client) + flags = gnome_client_get_flags (client); + + if (flags & GNOME_CLIENT_IS_CONNECTED) + { + int token = + gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES", + gnome_client_get_id (client)); + if (token) + { + char *session_args[20]; + int i = 0; + session_args[i++] = real_progname; + session_args[i++] = "--capplet"; + session_args[i++] = "--init-session-settings"; + session_args[i] = 0; + gnome_client_set_priority (client, 20); + gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY); + gnome_client_set_restart_command (client, i, session_args); + } + else + { + gnome_client_set_restart_style (client, GNOME_RESTART_NEVER); + } + + gnome_client_flush (client); + } + + if (init_results == 1) + { + system ("xscreensaver -nosplash &"); + return 0; + } + + } + else +# endif /* HAVE_CRAPPLET */ + { + gtk_init (&argc, &argv); + } + + + /* We must read exactly the same resources as xscreensaver. + That means we must have both the same progclass *and* progname, + at least as far as the resource database is concerned. So, + put "xscreensaver" in argv[0] while initializing Xt. + */ + argv[0] = "xscreensaver"; + progname = argv[0]; + + + /* Teach Xt to use the Display that Gtk/Gdk have already opened. + */ + XtToolkitInitialize (); + app = XtCreateApplicationContext (); + dpy = GDK_DISPLAY(); + XtAppSetFallbackResources (app, defaults); + XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv); + toplevel_shell = XtAppCreateShell (progname, progclass, + applicationShellWidgetClass, + dpy, 0, 0); + + dpy = XtDisplay (toplevel_shell); + db = XtDatabase (dpy); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + XSetErrorHandler (demo_ehandler); + + /* Let's just ignore these. They seem to confuse Irix Gtk... */ + signal (SIGPIPE, SIG_IGN); + + /* After doing Xt-style command-line processing, complain about any + unrecognized command-line arguments. + */ + for (i = 1; i < argc; i++) + { + char *str = argv[i]; + if (str[0] == '-' && str[1] == '-') + str++; + if (!strcmp (str, "-prefs")) + prefs_p = True; + else if (!strcmp (str, "-settings")) + settings_p = True; + else if (crapplet_p) + /* There are lots of random args that we don't care about when we're + started as a crapplet, so just ignore unknown args in that case. */ + ; + else + { + fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, + argv[i]); + fprintf (stderr, "%s: %s\n", real_progname, usage); + exit (1); + } + } + + /* Load the init file, which may end up consulting the X resource database + and the site-wide app-defaults file. Note that at this point, it's + important that `progname' be "xscreensaver", rather than whatever + was in argv[0]. + */ + p->db = db; + s->nscreens = screen_count (dpy); + + hack_environment (s); /* must be before initialize_sort_map() */ + + load_init_file (dpy, p); + initialize_sort_map (s); + + /* Now that Xt has been initialized, and the resources have been read, + we can set our `progname' variable to something more in line with + reality. + */ + progname = real_progname; + + +#if 0 + /* Print out all the resources we read. */ + { + XrmName name = { 0 }; + XrmClass class = { 0 }; + int count = 0; + XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper, + (POINTER) &count); + } +#endif + + + /* Intern the atoms that xscreensaver_command() needs. + */ + XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False); + XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False); + XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False); + XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False); + XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False); + XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False); + XA_SELECT = XInternAtom (dpy, "SELECT", False); + XA_DEMO = XInternAtom (dpy, "DEMO", False); + XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False); + XA_BLANK = XInternAtom (dpy, "BLANK", False); + XA_LOCK = XInternAtom (dpy, "LOCK", False); + XA_EXIT = XInternAtom (dpy, "EXIT", False); + XA_RESTART = XInternAtom (dpy, "RESTART", False); + + + /* Create the window and all its widgets. + */ + s->base_widget = create_xscreensaver_demo (); + s->popup_widget = create_xscreensaver_settings_dialog (); + s->toplevel_widget = s->base_widget; + + + /* Set the main window's title. */ + { + char *base_title = _("Screensaver Preferences"); + char *v = (char *) strdup(strchr(screensaver_id, ' ')); + char *s1, *s2, *s3, *s4; + s1 = (char *) strchr(v, ' '); s1++; + s2 = (char *) strchr(s1, ' '); + s3 = (char *) strchr(v, '('); s3++; + s4 = (char *) strchr(s3, ')'); + *s2 = 0; + *s4 = 0; + + window_title = (char *) malloc (strlen (base_title) + + strlen (progclass) + + strlen (s1) + strlen (s3) + + 100); + sprintf (window_title, "%s (%s %s, %s)", base_title, progclass, s1, s3); + gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title); + gtk_window_set_title (GTK_WINDOW (s->popup_widget), window_title); + free (v); + } + + /* Adjust the (invisible) notebooks on the popup dialog... */ + { + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button")); + int page = 0; + +# ifdef HAVE_XML + gtk_widget_hide (std); +# else /* !HAVE_XML */ + /* Make the advanced page be the only one available. */ + gtk_widget_set_sensitive (std, False); + std = GTK_WIDGET (name_to_widget (s, "adv_button")); + gtk_widget_hide (std); + std = GTK_WIDGET (name_to_widget (s, "reset_button")); + gtk_widget_hide (std); + page = 1; +# endif /* !HAVE_XML */ + + gtk_notebook_set_page (notebook, page); + gtk_notebook_set_show_tabs (notebook, False); + } + + /* Various other widget initializations... + */ + gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event", + GTK_SIGNAL_FUNC (wm_toplevel_close_cb), + (gpointer) s); + gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event", + GTK_SIGNAL_FUNC (wm_popup_close_cb), + (gpointer) s); + + populate_hack_list (s); + populate_prefs_page (s); + sensitize_demo_widgets (s, False); + fix_text_entry_sizes (s); + scroll_to_current_hack (s); + + gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")), + "map", GTK_SIGNAL_FUNC(map_popup_window_cb), + (gpointer) s); + +#ifndef HAVE_GTK2 + gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")), + "map", GTK_SIGNAL_FUNC(map_prev_button_cb), + (gpointer) s); + gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")), + "map", GTK_SIGNAL_FUNC(map_next_button_cb), + (gpointer) s); +#endif /* !HAVE_GTK2 */ + + /* Hook up callbacks to the items on the mode menu. */ + { + GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); + GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt)); + GList *kids = gtk_container_children (GTK_CONTAINER (menu)); + int i; + for (i = 0; kids; kids = kids->next, i++) + { + gtk_signal_connect (GTK_OBJECT (kids->data), "activate", + GTK_SIGNAL_FUNC (mode_menu_item_cb), + (gpointer) s); + + /* The "random-same" mode menu item does not appear unless + there are multple screens. + */ + if (s->nscreens <= 1 && + mode_menu_order[i] == RANDOM_HACKS_SAME) + gtk_widget_hide (GTK_WIDGET (kids->data)); + } + + if (s->nscreens <= 1) /* recompute option-menu size */ + { + gtk_widget_unrealize (GTK_WIDGET (menu)); + gtk_widget_realize (GTK_WIDGET (menu)); + } + } + + + /* Handle the -prefs command-line argument. */ + if (prefs_p) + { + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "notebook")); + gtk_notebook_set_page (notebook, 1); + } + +# ifdef HAVE_CRAPPLET + if (crapplet_p) + { + GtkWidget *capplet; + GtkWidget *outer_vbox; + + gtk_widget_hide (s->toplevel_widget); + + capplet = capplet_widget_new (); + + /* Make there be a "Close" button instead of "OK" and "Cancel" */ +# ifdef HAVE_CRAPPLET_IMMEDIATE + capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet)); +# endif /* HAVE_CRAPPLET_IMMEDIATE */ + /* In crapplet-mode, take off the menubar. */ + gtk_widget_hide (name_to_widget (s, "menubar")); + + /* Reparent our top-level container to be a child of the capplet + window. + */ + outer_vbox = GTK_BIN (s->toplevel_widget)->child; + gtk_widget_ref (outer_vbox); + gtk_container_remove (GTK_CONTAINER (s->toplevel_widget), + outer_vbox); + STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING); + gtk_container_add (GTK_CONTAINER (capplet), outer_vbox); + + /* Find the window above us, and set the title and close handler. */ + { + GtkWidget *window = capplet; + while (window && !GTK_IS_WINDOW (window)) + window = window->parent; + if (window) + { + gtk_window_set_title (GTK_WINDOW (window), window_title); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (wm_toplevel_close_cb), + (gpointer) s); + } + } + + s->toplevel_widget = capplet; + } +# endif /* HAVE_CRAPPLET */ + + + /* The Gnome folks hate the menubar. I think it's important to have access + to the commands on the File menu (Restart Daemon, etc.) and to the + About and Documentation commands on the Help menu. + */ +#if 0 +#ifdef HAVE_GTK2 + gtk_widget_hide (name_to_widget (s, "menubar")); +#endif +#endif + + free (window_title); + window_title = 0; + +#ifdef HAVE_GTK2 + /* After picking the default size, allow -geometry to override it. */ + if (geom) + gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom); +#endif + + gtk_widget_show (s->toplevel_widget); + init_icon (GTK_WIDGET (s->toplevel_widget)->window); /* after `show' */ + fix_preview_visual (s); + + /* Realize page zero, so that we can diddle the scrollbar when the + user tabs back to it -- otherwise, the current hack isn't scrolled + to the first time they tab back there, when started with "-prefs". + (Though it is if they then tab away, and back again.) + + #### Bah! This doesn't work. Gtk eats my ass! Someone who + #### understands this crap, explain to me how to make this work. + */ + gtk_widget_realize (name_to_widget (s, "demos_table")); + + + gtk_timeout_add (60 * 1000, check_blanked_timer, s); + + + /* Handle the --settings command-line argument. */ + if (settings_p) + gtk_timeout_add (500, settings_timer, 0); + + + /* Issue any warnings about the running xscreensaver daemon. */ + if (! s->debug_p) + the_network_is_not_the_computer (s); + + + /* Run the Gtk event loop, and not the Xt event loop. This means that + if there were Xt timers or fds registered, they would never get serviced, + and if there were any Xt widgets, they would never have events delivered. + Fortunately, we're using Gtk for all of the UI, and only initialized + Xt so that we could process the command line and use the X resource + manager. + */ + s->initializing_p = False; + + /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds + after we start up. Otherwise, it always appears scrolled to the top + when in crapplet-mode. */ + gtk_timeout_add (500, delayed_scroll_kludge, s); + + +#if 1 + /* Load every configurator in turn, to scan them for errors all at once. */ + if (s->debug_p) + { + int i; + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + conf_data *d = load_configurator (hack->command, s->debug_p); + if (d) free_conf_data (d); + } + } +#endif + + +# ifdef HAVE_CRAPPLET + if (crapplet_p) + capplet_gtk_main (); + else +# endif /* HAVE_CRAPPLET */ + gtk_main (); + + kill_preview_subproc (s, False); + exit (0); +} + +#endif /* HAVE_GTK -- whole file */ diff --git a/driver/demo-Xm-widgets.c b/driver/demo-Xm-widgets.c new file mode 100644 index 00000000..cbe33934 --- /dev/null +++ b/driver/demo-Xm-widgets.c @@ -0,0 +1,907 @@ +/* demo-Xm.c --- implements the interactive demo-mode and options dialogs. + * xscreensaver, Copyright (c) 1999, 2003 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include /* just for debug info */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XMCOMBOBOX /* a Motif 2.0 widget */ +# include +# ifndef XmNtextField /* Lesstif 0.89.4 bug */ +# undef HAVE_XMCOMBOBOX +# endif +#endif /* HAVE_XMCOMBOBOX */ + +#include +#include + + + +const char *visual_menu[] = { + "Any", "Best", "Default", "Default-N", "GL", "TrueColor", "PseudoColor", + "StaticGray", "GrayScale", "DirectColor", "Color", "Gray", "Mono", 0 +}; + + + +static Widget create_demos_page (Widget parent); +static Widget create_options_page (Widget parent); + +static void +tab_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + Widget parent = XtParent(button); + Widget tabber = XtNameToWidget (parent, "*folder"); + Widget this_tab = (Widget) client_data; + Widget *kids = 0; + Cardinal nkids = 0; + if (!tabber) abort(); + + XtVaGetValues (tabber, XmNnumChildren, &nkids, XmNchildren, &kids, NULL); + if (!kids) abort(); + if (nkids > 0) + XtUnmanageChildren (kids, nkids); + + XtManageChild (this_tab); +} + + +Widget +create_xscreensaver_demo (Widget parent) +{ + /* MainWindow + Form + Menubar + DemoTab + OptionsTab + HR + Tabber + (demo page) + (options page) + */ + + Widget mainw, form, menubar; + Widget demo_tab, options_tab, hr, tabber, demos, options; + Arg av[100]; + int ac = 0; + + mainw = XmCreateMainWindow (parent, "demoForm", av, ac); + form = XmCreateForm (mainw, "form", av, ac); + menubar = XmCreateSimpleMenuBar (form, "menubar", av, ac); + XtVaSetValues (menubar, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + NULL); + + { + Widget menu = 0, item = 0; + char *menus[] = { + "*file", "blank", "lock", "kill", "restart", "-", "exit", + "*edit", "cut", "copy", "paste", + "*help", "about", "docMenu" }; + int i; + for (i = 0; i < sizeof(menus)/sizeof(*menus); i++) + { + ac = 0; + if (menus[i][0] == '-') + item = XmCreateSeparatorGadget (menu, "separator", av, ac); + else if (menus[i][0] != '*') + item = XmCreatePushButtonGadget (menu, menus[i], av, ac); + else + { + menu = XmCreatePulldownMenu (parent, menus[i]+1, av, ac); + XtSetArg (av [ac], XmNsubMenuId, menu); ac++; + item = XmCreateCascadeButtonGadget (menubar, menus[i]+1, av, ac); + + if (!strcmp (menus[i]+1, "help")) + XtVaSetValues(menubar, XmNmenuHelpWidget, item, NULL); + } + XtManageChild (item); + } + ac = 0; + } + + demo_tab = XmCreatePushButtonGadget (form, "demoTab", av, ac); + XtVaSetValues (demo_tab, + XmNleftAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menubar, + NULL); + + options_tab = XmCreatePushButtonGadget (form, "optionsTab", av, ac); + XtVaSetValues (options_tab, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, demo_tab, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopWidget, demo_tab, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomWidget, demo_tab, + NULL); + + hr = XmCreateSeparatorGadget (form, "hr", av, ac); + XtVaSetValues (hr, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, demo_tab, + NULL); + + tabber = XmCreateForm (form, "folder", av, ac); + XtVaSetValues (tabber, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, hr, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + demos = create_demos_page (tabber); + options = create_options_page (tabber); + + XtAddCallback (demo_tab, XmNactivateCallback, tab_cb, demos); + XtAddCallback (options_tab, XmNactivateCallback, tab_cb, options); + + XtManageChild (demos); + XtManageChild (options); + + XtManageChild (demo_tab); + XtManageChild (options_tab); + XtManageChild (hr); + XtManageChild (menubar); + XtManageChild (tabber); + XtManageChild (form); + +#if 1 + XtUnmanageChild (options); + XtManageChild (demos); +#endif + + return mainw; +} + + +static Widget +create_demos_page (Widget parent) +{ + /* Form1 (horizontal) + Form2 (vertical) + Scroller + List + ButtonBox1 (vertical) + Button ("Down") + Button ("Up") + Form3 (vertical) + Frame + Label + TextArea (doc) + Label + Text ("Command Line") + Form4 (horizontal) + Checkbox ("Enabled") + Label ("Visual") + ComboBox + HR + ButtonBox2 (vertical) + Button ("Demo") + Button ("Documentation") + */ + Widget form1, form2, form3, form4; + Widget scroller, list, buttonbox1, down, up; + Widget frame, frame_label, doc, cmd_label, cmd_text, enabled, vis_label; + Widget combo; + Widget hr, buttonbox2, demo, man; + Arg av[100]; + int ac = 0; + int i; + + form1 = XmCreateForm (parent, "form1", av, ac); + form2 = XmCreateForm (form1, "form2", av, ac); + XtVaSetValues (form2, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + scroller = XmCreateScrolledWindow (form2, "scroller", av, ac); + XtVaSetValues (scroller, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + NULL); + list = XmCreateList (scroller, "list", av, ac); + + buttonbox1 = XmCreateForm (form2, "buttonbox1", av, ac); + XtVaSetValues (buttonbox1, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + XtVaSetValues (scroller, XmNbottomWidget, buttonbox1, NULL); + + down = XmCreatePushButton (buttonbox1, "down", av, ac); + XtVaSetValues (down, + XmNleftAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + up = XmCreatePushButton (buttonbox1, "up", av, ac); + XtVaSetValues (up, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, down, + XmNtopAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + form3 = XmCreateForm (form1, "form3", av, ac); + XtVaSetValues (form3, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, form2, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + frame = XmCreateFrame (form3, "frame", av, ac); + + ac = 0; + XtSetArg (av [ac], XmNchildType, XmFRAME_TITLE_CHILD); ac++; + frame_label = XmCreateLabelGadget (frame, "frameLabel", av, ac); + + ac = 0; + XtVaSetValues (frame, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + NULL); + + ac = 0; + XtSetArg (av [ac], XmNchildType, XmFRAME_WORKAREA_CHILD); ac++; + doc = XmCreateText (frame, "doc", av, ac); + + ac = 0; + XtVaSetValues (doc, + XmNeditable, FALSE, + XmNcursorPositionVisible, FALSE, + XmNwordWrap, TRUE, + XmNeditMode, XmMULTI_LINE_EDIT, + XmNshadowThickness, 0, + NULL); + + cmd_label = XmCreateLabelGadget (form3, "cmdLabel", av, ac); + XtVaSetValues (cmd_label, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + NULL); + XtVaSetValues (frame, XmNbottomWidget, cmd_label, NULL); + + cmd_text = XmCreateTextField (form3, "cmdText", av, ac); + XtVaSetValues (cmd_text, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + NULL); + XtVaSetValues (cmd_label, XmNbottomWidget, cmd_text, NULL); + + form4 = XmCreateForm (form3, "form4", av, ac); + XtVaSetValues (form4, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + NULL); + XtVaSetValues (cmd_text, XmNbottomWidget, form4, NULL); + + enabled = XmCreateToggleButtonGadget (form4, "enabled", av, ac); + XtVaSetValues (enabled, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + vis_label = XmCreateLabelGadget (form4, "visLabel", av, ac); + XtVaSetValues (vis_label, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, enabled, + XmNbottomAttachment, XmATTACH_FORM, + NULL); +#ifdef HAVE_XMCOMBOBOX + { + Widget list; + ac = 0; + XtSetArg (av [ac], XmNcomboBoxType, XmDROP_DOWN_COMBO_BOX); ac++; + combo = XmCreateComboBox (form4, "combo", av, ac); + for (i = 0; visual_menu[i]; i++) + { + XmString xs = XmStringCreate ((char *) visual_menu[i], + XmSTRING_DEFAULT_CHARSET); + XmComboBoxAddItem (combo, xs, 0, False); + XmStringFree (xs); + } + XtVaGetValues (combo, XmNlist, &list, NULL); + XtVaSetValues (list, XmNvisibleItemCount, i, NULL); + } +#else /* !HAVE_XMCOMBOBOX */ + { + Widget popup_menu = XmCreatePulldownMenu (parent, "menu", av, ac); + Widget kids[100]; + for (i = 0; visual_menu[i]; i++) + { + XmString xs = XmStringCreate ((char *) visual_menu[i], + XmSTRING_DEFAULT_CHARSET); + ac = 0; + XtSetArg (av [ac], XmNlabelString, xs); ac++; + kids[i] = XmCreatePushButtonGadget (popup_menu, "button", av, ac); + /* XtAddCallback (combo, XmNactivateCallback, visual_popup_cb, + combo); */ + XmStringFree (xs); + } + XtManageChildren (kids, i); + + ac = 0; + XtSetArg (av [ac], XmNsubMenuId, popup_menu); ac++; + combo = XmCreateOptionMenu (form4, "combo", av, ac); + ac = 0; + } +#endif /* !HAVE_XMCOMBOBOX */ + + XtVaSetValues (combo, + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, vis_label, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + hr = XmCreateSeparatorGadget (form3, "hr", av, ac); + XtVaSetValues (hr, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + NULL); + XtVaSetValues (form4, XmNbottomWidget, hr, NULL); + + buttonbox2 = XmCreateForm (form3, "buttonbox2", av, ac); + XtVaSetValues (buttonbox2, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + XtVaSetValues (hr, XmNbottomWidget, buttonbox2, NULL); + + demo = XmCreatePushButtonGadget (buttonbox2, "demo", av, ac); + XtVaSetValues (demo, + XmNleftAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + man = XmCreatePushButtonGadget (buttonbox2, "man", av, ac); + XtVaSetValues (man, + XmNrightAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + XtManageChild (demo); + XtManageChild (man); + XtManageChild (buttonbox2); + XtManageChild (hr); + + XtManageChild (combo); + XtManageChild (vis_label); + XtManageChild (enabled); + XtManageChild (form4); + + XtManageChild (cmd_text); + XtManageChild (cmd_label); + + XtManageChild (doc); + XtManageChild (frame_label); + XtManageChild (frame); + XtManageChild (form3); + + XtManageChild (up); + XtManageChild (down); + XtManageChild (buttonbox1); + + XtManageChild (list); + XtManageChild (scroller); + XtManageChild (form2); + + XtManageChild (form1); + + XtVaSetValues (form1, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + return form1; +} + + + +static Widget +create_options_page (Widget parent) +{ + /* This is what the layout is today: + + Form (horizontal) + Label ("Saver Timeout") + Label ("Cycle Timeout") + Label ("Fade Duration") + Label ("Fade Ticks") + Label ("Lock Timeout") + Label ("Password Timeout") + + Text (timeout) + Text (cycle) + Text (fade seconds) + Text (fade ticks) + Text (lock) + Text (passwd) + + Toggle ("Verbose") + Toggle ("Install Colormap") + Toggle ("Fade Colormap") + Toggle ("Unfade Colormap") + Toggle ("Require Password") + + HR + Button ("OK") + Button ("Cancel") + */ + + /* This is what it should be: + + Form (horizontal) + Form (vertical) ("column1") + Frame + Label ("Blanking and Locking") + Form + Label ("Blank After") + Label ("Cycle After") + Text ("Blank After") + Text ("Cycle After") + HR + Checkbox ("Require Password") + Label ("Lock After") + Text ("Lock After") + Frame + Label ("Image Manipulation") + Form + Checkbox ("Grab Desktop Images") + Checkbox ("Grab Video Frames") + Checkbox ("Choose Random Image") + Text (pathname) + Button ("Browse") + Frame + Label ("Diagnostics") + Form + Checkbox ("Verbose Diagnostics") + Checkbox ("Display Subprocess Errors") + Checkbox ("Display Splash Screen at Startup") + Form (vertical) ("column2") + Frame + Label ("Display Power Management") + Form + Checkbox ("Power Management Enabled") + Label ("Standby After") + Label ("Suspend After") + Label ("Off After") + Text ("Standby After") + Text ("Suspend After") + Text ("Off After") + Frame + Label ("Colormaps") + Form + Checkbox ("Install Colormap") + HR + Checkbox ("Fade To Black When Blanking") + Checkbox ("Fade From Black When Unblanking") + Label ("Fade Duration") + Text ("Fade Duration") + + timeoutLabel + cycleLabel + fadeSecondsLabel + fadeTicksLabel + lockLabel + passwdLabel + + timeoutText + cycleText + fadeSecondsText + fadeTicksText + lockText + passwdText + + verboseToggle + cmapToggle + fadeToggle + unfadeToggle + lockToggle + + separator + OK + Cancel + */ + + + + Arg av[64]; + int ac = 0; + Widget children[100]; + Widget timeout_label, cycle_label, fade_seconds_label, fade_ticks_label; + Widget lock_label, passwd_label, hr; + Widget preferences_form; + + Widget timeout_text, cycle_text, fade_text, fade_ticks_text; + Widget lock_timeout_text, passwd_timeout_text, verbose_toggle; + Widget install_cmap_toggle, fade_toggle, unfade_toggle; + Widget lock_toggle, prefs_done, prefs_cancel; + + ac = 0; + XtSetArg (av [ac], XmNdialogType, XmDIALOG_PROMPT); ac++; + + ac = 0; + XtSetArg (av [ac], XmNtopAttachment, XmATTACH_FORM); ac++; + XtSetArg (av [ac], XmNbottomAttachment, XmATTACH_FORM); ac++; + XtSetArg (av [ac], XmNleftAttachment, XmATTACH_FORM); ac++; + XtSetArg (av [ac], XmNrightAttachment, XmATTACH_FORM); ac++; + preferences_form = XmCreateForm (parent, "preferencesForm", av, ac); + XtManageChild (preferences_form); + + ac = 0; + + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++; + timeout_label = XmCreateLabelGadget (preferences_form, "timeoutLabel", + av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++; + cycle_label = XmCreateLabelGadget (preferences_form, "cycleLabel", + av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++; + fade_seconds_label = XmCreateLabelGadget (preferences_form, + "fadeSecondsLabel", av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++; + fade_ticks_label = XmCreateLabelGadget (preferences_form, "fadeTicksLabel", + av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++; + lock_label = XmCreateLabelGadget (preferences_form, "lockLabel", av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++; + passwd_label = XmCreateLabelGadget (preferences_form, "passwdLabel", av, ac); + ac = 0; + timeout_text = XmCreateTextField (preferences_form, "timeoutText", av, ac); + cycle_text = XmCreateTextField (preferences_form, "cycleText", av, ac); + fade_text = XmCreateTextField (preferences_form, "fadeSecondsText", av, ac); + fade_ticks_text = XmCreateTextField (preferences_form, "fadeTicksText", + av, ac); + lock_timeout_text = XmCreateTextField (preferences_form, "lockText", + av, ac); + passwd_timeout_text = XmCreateTextField (preferences_form, "passwdText", + av, ac); + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++; + verbose_toggle = XmCreateToggleButtonGadget (preferences_form, + "verboseToggle", av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++; + install_cmap_toggle = XmCreateToggleButtonGadget (preferences_form, + "cmapToggle", av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++; + fade_toggle = XmCreateToggleButtonGadget (preferences_form, "fadeToggle", + av, ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++; + unfade_toggle = XmCreateToggleButtonGadget (preferences_form, "unfadeToggle", + av,ac); + ac = 0; + XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++; + lock_toggle = XmCreateToggleButtonGadget (preferences_form, "lockToggle", + av, ac); + ac = 0; + hr = XmCreateSeparatorGadget (preferences_form, "separator", av, ac); + + prefs_done = XmCreatePushButtonGadget (preferences_form, "OK", av, ac); + prefs_cancel = XmCreatePushButtonGadget (preferences_form, "Cancel", av, ac); + + XtVaSetValues (timeout_label, + XmNtopAttachment, XmATTACH_FORM, + XmNtopOffset, 4, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomWidget, timeout_text, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 20, + XmNrightAttachment, XmATTACH_WIDGET, + XmNrightOffset, 4, + XmNrightWidget, timeout_text, + NULL); + + XtVaSetValues (cycle_label, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, cycle_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, cycle_text, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 20, + XmNrightAttachment, XmATTACH_WIDGET, + XmNrightOffset, 4, + XmNrightWidget, cycle_text, + NULL); + + XtVaSetValues (fade_seconds_label, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, fade_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, fade_text, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 20, + XmNrightAttachment, XmATTACH_WIDGET, + XmNrightOffset, 4, + XmNrightWidget, fade_text, + NULL); + + XtVaSetValues (fade_ticks_label, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, fade_ticks_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, fade_ticks_text, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 20, + XmNrightAttachment, XmATTACH_WIDGET, + XmNrightOffset, 4, + XmNrightWidget, fade_ticks_text, + NULL); + + XtVaSetValues (lock_label, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, lock_timeout_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, lock_timeout_text, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 19, + XmNrightAttachment, XmATTACH_WIDGET, + XmNrightOffset, 4, + XmNrightWidget, lock_timeout_text, + NULL); + + XtVaSetValues (passwd_label, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, passwd_timeout_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, passwd_timeout_text, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 14, + XmNrightAttachment, XmATTACH_WIDGET, + XmNrightOffset, 4, + XmNrightWidget, passwd_timeout_text, + NULL); + + XtVaSetValues (timeout_text, + XmNtopAttachment, XmATTACH_FORM, + XmNtopOffset, 4, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, 141, + NULL); + + XtVaSetValues (cycle_text, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopOffset, 2, + XmNtopWidget, timeout_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, timeout_text, + NULL); + + XtVaSetValues (fade_text, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopOffset, 2, + XmNtopWidget, cycle_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, cycle_text, + NULL); + + XtVaSetValues (fade_ticks_text, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopOffset, 2, + XmNtopWidget, fade_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, fade_text, + NULL); + + XtVaSetValues (lock_timeout_text, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopOffset, 2, + XmNtopWidget, fade_ticks_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, fade_ticks_text, + NULL); + + XtVaSetValues (passwd_timeout_text, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopOffset, 4, + XmNtopWidget, lock_timeout_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, lock_timeout_text, + NULL); + + XtVaSetValues (verbose_toggle, + XmNtopAttachment, XmATTACH_FORM, + XmNtopOffset, 4, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, timeout_text, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftOffset, 20, + XmNleftWidget, timeout_text, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, 20, + NULL); + + XtVaSetValues (install_cmap_toggle, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, cycle_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, cycle_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, verbose_toggle, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, 20, + NULL); + + XtVaSetValues (fade_toggle, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, fade_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, fade_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, install_cmap_toggle, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, 20, + NULL); + + XtVaSetValues (unfade_toggle, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, fade_ticks_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, fade_ticks_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, fade_toggle, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, 20, + NULL); + + XtVaSetValues (lock_toggle, + XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNtopOffset, 0, + XmNtopWidget, lock_timeout_text, + XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNbottomOffset, 0, + XmNbottomWidget, lock_timeout_text, + XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, + XmNleftOffset, 0, + XmNleftWidget, unfade_toggle, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, 20, + NULL); + + XtVaSetValues (hr, + XmNtopWidget, passwd_timeout_text, + XmNbottomAttachment, XmATTACH_FORM, + XmNbottomOffset, 4, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + NULL); + + XtVaSetValues (prefs_done, + XmNleftAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + XtVaSetValues (prefs_cancel, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + XtVaSetValues (hr, + XmNbottomAttachment, XmATTACH_WIDGET, + XmNbottomWidget, prefs_done, + NULL); + + ac = 0; + children[ac++] = timeout_label; + children[ac++] = cycle_label; + children[ac++] = fade_seconds_label; + children[ac++] = fade_ticks_label; + children[ac++] = lock_label; + children[ac++] = passwd_label; + children[ac++] = timeout_text; + children[ac++] = cycle_text; + children[ac++] = fade_text; + children[ac++] = fade_ticks_text; + children[ac++] = lock_timeout_text; + children[ac++] = passwd_timeout_text; + children[ac++] = verbose_toggle; + children[ac++] = install_cmap_toggle; + children[ac++] = fade_toggle; + children[ac++] = unfade_toggle; + children[ac++] = lock_toggle; + children[ac++] = hr; + + XtManageChildren(children, ac); + ac = 0; + + XtManageChild (prefs_done); + XtManageChild (prefs_cancel); + + XtManageChild (preferences_form); + + XtVaSetValues (preferences_form, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + NULL); + + return preferences_form; +} diff --git a/driver/demo-Xm.c b/driver/demo-Xm.c new file mode 100644 index 00000000..56d1aac5 --- /dev/null +++ b/driver/demo-Xm.c @@ -0,0 +1,1875 @@ +/* demo-Xm.c --- implements the interactive demo-mode and options dialogs. + * xscreensaver, Copyright (c) 1993-2003, 2005 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_MOTIF /* whole file */ + +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifndef VMS +# include /* for getpwuid() */ +#else /* VMS */ +# include "vms-pwd.h" +#endif /* VMS */ + +#ifdef HAVE_UNAME +# include /* for uname() */ +#endif /* HAVE_UNAME */ + +#include + +#include /* for CARD32 */ +#include /* for XA_INTEGER */ +#include +#include + +/* We don't actually use any widget internals, but these are included + so that gdb will have debug info for the widgets... */ +#include +#include + +#ifdef HAVE_XPM +# include +#endif /* HAVE_XPM */ + +#ifdef HAVE_XMU +# ifndef VMS +# include +# else /* VMS */ +# include +# endif +#else +# include "xmu.h" +#endif + + + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XMCOMBOBOX /* a Motif 2.0 widget */ +# include +# ifndef XmNtextField /* Lesstif 0.89.4 bug */ +# undef HAVE_XMCOMBOBOX +# endif +# if (XmVersion < 2001) /* Lesstif has two personalities these days */ +# undef HAVE_XMCOMBOBOX +# endif +#endif /* HAVE_XMCOMBOBOX */ + +#include "version.h" +#include "prefs.h" +#include "resources.h" /* for parse_time() */ +#include "visual.h" /* for has_writable_cells() */ +#include "remote.h" /* for xscreensaver_command() */ +#include "usleep.h" + +#include +#include +#include + +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + + +char *progname = 0; +char *progclass = "XScreenSaver"; +XrmDatabase db; + +typedef struct { + saver_preferences *a, *b; +} prefs_pair; + +static void *global_prefs_pair; /* I hate C so much... */ + +char *blurb (void) { return progname; } + +extern Widget create_xscreensaver_demo (Widget parent); +extern const char *visual_menu[]; + + +static char *short_version = 0; + +Atom XA_VROOT; +Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION; +Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO; +Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT; + + +static void populate_demo_window (Widget toplevel, + int which, prefs_pair *pair); +static void populate_prefs_page (Widget top, prefs_pair *pair); +static int apply_changes_and_save (Widget widget); +static int maybe_reload_init_file (Widget widget, prefs_pair *pair); +static void await_xscreensaver (Widget widget); + + +/* Some random utility functions + */ + +static Widget +name_to_widget (Widget widget, const char *name) +{ + Widget parent; + char name2[255]; + name2[0] = '*'; + strcpy (name2+1, name); + + while ((parent = XtParent (widget))) + widget = parent; + return XtNameToWidget (widget, name2); +} + + + +/* Why this behavior isn't automatic in *either* toolkit, I'll never know. + Takes a scroller, viewport, or list as an argument. + */ +static void +ensure_selected_item_visible (Widget list) +{ + int *pos_list = 0; + int pos_count = 0; + if (XmListGetSelectedPos (list, &pos_list, &pos_count) && pos_count > 0) + { + int top = -2; + int visible = 0; + XtVaGetValues (list, + XmNtopItemPosition, &top, + XmNvisibleItemCount, &visible, + NULL); + if (pos_list[0] >= top + visible) + { + int pos = pos_list[0] - visible + 1; + if (pos < 0) pos = 0; + XmListSetPos (list, pos); + } + else if (pos_list[0] < top) + { + XmListSetPos (list, pos_list[0]); + } + } + if (pos_list) + XtFree ((char *) pos_list); +} + + +static void +warning_dialog_dismiss_cb (Widget button, XtPointer client_data, + XtPointer user_data) +{ + Widget shell = (Widget) client_data; + XtDestroyWidget (shell); +} + + +static void +warning_dialog (Widget parent, const char *message, int center) +{ + char *msg = strdup (message); + char *head; + + Widget dialog = 0; + Widget label = 0; + Widget ok = 0; + int i = 0; + + Widget w; + Widget container; + XmString xmstr; + Arg av[10]; + int ac = 0; + + ac = 0; + dialog = XmCreateWarningDialog (parent, "warning", av, ac); + + w = XmMessageBoxGetChild (dialog, XmDIALOG_MESSAGE_LABEL); + if (w) XtUnmanageChild (w); + w = XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON); + if (w) XtUnmanageChild (w); + w = XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON); + if (w) XtUnmanageChild (w); + + ok = XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON); + + ac = 0; + XtSetArg (av[ac], XmNnumColumns, 1); ac++; + XtSetArg (av[ac], XmNorientation, XmVERTICAL); ac++; + XtSetArg (av[ac], XmNpacking, XmPACK_COLUMN); ac++; + XtSetArg (av[ac], XmNrowColumnType, XmWORK_AREA); ac++; + XtSetArg (av[ac], XmNspacing, 0); ac++; + container = XmCreateRowColumn (dialog, "container", av, ac); + + head = msg; + while (head) + { + char name[20]; + char *s = strchr (head, '\n'); + if (s) *s = 0; + + sprintf (name, "label%d", i++); + + xmstr = XmStringCreate (head, XmSTRING_DEFAULT_CHARSET); + ac = 0; + XtSetArg (av[ac], XmNlabelString, xmstr); ac++; + XtSetArg (av[ac], XmNmarginHeight, 0); ac++; + label = XmCreateLabelGadget (container, name, av, ac); + XtManageChild (label); + XmStringFree (xmstr); + + if (s) + head = s+1; + else + head = 0; + + center--; + } + + XtManageChild (container); + XtRealizeWidget (dialog); + XtManageChild (dialog); + + XtAddCallback (ok, XmNactivateCallback, warning_dialog_dismiss_cb, dialog); + + free (msg); +} + + +static void +run_cmd (Widget widget, Atom command, int arg) +{ + char *err = 0; + int status; + + apply_changes_and_save (widget); + status = xscreensaver_command (XtDisplay (widget), + command, arg, False, &err); + if (status < 0) + { + char buf [255]; + if (err) + sprintf (buf, "Error:\n\n%s", err); + else + strcpy (buf, "Unknown error!"); + warning_dialog (widget, buf, 100); + } + if (err) free (err); +} + + +static void +run_hack (Widget widget, int which, Bool report_errors_p) +{ + if (which < 0) return; + apply_changes_and_save (widget); + if (report_errors_p) + run_cmd (widget, XA_DEMO, which + 1); + else + { + char *s = 0; + xscreensaver_command (XtDisplay (widget), XA_DEMO, which + 1, False, &s); + if (s) free (s); + } +} + + + +/* Button callbacks + */ + +void +exit_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + apply_changes_and_save (XtParent (button)); + exit (0); +} + +#if 0 +static void +wm_close_cb (Widget widget, GdkEvent *event, XtPointer data) +{ + apply_changes_and_save (XtParent (button)); + exit (0); +} +#endif + +void +cut_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + /* #### */ + warning_dialog (XtParent (button), + "Error:\n\n" + "cut unimplemented\n", 1); +} + + +void +copy_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + /* #### */ + warning_dialog (XtParent (button), + "Error:\n\n" + "copy unimplemented\n", 1); +} + + +void +paste_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + /* #### */ + warning_dialog (XtParent (button), + "Error:\n\n" + "paste unimplemented\n", 1); +} + + +void +about_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + char buf [2048]; + char *s = strdup (screensaver_id + 4); + char *s2; + + s2 = strchr (s, ','); + *s2 = 0; + s2 += 2; + + sprintf (buf, "%s\n%s\n" + "\n" + "This is the Motif version of \"xscreensaver-demo\". The Motif\n" + "version is no longer maintained. Please use the GTK version\n" + "instead, which has many more features.\n" + "\n" + "For xscreensaver updates, check http://www.jwz.org/xscreensaver/", + s, s2); + free (s); + + warning_dialog (XtParent (button), buf, 100); +} + + +void +doc_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + prefs_pair *pair = (prefs_pair *) client_data; + + saver_preferences *p = pair->a; + char *help_command; + + if (!p->help_url || !*p->help_url) + { + warning_dialog (XtParent (button), + "Error:\n\n" + "No Help URL has been specified.\n", 100); + return; + } + + help_command = (char *) malloc (strlen (p->load_url_command) + + (strlen (p->help_url) * 4) + 20); + strcpy (help_command, "( "); + sprintf (help_command + strlen(help_command), + p->load_url_command, + p->help_url, p->help_url, p->help_url, p->help_url); + strcat (help_command, " ) &"); + system (help_command); + free (help_command); +} + + +void +activate_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + run_cmd (XtParent (button), XA_ACTIVATE, 0); +} + + +void +lock_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + run_cmd (XtParent (button), XA_LOCK, 0); +} + + +void +kill_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + run_cmd (XtParent (button), XA_EXIT, 0); +} + + +void +restart_menu_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ +#if 0 + run_cmd (XtParent (button), XA_RESTART, 0); +#else + button = XtParent (button); + apply_changes_and_save (button); + xscreensaver_command (XtDisplay (button), XA_EXIT, 0, False, NULL); + sleep (1); + system ("xscreensaver -nosplash &"); +#endif + + await_xscreensaver (button); +} + +static void +await_xscreensaver (Widget widget) +{ + int countdown = 5; + + Display *dpy = XtDisplay (widget); + char *rversion = 0; + + while (!rversion && (--countdown > 0)) + { + /* Check for the version of the running xscreensaver... */ + server_xscreensaver_version (dpy, &rversion, 0, 0); + + /* If it's not there yet, wait a second... */ + sleep (1); + } + + if (rversion) + { + /* Got it. */ + free (rversion); + } + else + { + /* Timed out, no screensaver running. */ + + char buf [1024]; + Bool root_p = (geteuid () == 0); + + strcpy (buf, + "Error:\n\n" + "The xscreensaver daemon did not start up properly.\n" + "\n"); + + if (root_p) +# ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than + the length ISO C89 compilers are required to + support" in the following expression... */ +# endif + strcat (buf, + "You are running as root. This usually means that xscreensaver\n" + "was unable to contact your X server because access control is\n" + "turned on. Try running this command:\n" + "\n" + " xhost +localhost\n" + "\n" + "and then selecting `File / Restart Daemon'.\n" + "\n" + "Note that turning off access control will allow anyone logged\n" + "on to this machine to access your screen, which might be\n" + "considered a security problem. Please read the xscreensaver\n" + "manual and FAQ for more information.\n" + "\n" + "You shouldn't run X as root. Instead, you should log in as a\n" + "normal user, and `su' as necessary."); + else + strcat (buf, "Please check your $PATH and permissions."); + + warning_dialog (XtParent (widget), buf, 1); + } +} + + +static int _selected_hack_number = -1; + +static int +selected_hack_number (Widget toplevel) +{ + return _selected_hack_number; +} + + +static int +demo_write_init_file (Widget widget, saver_preferences *p) +{ + if (!write_init_file (XtDisplay (widget), p, short_version, False)) + return 0; + else + { + const char *f = init_file_name(); + if (!f || !*f) + warning_dialog (widget, + "Error:\n\nCouldn't determine init file name!\n", + 100); + else + { + char *b = (char *) malloc (strlen(f) + 1024); + sprintf (b, "Error:\n\nCouldn't write %s\n", f); + warning_dialog (widget, b, 100); + free (b); + } + return -1; + } +} + + +static int +apply_changes_and_save (Widget widget) +{ + prefs_pair *pair = global_prefs_pair; + saver_preferences *p = pair->a; + Widget list_widget = name_to_widget (widget, "list"); + int which = selected_hack_number (widget); + + Widget cmd = name_to_widget (widget, "cmdText"); + Widget enabled = name_to_widget (widget, "enabled"); + + Widget vis = name_to_widget (widget, "combo"); +# ifdef HAVE_XMCOMBOBOX + Widget text = 0; +# else /* !HAVE_XMCOMBOBOX */ + Widget menu = 0, *kids = 0, selected_item = 0; + Cardinal nkids = 0; + int i = 0; +# endif /* !HAVE_XMCOMBOBOX */ + + Bool enabled_p = False; + const char *visual = 0; + const char *command = 0; + + char c; + unsigned long id; + + if (which < 0) return -1; + +# ifdef HAVE_XMCOMBOBOX + XtVaGetValues (vis, XmNtextField, &text, NULL); + if (!text) + /* If we can't get at the text field of this combo box, we're screwed. */ + abort(); + XtVaGetValues (text, XmNvalue, &visual, NULL); + +# else /* !HAVE_XMCOMBOBOX */ + XtVaGetValues (vis, XmNsubMenuId, &menu, NULL); + XtVaGetValues (menu, XmNnumChildren, &nkids, XmNchildren, &kids, NULL); + XtVaGetValues (menu, XmNmenuHistory, &selected_item, NULL); + if (selected_item) + for (i = 0; i < nkids; i++) + if (kids[i] == selected_item) + break; + + visual = visual_menu[i]; +# endif /* !HAVE_XMCOMBOBOX */ + + XtVaGetValues (enabled, XmNset, &enabled_p, NULL); + XtVaGetValues (cmd, XtNvalue, &command, NULL); + + if (maybe_reload_init_file (widget, pair) != 0) + return 1; + + /* Sanity-check and canonicalize whatever the user typed into the combo box. + */ + if (!strcasecmp (visual, "")) visual = ""; + else if (!strcasecmp (visual, "any")) visual = ""; + else if (!strcasecmp (visual, "default")) visual = "Default"; + else if (!strcasecmp (visual, "default-n")) visual = "Default-N"; + else if (!strcasecmp (visual, "default-i")) visual = "Default-I"; + else if (!strcasecmp (visual, "best")) visual = "Best"; + else if (!strcasecmp (visual, "mono")) visual = "Mono"; + else if (!strcasecmp (visual, "monochrome")) visual = "Mono"; + else if (!strcasecmp (visual, "gray")) visual = "Gray"; + else if (!strcasecmp (visual, "grey")) visual = "Gray"; + else if (!strcasecmp (visual, "color")) visual = "Color"; + else if (!strcasecmp (visual, "gl")) visual = "GL"; + else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray"; + else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor"; + else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor"; + else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor"; + else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor"; + else if (1 == sscanf (visual, " %lu %c", &id, &c)) ; + else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; + else + { + XBell (XtDisplay (widget), 0); /* unparsable */ + visual = ""; + /* #### gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");*/ + } + + ensure_selected_item_visible (list_widget); + + if (!p->screenhacks[which]->visual) + p->screenhacks[which]->visual = strdup (""); + if (!p->screenhacks[which]->command) + p->screenhacks[which]->command = strdup (""); + + if (p->screenhacks[which]->enabled_p != enabled_p || + !!strcasecmp (p->screenhacks[which]->visual, visual) || + !!strcasecmp (p->screenhacks[which]->command, command)) + { + /* Something was changed -- store results into the struct, + and write the file. + */ + free (p->screenhacks[which]->visual); + free (p->screenhacks[which]->command); + p->screenhacks[which]->visual = strdup (visual); + p->screenhacks[which]->command = strdup (command); + p->screenhacks[which]->enabled_p = enabled_p; + + return demo_write_init_file (widget, p); + } + + /* No changes made */ + return 0; +} + +void +run_this_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + int which = selected_hack_number (XtParent (button)); + if (which < 0) return; + if (0 == apply_changes_and_save (XtParent (button))) + run_hack (XtParent (button), which, True); +} + + +void +manual_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + prefs_pair *pair = (prefs_pair *) client_data; + saver_preferences *p = pair->a; + Widget list_widget = name_to_widget (button, "list"); + int which = selected_hack_number (button); + char *name, *name2, *cmd, *s; + if (which < 0) return; + apply_changes_and_save (button); + ensure_selected_item_visible (list_widget); + + name = strdup (p->screenhacks[which]->command); + name2 = name; + while (isspace (*name2)) name2++; + s = name2; + while (*s && !isspace (*s)) s++; + *s = 0; + s = strrchr (name2, '/'); + if (s) name = s+1; + + cmd = get_string_resource (XtDisplay (button), "manualCommand", "ManualCommand"); + if (cmd) + { + char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100); + strcpy (cmd2, "( "); + sprintf (cmd2 + strlen (cmd2), + cmd, + name2, name2, name2, name2); + strcat (cmd2, " ) &"); + system (cmd2); + free (cmd2); + } + else + { + warning_dialog (XtParent (button), + "Error:\n\nno `manualCommand' resource set.", + 100); + } + + free (name); +} + + +void +run_next_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + prefs_pair *pair = (prefs_pair *) client_data; + saver_preferences *p = pair->a; + + Widget list_widget = name_to_widget (button, "list"); + int which = selected_hack_number (button); + + button = XtParent (button); + + if (which < 0) + which = 0; + else + which++; + + if (which >= p->screenhacks_count) + which = 0; + + apply_changes_and_save (button); + + XmListDeselectAllItems (list_widget); /* LessTif lossage */ + XmListSelectPos (list_widget, which+1, True); + + ensure_selected_item_visible (list_widget); + populate_demo_window (button, which, pair); + run_hack (button, which, False); +} + + +void +run_prev_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + prefs_pair *pair = (prefs_pair *) client_data; + saver_preferences *p = pair->a; + + Widget list_widget = name_to_widget (button, "list"); + int which = selected_hack_number (button); + + button = XtParent (button); + + if (which < 0) + which = p->screenhacks_count - 1; + else + which--; + + if (which < 0) + which = p->screenhacks_count - 1; + + apply_changes_and_save (button); + + XmListDeselectAllItems (list_widget); /* LessTif lossage */ + XmListSelectPos (list_widget, which+1, True); + + ensure_selected_item_visible (list_widget); + populate_demo_window (button, which, pair); + run_hack (button, which, False); +} + + +/* Helper for the text fields that contain time specifications: + this parses the text, and does error checking. + */ +static void +hack_time_text (Widget button, const char *line, Time *store, Bool sec_p) +{ + if (*line) + { + int value; + value = parse_time ((char *) line, sec_p, True); + value *= 1000; /* Time measures in microseconds */ + if (value < 0) + { + char b[255]; + sprintf (b, + "Error:\n\n" + "Unparsable time format: \"%s\"\n", + line); + warning_dialog (XtParent (button), b, 100); + } + else + *store = value; + } +} + + +void +prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + prefs_pair *pair = (prefs_pair *) client_data; + + saver_preferences *p = pair->a; + saver_preferences *p2 = pair->b; + Bool changed = False; + char *v = 0; + + button = XtParent (button); + +# define SECONDS(field, name) \ + v = 0; \ + XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \ + hack_time_text (button, v, (field), True) + +# define MINUTES(field, name) \ + v = 0; \ + XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \ + hack_time_text (button, v, (field), False) + +# define INTEGER(field, name) do { \ + unsigned int value; \ + char c; \ + XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \ + if (! *v) \ + ; \ + else if (sscanf (v, "%u%c", &value, &c) != 1) \ + { \ + char b[255]; \ + sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", v); \ + warning_dialog (XtParent (button), b, 100); \ + } \ + else \ + *(field) = value; \ + } while(0) + +# define CHECKBOX(field, name) \ + XtVaGetValues (name_to_widget (button, (name)), XmNset, &field, NULL) + + MINUTES (&p2->timeout, "timeoutText"); + MINUTES (&p2->cycle, "cycleText"); + SECONDS (&p2->fade_seconds, "fadeSecondsText"); + INTEGER (&p2->fade_ticks, "fadeTicksText"); + MINUTES (&p2->lock_timeout, "lockText"); + SECONDS (&p2->passwd_timeout, "passwdText"); + CHECKBOX (p2->verbose_p, "verboseToggle"); + CHECKBOX (p2->install_cmap_p, "cmapToggle"); + CHECKBOX (p2->fade_p, "fadeToggle"); + CHECKBOX (p2->unfade_p, "unfadeToggle"); + CHECKBOX (p2->lock_p, "lockToggle"); + +# undef SECONDS +# undef MINUTES +# undef INTEGER +# undef CHECKBOX + +# define COPY(field) \ + if (p->field != p2->field) changed = True; \ + p->field = p2->field + + COPY(timeout); + COPY(cycle); + COPY(lock_timeout); + COPY(passwd_timeout); + COPY(fade_seconds); + COPY(fade_ticks); + COPY(verbose_p); + COPY(install_cmap_p); + COPY(fade_p); + COPY(unfade_p); + COPY(lock_p); +# undef COPY + + populate_prefs_page (button, pair); + + if (changed) + demo_write_init_file (button, p); +} + + +void +prefs_cancel_cb (Widget button, XtPointer client_data, XtPointer ignored) +{ + prefs_pair *pair = (prefs_pair *) client_data; + + *pair->b = *pair->a; + populate_prefs_page (XtParent (button), pair); +} + + +static void +list_select_cb (Widget list, XtPointer client_data, XtPointer call_data) +{ + prefs_pair *pair = (prefs_pair *) client_data; + + XmListCallbackStruct *lcb = (XmListCallbackStruct *) call_data; + int which = lcb->item_position - 1; + + apply_changes_and_save (list); + populate_demo_window (list, which, pair); + + if (lcb->reason == XmCR_DEFAULT_ACTION && which >= 0) + run_hack (list, which, True); +} + + +/* Populating the various widgets + */ + + +/* Formats a `Time' into "H:MM:SS". (Time is microseconds.) + */ +static void +format_time (char *buf, Time time) +{ + int s = time / 1000; + unsigned int h = 0, m = 0; + if (s >= 60) + { + m += (s / 60); + s %= 60; + } + if (m >= 60) + { + h += (m / 60); + m %= 60; + } + sprintf (buf, "%u:%02u:%02u", h, m, s); +} + + +/* Finds the number of the last hack to run, and makes that item be + selected by default. + */ +static void +scroll_to_current_hack (Widget toplevel, prefs_pair *pair) +{ + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *data = 0; + Display *dpy = XtDisplay (toplevel); + int which = 0; + Widget list; + + if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + XA_SCREENSAVER_STATUS, + 0, 3, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &data) + == Success + && type == XA_INTEGER + && nitems >= 3 + && data) + which = (int) data[2] - 1; + + if (data) free (data); + + if (which < 0) + return; + + list = name_to_widget (toplevel, "list"); + apply_changes_and_save (toplevel); + + XmListDeselectAllItems (list); /* LessTif lossage */ + XmListSelectPos (list, which+1, True); + + ensure_selected_item_visible (list); + populate_demo_window (toplevel, which, pair); +} + + + +static void +populate_hack_list (Widget toplevel, prefs_pair *pair) +{ + saver_preferences *p = pair->a; + Widget list = name_to_widget (toplevel, "list"); + screenhack **hacks = p->screenhacks; + screenhack **h; + + for (h = hacks; *h; h++) + { + char *pretty_name = (h[0]->name + ? strdup (h[0]->name) + : make_hack_name (XtDisplay (toplevel), h[0]->command)); + + XmString xmstr = XmStringCreate (pretty_name, XmSTRING_DEFAULT_CHARSET); + XmListAddItem (list, xmstr, 0); + XmStringFree (xmstr); + } + + XtAddCallback (list, XmNbrowseSelectionCallback, list_select_cb, pair); + XtAddCallback (list, XmNdefaultActionCallback, list_select_cb, pair); +} + + +static void +populate_prefs_page (Widget top, prefs_pair *pair) +{ + saver_preferences *p = pair->a; + char s[100]; + + format_time (s, p->timeout); + XtVaSetValues (name_to_widget (top, "timeoutText"), XmNvalue, s, NULL); + format_time (s, p->cycle); + XtVaSetValues (name_to_widget (top, "cycleText"), XmNvalue, s, NULL); + format_time (s, p->lock_timeout); + XtVaSetValues (name_to_widget (top, "lockText"), XmNvalue, s, NULL); + format_time (s, p->passwd_timeout); + XtVaSetValues (name_to_widget (top, "passwdText"), XmNvalue, s, NULL); + format_time (s, p->fade_seconds); + XtVaSetValues (name_to_widget (top, "fadeSecondsText"), XmNvalue, s, NULL); + sprintf (s, "%u", p->fade_ticks); + XtVaSetValues (name_to_widget (top, "fadeTicksText"), XmNvalue, s, NULL); + + XtVaSetValues (name_to_widget (top, "verboseToggle"), + XmNset, p->verbose_p, NULL); + XtVaSetValues (name_to_widget (top, "cmapToggle"), + XmNset, p->install_cmap_p, NULL); + XtVaSetValues (name_to_widget (top, "fadeToggle"), + XmNset, p->fade_p, NULL); + XtVaSetValues (name_to_widget (top, "unfadeToggle"), + XmNset, p->unfade_p, NULL); + XtVaSetValues (name_to_widget (top, "lockToggle"), + XmNset, p->lock_p, NULL); + + + { + Bool found_any_writable_cells = False; + Display *dpy = XtDisplay (top); + int nscreens = ScreenCount(dpy); + int i; + for (i = 0; i < nscreens; i++) + { + Screen *s = ScreenOfDisplay (dpy, i); + if (has_writable_cells (s, DefaultVisualOfScreen (s))) + { + found_any_writable_cells = True; + break; + } + } + +#ifdef HAVE_XF86VMODE_GAMMA + found_any_writable_cells = True; /* if we can gamma fade, go for it */ +#endif + + XtVaSetValues (name_to_widget (top, "fadeSecondsLabel"), XtNsensitive, + found_any_writable_cells, NULL); + XtVaSetValues (name_to_widget (top, "fadeTicksLabel"), XtNsensitive, + found_any_writable_cells, NULL); + XtVaSetValues (name_to_widget (top, "fadeSecondsText"), XtNsensitive, + found_any_writable_cells, NULL); + XtVaSetValues (name_to_widget (top, "fadeTicksText"), XtNsensitive, + found_any_writable_cells, NULL); + XtVaSetValues (name_to_widget (top, "cmapToggle"), XtNsensitive, + found_any_writable_cells, NULL); + XtVaSetValues (name_to_widget (top, "fadeToggle"), XtNsensitive, + found_any_writable_cells, NULL); + XtVaSetValues (name_to_widget (top, "unfadeToggle"), XtNsensitive, + found_any_writable_cells, NULL); + } +} + + +static void +sensitize_demo_widgets (Widget toplevel, Bool sensitive_p) +{ + const char *names[] = { "cmdLabel", "cmdText", "enabled", + "visLabel", "combo", "demo", "man" }; + int i; + for (i = 0; i < sizeof(names)/countof(*names); i++) + { + Widget w = name_to_widget (toplevel, names[i]); + XtVaSetValues (w, XtNsensitive, sensitive_p, NULL); + } + + /* I don't know how to handle these yet... */ + { + const char *names2[] = { "cut", "copy", "paste" }; + for (i = 0; i < sizeof(names2)/countof(*names2); i++) + { + Widget w = name_to_widget (toplevel, names2[i]); + XtVaSetValues (w, XtNsensitive, FALSE, NULL); + } + } +} + + + +/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...) + */ + +#ifdef HAVE_XPM + +static char *up_arrow_xpm[] = { + "15 15 4 1", + " c None s background", + "- c #FFFFFF", + "+ c #D6D6D6", + "@ c #000000", + + " @ ", + " @ ", + " -+@ ", + " -+@ ", + " -+++@ ", + " -+++@ ", + " -+++++@ ", + " -+++++@ ", + " -+++++++@ ", + " -+++++++@ ", + " -+++++++++@ ", + " -+++++++++@ ", + " -+++++++++++@ ", + " @@@@@@@@@@@@@ ", + " " +}; + +static char *down_arrow_xpm[] = { + "15 15 4 1", + " c None s background", + "- c #FFFFFF", + "+ c #D6D6D6", + "@ c #000000", + + " ", + " ------------- ", + " -+++++++++++@ ", + " -+++++++++@ ", + " -+++++++++@ ", + " -+++++++@ ", + " -+++++++@ ", + " -+++++@ ", + " -+++++@ ", + " -+++@ ", + " -+++@ ", + " -+@ ", + " -+@ ", + " @ ", + " @ " +}; + +#endif /* HAVE_XPM */ + + +static void +pixmapify_buttons (Widget toplevel) +{ +#ifdef HAVE_XPM + + Display *dpy = XtDisplay (toplevel); + Window window = XtWindow (toplevel); + XWindowAttributes xgwa; + XpmAttributes xpmattrs; + Pixmap up_pixmap = 0, down_pixmap = 0; + int result; + Widget up = name_to_widget (toplevel, "up"); + Widget dn = name_to_widget (toplevel, "down"); +# ifdef XpmColorSymbols + XColor xc; + XpmColorSymbol symbols[2]; + char color[20]; +# endif + + XGetWindowAttributes (dpy, window, &xgwa); + + xpmattrs.valuemask = 0; + +# ifdef XpmColorSymbols + symbols[0].name = "background"; + symbols[0].pixel = 0; + symbols[1].name = 0; + XtVaGetValues (up, XmNbackground, &xc, NULL); + XQueryColor (dpy, xgwa.colormap, &xc); + sprintf (color, "#%04X%04X%04X", xc.red, xc.green, xc.blue); + symbols[0].value = color; + symbols[0].pixel = xc.pixel; + + xpmattrs.valuemask |= XpmColorSymbols; + xpmattrs.colorsymbols = symbols; + xpmattrs.numsymbols = 1; +# endif + +# ifdef XpmCloseness + xpmattrs.valuemask |= XpmCloseness; + xpmattrs.closeness = 40000; +# endif +# ifdef XpmVisual + xpmattrs.valuemask |= XpmVisual; + xpmattrs.visual = xgwa.visual; +# endif +# ifdef XpmDepth + xpmattrs.valuemask |= XpmDepth; + xpmattrs.depth = xgwa.depth; +# endif +# ifdef XpmColormap + xpmattrs.valuemask |= XpmColormap; + xpmattrs.colormap = xgwa.colormap; +# endif + + result = XpmCreatePixmapFromData(dpy, window, up_arrow_xpm, + &up_pixmap, 0 /* mask */, &xpmattrs); + if (!up_pixmap || (result != XpmSuccess && result != XpmColorError)) + { + fprintf (stderr, "%s: Can't load pixmaps\n", progname); + return; + } + + result = XpmCreatePixmapFromData(dpy, window, down_arrow_xpm, + &down_pixmap, 0 /* mask */, &xpmattrs); + if (!down_pixmap || (result != XpmSuccess && result != XpmColorError)) + { + fprintf (stderr, "%s: Can't load pixmaps\n", progname); + return; + } + + XtVaSetValues (up, XmNlabelType, XmPIXMAP, XmNlabelPixmap, up_pixmap, NULL); + XtVaSetValues (dn, XmNlabelType, XmPIXMAP, XmNlabelPixmap, down_pixmap,NULL); + +#endif /* HAVE_XPM */ +} + + + +char * +get_hack_blurb (Display *dpy, screenhack *hack) +{ + char *doc_string; + char *prog_name = strdup (hack->command); + char *pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (dpy, hack->command)); + char doc_name[255], doc_class[255]; + char *s, *s2; + + for (s = prog_name; *s && !isspace(*s); s++) + ; + *s = 0; + s = strrchr (prog_name, '/'); + if (s) strcpy (prog_name, s+1); + + sprintf (doc_name, "hacks.%s.documentation", pretty_name); + sprintf (doc_class, "hacks.%s.documentation", prog_name); + free (prog_name); + free (pretty_name); + + doc_string = get_string_resource (dpy, doc_name, doc_class); + if (doc_string) + { + for (s = doc_string; *s; s++) + { + if (*s == '\n') + { + /* skip over whitespace at beginning of line */ + s++; + while (*s && (*s == ' ' || *s == '\t')) + s++; + } + else if (*s == ' ' || *s == '\t') + { + /* compress all other horizontal whitespace. */ + *s = ' '; + s++; + for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++) + ; + if (s2 > s) strcpy (s, s2); + s--; + } + } + + while (*s && isspace (*s)) /* Strip trailing whitespace */ + *(--s) = 0; + + /* Delete whitespace at end of each line. */ + for (; s > doc_string; s--) + if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t')) + { + for (s2 = s-1; + s2 > doc_string && (*s2 == ' ' || *s2 == '\t'); + s2--) + ; + s2++; + if (s2 < s) strcpy (s2, s); + s = s2; + } + + /* Delete leading blank lines. */ + for (s = doc_string; *s == '\n'; s++) + ; + if (s > doc_string) strcpy (doc_string, s); + } + else + { +# if 0 + static int doc_installed = 0; + if (doc_installed == 0) + { + if (get_boolean_resource ("hacks.documentation.isInstalled", + "hacks.documentation.isInstalled")) + doc_installed = 1; + else + doc_installed = -1; + } + + if (doc_installed < 0) + doc_string = + strdup ("Error:\n\n" + "The documentation strings do not appear to be " + "installed. This is probably because there is " + "an \"XScreenSaver\" app-defaults file installed " + "that is from an older version of the program. " + "To fix this problem, delete that file, or " + "install a current version (either will work.)"); + else +# endif /* 0 */ + doc_string = strdup ( + "\n" + "This is the Motif version of \"xscreensaver-demo\". The Motif " + "version is no longer maintained. Please use the GTK version " + "instead, which has many more features." + "\n\n" + "If you were running the GTK version, there would be a preview " + "of this screen saver mode displayed here, along with graphical " + "configuration options."); + } + + return doc_string; +} + + +static void +populate_demo_window (Widget toplevel, int which, prefs_pair *pair) +{ + saver_preferences *p = pair->a; + screenhack *hack = (which >= 0 ? p->screenhacks[which] : 0); + Widget frameL = name_to_widget (toplevel, "frameLabel"); + Widget doc = name_to_widget (toplevel, "doc"); + Widget cmd = name_to_widget (toplevel, "cmdText"); + Widget enabled = name_to_widget (toplevel, "enabled"); + Widget vis = name_to_widget (toplevel, "combo"); + int i = 0; + + char *pretty_name = (hack + ? (hack->name + ? strdup (hack->name) + : make_hack_name (XtDisplay (toplevel), hack->command)) + : 0); + char *doc_string = hack ? get_hack_blurb (XtDisplay (toplevel), hack) : 0; + + XmString xmstr; + + xmstr = XmStringCreate (pretty_name, XmSTRING_DEFAULT_CHARSET); + XtVaSetValues (frameL, XmNlabelString, xmstr, NULL); + XmStringFree (xmstr); + + XtVaSetValues (doc, XmNvalue, doc_string, NULL); + XtVaSetValues (cmd, XmNvalue, (hack ? hack->command : ""), NULL); + + XtVaSetValues (enabled, XmNset, (hack ? hack->enabled_p : False), NULL); + + i = 0; + if (hack && hack->visual && *hack->visual) + for (i = 0; visual_menu[i]; i++) + if (!strcasecmp (hack->visual, visual_menu[i])) + break; + if (!visual_menu[i]) i = -1; + + { +# ifdef HAVE_XMCOMBOBOX + Widget text = 0; + XtVaGetValues (vis, XmNtextField, &text, NULL); + XtVaSetValues (vis, XmNselectedPosition, i, NULL); + if (i < 0) + XtVaSetValues (text, XmNvalue, hack->visual, NULL); +# else /* !HAVE_XMCOMBOBOX */ + Cardinal nkids; + Widget *kids; + Widget menu; + + XtVaGetValues (vis, XmNsubMenuId, &menu, NULL); + if (!menu) abort (); + XtVaGetValues (menu, XmNnumChildren, &nkids, XmNchildren, &kids, NULL); + if (!kids) abort(); + if (i < nkids) + XtVaSetValues (vis, XmNmenuHistory, kids[i], NULL); +# endif /* !HAVE_XMCOMBOBOX */ + } + + sensitize_demo_widgets (toplevel, (hack ? True : False)); + + if (pretty_name) free (pretty_name); + if (doc_string) free (doc_string); + + _selected_hack_number = which; +} + + + +static int +maybe_reload_init_file (Widget widget, prefs_pair *pair) +{ + int status = 0; + saver_preferences *p = pair->a; + + static Bool reentrant_lock = False; + if (reentrant_lock) return 0; + reentrant_lock = True; + + if (init_file_changed_p (p)) + { + const char *f = init_file_name(); + char *b; + int which; + Widget list; + + if (!f || !*f) return 0; + b = (char *) malloc (strlen(f) + 1024); + sprintf (b, + "Warning:\n\n" + "file \"%s\" has changed, reloading.\n", + f); + warning_dialog (widget, b, 100); + free (b); + + load_init_file (XtDisplay (widget), p); + + which = selected_hack_number (widget); + list = name_to_widget (widget, "list"); + + XtVaSetValues (list, XmNitemCount, 0, NULL); + + populate_hack_list (widget, pair); + + XmListDeselectAllItems (list); /* LessTif lossage */ + XmListSelectPos (list, which+1, True); + + populate_prefs_page (widget, pair); + populate_demo_window (widget, which, pair); + ensure_selected_item_visible (list); + + status = 1; + } + + reentrant_lock = False; + return status; +} + + + +/* Attach all callback functions to widgets + */ + +static void +add_callbacks (Widget toplevel, prefs_pair *pair) +{ + Widget w; + +# define CB(NAME,FN) \ + w = name_to_widget (toplevel, (NAME)); \ + XtAddCallback (w, XmNactivateCallback, (FN), pair) + + CB ("blank", activate_menu_cb); + CB ("lock", lock_menu_cb); + CB ("kill", kill_menu_cb); + CB ("restart", restart_menu_cb); + CB ("exit", exit_menu_cb); + + CB ("cut", cut_menu_cb); + CB ("copy", copy_menu_cb); + CB ("paste", paste_menu_cb); + + CB ("about", about_menu_cb); + CB ("docMenu", doc_menu_cb); + + CB ("down", run_next_cb); + CB ("up", run_prev_cb); + CB ("demo", run_this_cb); + CB ("man", manual_cb); + + CB ("preferencesForm.Cancel", prefs_cancel_cb); + CB ("preferencesForm.OK", prefs_ok_cb); + +# undef CB +} + + +static void +sanity_check_resources (Widget toplevel) +{ + const char *names[] = { "demoTab", "optionsTab", "cmdLabel", "visLabel", + "enabled", "demo", "man", "timeoutLabel", + "cycleLabel", "fadeSecondsLabel", "fadeTicksLabel", + "lockLabel", "passwdLabel" }; + int i; + for (i = 0; i < sizeof(names)/countof(*names); i++) + { + Widget w = name_to_widget (toplevel, names[i]); + const char *name = XtName(w); + XmString xm = 0; + char *label = 0; + XtVaGetValues (w, XmNlabelString, &xm, NULL); + if (xm) XmStringGetLtoR (xm, XmSTRING_DEFAULT_CHARSET, &label); + if (w && (!label || !strcmp (name, label))) + { + xm = XmStringCreate ("ERROR", XmSTRING_DEFAULT_CHARSET); + XtVaSetValues (w, XmNlabelString, xm, NULL); + } + } +} + +/* Set certain buttons to be the same size (the max of the set.) + */ +static void +hack_button_sizes (Widget toplevel) +{ + Widget demo = name_to_widget (toplevel, "demo"); + Widget man = name_to_widget (toplevel, "man"); + Widget ok = name_to_widget (toplevel, "OK"); + Widget can = name_to_widget (toplevel, "Cancel"); + Widget up = name_to_widget (toplevel, "up"); + Widget down = name_to_widget (toplevel, "down"); + Dimension w1, w2; + + XtVaGetValues (demo, XmNwidth, &w1, NULL); + XtVaGetValues (man, XmNwidth, &w2, NULL); + XtVaSetValues ((w1 > w2 ? man : demo), XmNwidth, (w1 > w2 ? w1 : w2), NULL); + + XtVaGetValues (ok, XmNwidth, &w1, NULL); + XtVaGetValues (can, XmNwidth, &w2, NULL); + XtVaSetValues ((w1 > w2 ? can : ok), XmNwidth, (w1 > w2 ? w1 : w2), NULL); + + XtVaGetValues (up, XmNwidth, &w1, NULL); + XtVaGetValues (down, XmNwidth, &w2, NULL); + XtVaSetValues ((w1 > w2 ? down : up), XmNwidth, (w1 > w2 ? w1 : w2), NULL); +} + + + + +/* The main demo-mode command loop. + */ + +#if 0 +static Bool +mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, + XrmRepresentation *type, XrmValue *value, XPointer closure) +{ + int i; + for (i = 0; quarks[i]; i++) + { + if (bindings[i] == XrmBindTightly) + fprintf (stderr, (i == 0 ? "" : ".")); + else if (bindings[i] == XrmBindLoosely) + fprintf (stderr, "*"); + else + fprintf (stderr, " ??? "); + fprintf(stderr, "%s", XrmQuarkToString (quarks[i])); + } + + fprintf (stderr, ": %s\n", (char *) value->addr); + + return False; +} +#endif + + +static void +the_network_is_not_the_computer (Widget parent) +{ + Display *dpy = XtDisplay (parent); + char *rversion, *ruser, *rhost; + char *luser, *lhost; + char *msg = 0; + struct passwd *p = getpwuid (getuid ()); + const char *d = DisplayString (dpy); + +# if defined(HAVE_UNAME) + struct utsname uts; + if (uname (&uts) < 0) + lhost = ""; + else + lhost = uts.nodename; +# elif defined(VMS) + strcpy (lhost, getenv("SYS$NODE")); +# else /* !HAVE_UNAME && !VMS */ + strcat (lhost, ""); +# endif /* !HAVE_UNAME && !VMS */ + + if (p && p->pw_name) + luser = p->pw_name; + else + luser = "???"; + + server_xscreensaver_version (dpy, &rversion, &ruser, &rhost); + + /* Make a buffer that's big enough for a number of copies of all the + strings, plus some. */ + msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) + + (ruser ? strlen(ruser) : 0) + + (rhost ? strlen(rhost) : 0) + + strlen(lhost) + + strlen(luser) + + strlen(d) + + 1024)); + *msg = 0; + + if (!rversion || !*rversion) + { + sprintf (msg, + "Warning:\n\n" + "The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%s\". You can launch it by selecting\n" + "`Restart Daemon' from the File menu, or by typing\n" + "\"xscreensaver &\" in a shell.", + d); + } + else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name)) + { + /* Warn that the two processes are running as different users. + */ + sprintf(msg, + "Warning:\n\n" + "%s is running as user \"%s\" on host \"%s\".\n" + "But the xscreensaver managing display \"%s\"\n" + "is running as user \"%s\" on host \"%s\".\n" + "\n" + "Since they are different users, they won't be reading/writing\n" + "the same ~/.xscreensaver file, so %s isn't\n" + "going to work right.\n" + "\n" + "Either re-run %s as \"%s\", or re-run\n" + "xscreensaver as \"%s\" (which you can do by\n" + "selecting `Restart Daemon' from the File menu.)\n", + progname, luser, lhost, + d, + (ruser ? ruser : "???"), (rhost ? rhost : "???"), + progname, + progname, (ruser ? ruser : "???"), + luser); + } + else if (rhost && *rhost && !!strcmp (rhost, lhost)) + { + /* Warn that the two processes are running on different hosts. + */ + sprintf (msg, + "Warning:\n\n" + "%s is running as user \"%s\" on host \"%s\".\n" + "But the xscreensaver managing display \"%s\"\n" + "is running as user \"%s\" on host \"%s\".\n" + "\n" + "If those two machines don't share a file system (that is,\n" + "if they don't see the same ~%s/.xscreensaver file) then\n" + "%s won't work right.\n" + "\n" + "You can restart the daemon on \"%s\" as \"%s\" by\n" + "selecting `Restart Daemon' from the File menu.)", + progname, luser, lhost, + d, + (ruser ? ruser : "???"), (rhost ? rhost : "???"), + luser, + progname, + lhost, luser); + } + else if (!!strcmp (rversion, short_version)) + { + /* Warn that the version numbers don't match. + */ + sprintf (msg, + "Warning:\n\n" + "This is %s version %s.\n" + "But the xscreensaver managing display \"%s\"\n" + "is version %s. This could cause problems.", + progname, short_version, + d, + rversion); + } + + + if (*msg) + warning_dialog (parent, msg, 1); + + free (msg); +} + + +/* We use this error handler so that X errors are preceeded by the name + of the program that generated them. + */ +static int +demo_ehandler (Display *dpy, XErrorEvent *error) +{ + fprintf (stderr, "\nX error in %s:\n", progname); + XmuPrintDefaultErrorMessage (dpy, error, stderr); + exit (-1); + return 0; +} + + + +#ifdef __GNUC__ + __extension__ /* shut up about "string length is greater than the length + ISO C89 compilers are required to support" when including + the .ad file... */ +#endif + +static char *defaults[] = { +#include "XScreenSaver_ad.h" +#include "XScreenSaver_Xm_ad.h" + 0 +}; + + +int +main (int argc, char **argv) +{ + XtAppContext app; + prefs_pair Pair, *pair; + saver_preferences P, P2, *p, *p2; + Bool prefs = False; + int i; + Display *dpy; + Widget toplevel_shell, dialog; + char *real_progname = argv[0]; + char *s; + + s = strrchr (real_progname, '/'); + if (s) real_progname = s+1; + + p = &P; + p2 = &P2; + pair = &Pair; + pair->a = p; + pair->b = p2; + memset (p, 0, sizeof (*p)); + memset (p2, 0, sizeof (*p2)); + + global_prefs_pair = pair; + + progname = real_progname; + + /* We must read exactly the same resources as xscreensaver. + That means we must have both the same progclass *and* progname, + at least as far as the resource database is concerned. So, + put "xscreensaver" in argv[0] while initializing Xt. + */ + argv[0] = "xscreensaver"; + progname = argv[0]; + + + toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, &argc, argv, + defaults, 0, 0); + + dpy = XtDisplay (toplevel_shell); + db = XtDatabase (dpy); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + XSetErrorHandler (demo_ehandler); + + /* Complain about unrecognized command-line arguments. + */ + for (i = 1; i < argc; i++) + { + char *s = argv[i]; + if (s[0] == '-' && s[1] == '-') + s++; + if (!strcmp (s, "-prefs")) + prefs = True; + else + { + fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n", + real_progname); + exit (1); + } + } + + short_version = (char *) malloc (5); + memcpy (short_version, screensaver_id + 17, 4); + short_version [4] = 0; + + /* Load the init file, which may end up consulting the X resource database + and the site-wide app-defaults file. Note that at this point, it's + important that `progname' be "xscreensaver", rather than whatever + was in argv[0]. + */ + p->db = db; + load_init_file (dpy, p); + *p2 = *p; + + /* Now that Xt has been initialized, and the resources have been read, + we can set our `progname' variable to something more in line with + reality. + */ + progname = real_progname; + + +#if 0 + { + XrmName name = { 0 }; + XrmClass class = { 0 }; + int count = 0; + XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper, + (POINTER) &count); + } +#endif + + + /* Intern the atoms that xscreensaver_command() needs. + */ + XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False); + XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False); + XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False); + XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False); + XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False); + XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False); + XA_SELECT = XInternAtom (dpy, "SELECT", False); + XA_DEMO = XInternAtom (dpy, "DEMO", False); + XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False); + XA_BLANK = XInternAtom (dpy, "BLANK", False); + XA_LOCK = XInternAtom (dpy, "LOCK", False); + XA_EXIT = XInternAtom (dpy, "EXIT", False); + XA_RESTART = XInternAtom (dpy, "RESTART", False); + + /* Create the window and all its widgets. + */ + dialog = create_xscreensaver_demo (toplevel_shell); + + /* Set the window's title. */ + { + char title[255]; + char *v = (char *) strdup(strchr(screensaver_id, ' ')); + char *s1, *s2, *s3, *s4; + s1 = (char *) strchr(v, ' '); s1++; + s2 = (char *) strchr(s1, ' '); + s3 = (char *) strchr(v, '('); s3++; + s4 = (char *) strchr(s3, ')'); + *s2 = 0; + *s4 = 0; + sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3); + XtVaSetValues (toplevel_shell, XtNtitle, title, NULL); + free (v); + } + + sanity_check_resources (toplevel_shell); + add_callbacks (toplevel_shell, pair); + populate_hack_list (toplevel_shell, pair); + populate_prefs_page (toplevel_shell, pair); + sensitize_demo_widgets (toplevel_shell, False); + scroll_to_current_hack (toplevel_shell, pair); + + XtManageChild (dialog); + XtRealizeWidget(toplevel_shell); + + /* The next few calls must come after XtRealizeWidget(). */ + pixmapify_buttons (toplevel_shell); + hack_button_sizes (toplevel_shell); + ensure_selected_item_visible (name_to_widget (toplevel_shell, "list")); + + XSync (dpy, False); + XtVaSetValues (toplevel_shell, XmNallowShellResize, False, NULL); + + + /* Handle the -prefs command-line argument. */ + if (prefs) + { + Widget tabber = name_to_widget (toplevel_shell, "folder"); + Widget this_tab = name_to_widget (toplevel_shell, "optionsTab"); + Widget this_page = name_to_widget (toplevel_shell, "preferencesForm"); + Widget *kids = 0; + Cardinal nkids = 0; + if (!tabber) abort(); + + XtVaGetValues (tabber, XmNnumChildren, &nkids, XmNchildren, &kids, NULL); + if (!kids) abort(); + if (nkids > 0) + XtUnmanageChildren (kids, nkids); + + XtManageChild (this_page); + + XmProcessTraversal (this_tab, XmTRAVERSE_CURRENT); + } + + /* Issue any warnings about the running xscreensaver daemon. */ + the_network_is_not_the_computer (toplevel_shell); + + + XtAppMainLoop (app); + exit (0); +} + +#endif /* HAVE_MOTIF -- whole file */ diff --git a/driver/dpms.c b/driver/dpms.c new file mode 100644 index 00000000..526c4bde --- /dev/null +++ b/driver/dpms.c @@ -0,0 +1,256 @@ +/* dpms.c --- syncing the X Display Power Management values + * xscreensaver, Copyright (c) 2001-2009 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* Display Power Management System (DPMS.) + + On XFree86 systems, "man xset" reports: + + -dpms The -dpms option disables DPMS (Energy Star) features. + +dpms The +dpms option enables DPMS (Energy Star) features. + + dpms flags... + The dpms option allows the DPMS (Energy Star) + parameters to be set. The option can take up to three + numerical values, or the `force' flag followed by a + DPMS state. The `force' flags forces the server to + immediately switch to the DPMS state specified. The + DPMS state can be one of `standby', `suspend', or + `off'. When numerical values are given, they set the + inactivity period before the three modes are activated. + The first value given is for the `standby' mode, the + second is for the `suspend' mode, and the third is for + the `off' mode. Setting these values implicitly + enables the DPMS features. A value of zero disables a + particular mode. + + However, note that the implementation is more than a little bogus, + in that there is code in /usr/X11R6/lib/libXdpms.a to implement all + the usual server-extension-querying utilities -- but there are no + prototypes in any header file! Thus, the prototypes here. (The + stuff in X11/extensions/dpms.h and X11/extensions/dpmsstr.h define + the raw X protcol, they don't define the API to libXdpms.a.) + + Some documentation: + Library: ftp://ftp.x.org/pub/R6.4/xc/doc/specs/Xext/DPMSLib.ms + Protocol: ftp://ftp.x.org/pub/R6.4/xc/doc/specs/Xext/DPMS.ms + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#ifdef HAVE_DPMS_EXTENSION /* almost the whole file */ + +# include +# include +/*# include */ + + /* Why this crap is not in a header file somewhere, I have no idea. Losers! + */ + extern Bool DPMSQueryExtension (Display *, int *event_ret, int *err_ret); + extern Status DPMSGetVersion (Display *, int *major_ret, int *minor_ret); + extern Bool DPMSCapable (Display *); + extern Status DPMSInfo (Display *, CARD16 *power_level, BOOL *state); + extern Status DPMSEnable (Display *dpy); + extern Status DPMSDisable (Display *dpy); + extern Status DPMSForceLevel (Display *, CARD16 level); + extern Status DPMSSetTimeouts (Display *, CARD16 standby, CARD16 suspend, + CARD16 off); + extern Bool DPMSGetTimeouts (Display *, CARD16 *standby, + CARD16 *suspend, CARD16 *off); + +#endif /* HAVE_DPMS_EXTENSION */ + + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" + +#ifdef HAVE_DPMS_EXTENSION + +void +sync_server_dpms_settings (Display *dpy, Bool enabled_p, + int standby_secs, int suspend_secs, int off_secs, + Bool verbose_p) +{ + int event = 0, error = 0; + BOOL o_enabled = False; + CARD16 o_power = 0; + CARD16 o_standby = 0, o_suspend = 0, o_off = 0; + Bool bogus_p = False; + + if (standby_secs == 0 && suspend_secs == 0 && off_secs == 0) + /* all zero implies "DPMS disabled" */ + enabled_p = False; + + else if ((standby_secs != 0 && standby_secs < 10) || + (suspend_secs != 0 && suspend_secs < 10) || + (off_secs != 0 && off_secs < 10)) + /* any negative, or any positive-and-less-than-10-seconds, is crazy. */ + bogus_p = True; + + if (bogus_p) enabled_p = False; + + /* X protocol sends these values in a CARD16, so truncate them to 16 bits. + This means that the maximum timeout is 18:12:15. + */ + if (standby_secs > 0xFFFF) standby_secs = 0xFFFF; + if (suspend_secs > 0xFFFF) suspend_secs = 0xFFFF; + if (off_secs > 0xFFFF) off_secs = 0xFFFF; + + if (! DPMSQueryExtension (dpy, &event, &error)) + { + if (verbose_p) + fprintf (stderr, "%s: XDPMS extension not supported.\n", blurb()); + return; + } + + if (! DPMSCapable (dpy)) + { + if (verbose_p) + fprintf (stderr, "%s: DPMS not supported.\n", blurb()); + return; + } + + if (! DPMSInfo (dpy, &o_power, &o_enabled)) + { + if (verbose_p) + fprintf (stderr, "%s: unable to get DPMS state.\n", blurb()); + return; + } + + if (o_enabled != enabled_p) + { + if (! (enabled_p ? DPMSEnable (dpy) : DPMSDisable (dpy))) + { + if (verbose_p) + fprintf (stderr, "%s: unable to set DPMS state.\n", blurb()); + return; + } + else if (verbose_p) + fprintf (stderr, "%s: turned DPMS %s.\n", blurb(), + enabled_p ? "on" : "off"); + } + + if (bogus_p) + { + if (verbose_p) + fprintf (stderr, "%s: not setting bogus DPMS timeouts: %d %d %d.\n", + blurb(), standby_secs, suspend_secs, off_secs); + return; + } + + if (!DPMSGetTimeouts (dpy, &o_standby, &o_suspend, &o_off)) + { + if (verbose_p) + fprintf (stderr, "%s: unable to get DPMS timeouts.\n", blurb()); + return; + } + + if (o_standby != standby_secs || + o_suspend != suspend_secs || + o_off != off_secs) + { + if (!DPMSSetTimeouts (dpy, standby_secs, suspend_secs, off_secs)) + { + if (verbose_p) + fprintf (stderr, "%s: unable to set DPMS timeouts.\n", blurb()); + return; + } + else if (verbose_p) + fprintf (stderr, "%s: set DPMS timeouts: %d %d %d.\n", blurb(), + standby_secs, suspend_secs, off_secs); + } +} + +Bool +monitor_powered_on_p (saver_info *si) +{ + Bool result; + int event_number, error_number; + BOOL onoff = False; + CARD16 state; + + if (!DPMSQueryExtension(si->dpy, &event_number, &error_number)) + /* Server doesn't know -- assume the monitor is on. */ + result = True; + + else if (!DPMSCapable(si->dpy)) + /* Server says the monitor doesn't do power management -- so it's on. */ + result = True; + + else + { + DPMSInfo(si->dpy, &state, &onoff); + if (!onoff) + /* Server says DPMS is disabled -- so the monitor is on. */ + result = True; + else + switch (state) { + case DPMSModeOn: result = True; break; /* really on */ + case DPMSModeStandby: result = False; break; /* kinda off */ + case DPMSModeSuspend: result = False; break; /* pretty off */ + case DPMSModeOff: result = False; break; /* really off */ + default: result = True; break; /* protocol error? */ + } + } + + return result; +} + +void +monitor_power_on (saver_info *si) +{ + if (!monitor_powered_on_p (si)) + { + DPMSForceLevel(si->dpy, DPMSModeOn); + XSync(si->dpy, False); + if (!monitor_powered_on_p (si)) + fprintf (stderr, + "%s: DPMSForceLevel(dpy, DPMSModeOn) did not power the monitor on?\n", + blurb()); + } +} + +#else /* !HAVE_DPMS_EXTENSION */ + +void +sync_server_dpms_settings (Display *dpy, Bool enabled_p, + int standby_secs, int suspend_secs, int off_secs, + Bool verbose_p) +{ + if (verbose_p) + fprintf (stderr, "%s: DPMS support not compiled in.\n", blurb()); +} + +Bool +monitor_powered_on_p (saver_info *si) +{ + return True; +} + +void +monitor_power_on (saver_info *si) +{ + return; +} + +#endif /* !HAVE_DPMS_EXTENSION */ diff --git a/driver/exec.c b/driver/exec.c new file mode 100644 index 00000000..5da53a01 --- /dev/null +++ b/driver/exec.c @@ -0,0 +1,299 @@ +/* exec.c --- executes a program in *this* pid, without an intervening process. + * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + + +/* I don't believe what a sorry excuse for an operating system UNIX is! + + - I want to spawn a process. + - I want to know it's pid so that I can kill it. + - I would like to receive a message when it dies of natural causes. + - I want the spawned process to have user-specified arguments. + + If shell metacharacters are present (wildcards, backquotes, etc), the + only way to parse those arguments is to run a shell to do the parsing + for you. + + And the only way to know the pid of the process is to fork() and exec() + it in the spawned side of the fork. + + But if you're running a shell to parse your arguments, this gives you + the pid of the *shell*, not the pid of the *process* that you're + actually interested in, which is an *inferior* of the shell. This also + means that the SIGCHLD you get applies to the shell, not its inferior. + (Why isn't that sufficient? I don't remember any more, but it turns + out that it isn't.) + + So, the only solution, when metacharacters are present, is to force the + shell to exec() its inferior. What a fucking hack! We prepend "exec " + to the command string, and hope it doesn't contain unquoted semicolons + or ampersands (we don't search for them, because we don't want to + prohibit their use in quoted strings (messages, for example) and parsing + out the various quote characters is too much of a pain.) + + (Actually, Clint Wong points out that process groups + might be used to take care of this problem; this may be worth considering + some day, except that, 1: this code works now, so why fix it, and 2: from + what I've seen in Emacs, dealing with process groups isn't especially + portable.) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include +#include + +#ifndef ESRCH +# include +#endif + +#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS) +# include /* for setpriority() and PRIO_PROCESS */ + /* and also setrlimit() and RLIMIT_AS */ +#endif + +#ifdef VMS +# include +# include /* for close */ +# include /* for getpid */ +# define pid_t int +# define fork vfork +#endif /* VMS */ + +#include "exec.h" + +extern const char *blurb (void); + +static void nice_process (int nice_level); + + +#ifndef VMS + +static void +exec_simple_command (const char *command) +{ + char *av[1024]; + int ac = 0; + char *token = strtok (strdup(command), " \t"); + while (token) + { + av[ac++] = token; + token = strtok(0, " \t"); + } + av[ac] = 0; + + execvp (av[0], av); /* shouldn't return. */ +} + + +static void +exec_complex_command (const char *shell, const char *command) +{ + char *av[5]; + int ac = 0; + char *command2 = (char *) malloc (strlen (command) + 10); + const char *s; + int got_eq = 0; + const char *after_vars; + + /* Skip leading whitespace. + */ + while (*command == ' ' || *command == '\t') + command++; + + /* If the string has a series of tokens with "=" in them at them, set + `after_vars' to point into the string after those tokens and any + trailing whitespace. Otherwise, after_vars == command. + */ + after_vars = command; + for (s = command; *s; s++) + { + if (*s == '=') got_eq = 1; + else if (*s == ' ') + { + if (got_eq) + { + while (*s == ' ' || *s == '\t') + s++; + after_vars = s; + got_eq = 0; + } + else + break; + } + } + + *command2 = 0; + strncat (command2, command, after_vars - command); + strcat (command2, "exec "); + strcat (command2, after_vars); + + /* We have now done these transformations: + "foo -x -y" ==> "exec foo -x -y" + "BLAT=foop foo -x" ==> "BLAT=foop exec foo -x" + "BLAT=foop A=b foo -x" ==> "BLAT=foop A=b exec foo -x" + */ + + + /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */ + av [ac++] = (char *) shell; + av [ac++] = "-c"; + av [ac++] = command2; + av [ac] = 0; + + execvp (av[0], av); /* shouldn't return. */ +} + +#else /* VMS */ + +static void +exec_vms_command (const char *command) +{ + system (command); + fflush (stderr); + fflush (stdout); + exit (1); /* Note that this only exits a child fork. */ +} + +#endif /* !VMS */ + + +void +exec_command (const char *shell, const char *command, int nice_level) +{ + int hairy_p; + +#ifndef VMS + nice_process (nice_level); + + hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"="); + /* note: = is in the above because of the sh syntax "FOO=bar cmd". */ + + if (getuid() == (uid_t) 0 || geteuid() == (uid_t) 0) + { + /* If you're thinking of commenting this out, think again. + If you do so, you will open a security hole. Mail jwz + so that he may enlighten you as to the error of your ways. + */ + fprintf (stderr, "%s: we're still running as root! Disaster!\n", + blurb()); + exit (-1); + } + + if (hairy_p) + /* If it contains any shell metacharacters, do it the hard way, + and fork a shell to parse the arguments for us. */ + exec_complex_command (shell, command); + else + /* Otherwise, we can just exec the program directly. */ + exec_simple_command (command); + +#else /* VMS */ + exec_vms_command (command); +#endif /* VMS */ +} + + +/* Setting process priority + */ + +static void +nice_process (int nice_level) +{ + if (nice_level == 0) + return; + +#if defined(HAVE_NICE) + { + int old_nice = nice (0); + int n = nice_level - old_nice; + errno = 0; + if (nice (n) == -1 && errno != 0) + { + char buf [512]; + sprintf (buf, "%s: nice(%d) failed", blurb(), n); + perror (buf); + } + } +#elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS) + if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0) + { + char buf [512]; + sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed", + blurb(), (unsigned long) getpid(), nice_level); + perror (buf); + } +#else + fprintf (stderr, + "%s: don't know how to change process priority on this system.\n", + blurb()); + +#endif +} + + +/* Whether the given command exists on $PATH. + (Anything before the first space is considered to be the program name.) + */ +int +on_path_p (const char *program) +{ + int result = 0; + struct stat st; + char *cmd = strdup (program); + char *token = strchr (cmd, ' '); + char *path = 0; + int L; + + if (token) *token = 0; + token = 0; + + if (strchr (cmd, '/')) + { + result = (0 == stat (cmd, &st)); + goto DONE; + } + + path = getenv("PATH"); + if (!path || !*path) + goto DONE; + + L = strlen (cmd); + path = strdup (path); + token = strtok (path, ":"); + + while (token) + { + char *p2 = (char *) malloc (strlen (token) + L + 3); + strcpy (p2, token); + strcat (p2, "/"); + strcat (p2, cmd); + result = (0 == stat (p2, &st)); + if (result) + goto DONE; + token = strtok (0, ":"); + } + + DONE: + free (cmd); + if (path) free (path); + return result; +} + diff --git a/driver/exec.h b/driver/exec.h new file mode 100644 index 00000000..318410b8 --- /dev/null +++ b/driver/exec.h @@ -0,0 +1,21 @@ +/* exec.c --- executes a program in *this* pid, without an intervening process. + * xscreensaver, Copyright (c) 1991-2006 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifndef __XSCREENSAVER_EXEC_H__ +#define __XSCREENSAVER_EXEC_H__ + +extern void exec_command (const char *shell, const char *command, + int nice_level); + +extern int on_path_p (const char *program); + +#endif /* __XSCREENSAVER_EXEC_H__ */ diff --git a/driver/link_axp.com b/driver/link_axp.com new file mode 100644 index 00000000..a1418927 --- /dev/null +++ b/driver/link_axp.com @@ -0,0 +1,15 @@ +$! We fisrt test the version of DECW/Motif ; if 1.2 we need to link with new +$! X11R5 libraries +$@sys$update:decw$get_image_version sys$share:decw$xlibshr.exe decw$version +$ if f$extract(4,3,decw$version).eqs."1.2" +$ then +$! DECW/Motif 1.2 : link with X11R5 libraries +$ link xscreensaver-command,vms_axp_12.opt/opt +$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, - + windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_axp_12.opt/opt +$ else +$! Else, link with X11R4 libraries +$ link xscreensaver-command,vms_axp.opt/opt +$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, - + windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_axp.opt/opt +$ endif diff --git a/driver/link_decc.com b/driver/link_decc.com new file mode 100644 index 00000000..d1de0d0a --- /dev/null +++ b/driver/link_decc.com @@ -0,0 +1,15 @@ +$! We fisrt test the version of DECW/Motif ; if 1.2 we need to link with new +$! X11R5 libraries +$@sys$update:decw$get_image_version sys$share:decw$xlibshr.exe decw$version +$ if f$extract(4,3,decw$version).eqs."1.2" +$ then +$! DECW/Motif 1.2 : link with X11R5 libraries +$ link xscreensaver-command,vms_decc_12.opt/opt +$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, - + windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_decc_12.opt/opt +$ else +$! Else, link with X11R4 libraries +$ link xscreensaver-command,vms_decc.opt/opt +$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, - + windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_decc.opt/opt +$ endif diff --git a/driver/lock.c b/driver/lock.c new file mode 100644 index 00000000..2f3108c1 --- /dev/null +++ b/driver/lock.c @@ -0,0 +1,2118 @@ +/* lock.c --- handling the password dialog for locking-mode. + * xscreensaver, Copyright (c) 1993-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* Athena locking code contributed by Jon A. Christopher */ +/* Copyright 1997, with the same permissions as above. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include /* for time() */ +#include +#include +#include "xscreensaver.h" +#include "resources.h" +#include "mlstring.h" +#include "auth.h" + +#ifndef NO_LOCKING /* (mostly) whole file */ + +#ifdef HAVE_XHPDISABLERESET +# include + static void hp_lock_reset (saver_info *si, Bool lock_p); +#endif /* HAVE_XHPDISABLERESET */ + +#ifdef HAVE_XF86VMODE +# include + static void xfree_lock_mode_switch (saver_info *si, Bool lock_p); +#endif /* HAVE_XF86VMODE */ + +#ifdef HAVE_XF86MISCSETGRABKEYSSTATE +# include + static void xfree_lock_grab_smasher (saver_info *si, Bool lock_p); +#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */ + + +#ifdef _VROOT_H_ +ERROR! You must not include vroot.h in this file. +#endif + +#ifdef HAVE_UNAME +# include /* for hostname info */ +#endif /* HAVE_UNAME */ +#include + +#ifndef VMS +# include +#else /* VMS */ + +extern char *getenv(const char *name); +extern int validate_user(char *name, char *password); + +static Bool +vms_passwd_valid_p(char *pw, Bool verbose_p) +{ + return (validate_user (getenv("USER"), typed_passwd) == 1); +} +# undef passwd_valid_p +# define passwd_valid_p vms_passwd_valid_p + +#endif /* VMS */ + +#define SAMPLE_INPUT "MMMMMMMMMMMM" + + +#undef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) + +typedef struct info_dialog_data info_dialog_data; + +struct passwd_dialog_data { + + saver_screen_info *prompt_screen; + int previous_mouse_x, previous_mouse_y; + + char typed_passwd [80]; + XtIntervalId timer; + int i_beam; + + float ratio; + Position x, y; + Dimension width; + Dimension height; + Dimension border_width; + + Bool echo_input; + Bool show_stars_p; /* "I regret that I have but one asterisk for my country." + -- Nathan Hale, 1776. */ + + char *heading_label; + char *body_label; + char *user_label; + mlstring *info_label; + /* The entry field shall only be displayed if prompt_label is not NULL */ + mlstring *prompt_label; + char *date_label; + char *passwd_string; + Bool passwd_changed_p; /* Whether the user entry field needs redrawing */ + Bool caps_p; /* Whether we saw a keypress with caps-lock on */ + char *unlock_label; + char *login_label; + char *uname_label; + + Bool show_uname_p; + + XFontStruct *heading_font; + XFontStruct *body_font; + XFontStruct *label_font; + XFontStruct *passwd_font; + XFontStruct *date_font; + XFontStruct *button_font; + XFontStruct *uname_font; + + Pixel foreground; + Pixel background; + Pixel passwd_foreground; + Pixel passwd_background; + Pixel thermo_foreground; + Pixel thermo_background; + Pixel shadow_top; + Pixel shadow_bottom; + Pixel button_foreground; + Pixel button_background; + + Dimension preferred_logo_width, logo_width; + Dimension preferred_logo_height, logo_height; + Dimension thermo_width; + Dimension internal_border; + Dimension shadow_width; + + Dimension passwd_field_x, passwd_field_y; + Dimension passwd_field_width, passwd_field_height; + + Dimension unlock_button_x, unlock_button_y; + Dimension unlock_button_width, unlock_button_height; + + Dimension login_button_x, login_button_y; + Dimension login_button_width, login_button_height; + + Dimension thermo_field_x, thermo_field_y; + Dimension thermo_field_height; + + Pixmap logo_pixmap; + Pixmap logo_clipmask; + int logo_npixels; + unsigned long *logo_pixels; + + Cursor passwd_cursor; + Bool unlock_button_down_p; + Bool login_button_down_p; + Bool login_button_p; + Bool login_button_enabled_p; + Bool button_state_changed_p; /* Refers to both buttons */ + + Pixmap save_under; + Pixmap user_entry_pixmap; +}; + +static void draw_passwd_window (saver_info *si); +static void update_passwd_window (saver_info *si, const char *printed_passwd, + float ratio); +static void destroy_passwd_window (saver_info *si); +static void undo_vp_motion (saver_info *si); +static void finished_typing_passwd (saver_info *si, passwd_dialog_data *pw); +static void cleanup_passwd_window (saver_info *si); +static void restore_background (saver_info *si); + +extern void xss_authenticate(saver_info *si, Bool verbose_p); + +static int +new_passwd_window (saver_info *si) +{ + passwd_dialog_data *pw; + Screen *screen; + Colormap cmap; + char *f; + saver_screen_info *ssi = &si->screens [mouse_screen (si)]; + + pw = (passwd_dialog_data *) calloc (1, sizeof(*pw)); + if (!pw) + return -1; + + /* Display the button only if the "newLoginCommand" pref is non-null. + */ + pw->login_button_p = (si->prefs.new_login_command && + *si->prefs.new_login_command); + + pw->passwd_cursor = XCreateFontCursor (si->dpy, XC_top_left_arrow); + + pw->prompt_screen = ssi; + + screen = pw->prompt_screen->screen; + cmap = DefaultColormapOfScreen (screen); + + pw->show_stars_p = get_boolean_resource(si->dpy, "passwd.asterisks", + "Boolean"); + + pw->heading_label = get_string_resource (si->dpy, "passwd.heading.label", + "Dialog.Label.Label"); + pw->body_label = get_string_resource (si->dpy, "passwd.body.label", + "Dialog.Label.Label"); + pw->user_label = get_string_resource (si->dpy, "passwd.user.label", + "Dialog.Label.Label"); + pw->unlock_label = get_string_resource (si->dpy, "passwd.unlock.label", + "Dialog.Button.Label"); + pw->login_label = get_string_resource (si->dpy, "passwd.login.label", + "Dialog.Button.Label"); + + pw->date_label = get_string_resource (si->dpy, "dateFormat", "DateFormat"); + + if (!pw->heading_label) + pw->heading_label = strdup("ERROR: RESOURCES NOT INSTALLED CORRECTLY"); + if (!pw->body_label) + pw->body_label = strdup("ERROR: RESOURCES NOT INSTALLED CORRECTLY"); + if (!pw->user_label) pw->user_label = strdup("ERROR"); + if (!pw->date_label) pw->date_label = strdup("ERROR"); + if (!pw->unlock_label) pw->unlock_label = strdup("ERROR (UNLOCK)"); + if (!pw->login_label) pw->login_label = strdup ("ERROR (LOGIN)") ; + + /* Put the version number in the label. */ + { + char *s = (char *) malloc (strlen(pw->heading_label) + 20); + sprintf(s, pw->heading_label, si->version); + free (pw->heading_label); + pw->heading_label = s; + } + + /* Get hostname info */ + pw->uname_label = strdup(""); /* Initialy, write nothing */ + +# ifdef HAVE_UNAME + { + struct utsname uts; + + if (uname (&uts) == 0) + { +#if 0 /* Get the full hostname */ + { + char *s; + if ((s = strchr(uts.nodename, '.'))) + *s = 0; + } +#endif + char *s = strdup (uts.nodename); + free (pw->uname_label); + pw->uname_label = s; + } + } +# endif + + pw->passwd_string = strdup(""); + + f = get_string_resource (si->dpy, "passwd.headingFont", "Dialog.Font"); + pw->heading_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->heading_font) pw->heading_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource (si->dpy, "passwd.buttonFont", "Dialog.Font"); + pw->button_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->button_font) pw->button_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "passwd.bodyFont", "Dialog.Font"); + pw->body_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->body_font) pw->body_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "passwd.labelFont", "Dialog.Font"); + pw->label_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->label_font) pw->label_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "passwd.passwdFont", "Dialog.Font"); + pw->passwd_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->passwd_font) pw->passwd_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "passwd.dateFont", "Dialog.Font"); + pw->date_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->date_font) pw->date_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "passwd.unameFont", "Dialog.Font"); + pw->uname_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!pw->uname_font) pw->uname_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + pw->show_uname_p = get_boolean_resource(si->dpy, "passwd.uname", "Boolean"); + + pw->foreground = get_pixel_resource (si->dpy, cmap, + "passwd.foreground", + "Dialog.Foreground" ); + pw->background = get_pixel_resource (si->dpy, cmap, + "passwd.background", + "Dialog.Background" ); + + if (pw->foreground == pw->background) + { + /* Make sure the error messages show up. */ + pw->foreground = BlackPixelOfScreen (screen); + pw->background = WhitePixelOfScreen (screen); + } + + pw->passwd_foreground = get_pixel_resource (si->dpy, cmap, + "passwd.text.foreground", + "Dialog.Text.Foreground" ); + pw->passwd_background = get_pixel_resource (si->dpy, cmap, + "passwd.text.background", + "Dialog.Text.Background" ); + pw->button_foreground = get_pixel_resource (si->dpy, cmap, + "splash.Button.foreground", + "Dialog.Button.Foreground" ); + pw->button_background = get_pixel_resource (si->dpy, cmap, + "splash.Button.background", + "Dialog.Button.Background" ); + pw->thermo_foreground = get_pixel_resource (si->dpy, cmap, + "passwd.thermometer.foreground", + "Dialog.Thermometer.Foreground" ); + pw->thermo_background = get_pixel_resource ( si->dpy, cmap, + "passwd.thermometer.background", + "Dialog.Thermometer.Background" ); + pw->shadow_top = get_pixel_resource ( si->dpy, cmap, + "passwd.topShadowColor", + "Dialog.Foreground" ); + pw->shadow_bottom = get_pixel_resource (si->dpy, cmap, + "passwd.bottomShadowColor", + "Dialog.Background" ); + + pw->preferred_logo_width = get_integer_resource (si->dpy, "passwd.logo.width", + "Dialog.Logo.Width"); + pw->preferred_logo_height = get_integer_resource (si->dpy, "passwd.logo.height", + "Dialog.Logo.Height"); + pw->thermo_width = get_integer_resource (si->dpy, "passwd.thermometer.width", + "Dialog.Thermometer.Width"); + pw->internal_border = get_integer_resource (si->dpy, "passwd.internalBorderWidth", + "Dialog.InternalBorderWidth"); + pw->shadow_width = get_integer_resource (si->dpy, "passwd.shadowThickness", + "Dialog.ShadowThickness"); + + if (pw->preferred_logo_width == 0) pw->preferred_logo_width = 150; + if (pw->preferred_logo_height == 0) pw->preferred_logo_height = 150; + if (pw->internal_border == 0) pw->internal_border = 15; + if (pw->shadow_width == 0) pw->shadow_width = 4; + if (pw->thermo_width == 0) pw->thermo_width = pw->shadow_width; + + + /* We need to remember the mouse position and restore it afterward, or + sometimes (perhaps only with Xinerama?) the mouse gets warped to + inside the bounds of the lock dialog window. + */ + { + Window pointer_root, pointer_child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + pw->previous_mouse_x = 0; + pw->previous_mouse_y = 0; + if (XQueryPointer (si->dpy, RootWindowOfScreen (pw->prompt_screen->screen), + &pointer_root, &pointer_child, + &root_x, &root_y, &win_x, &win_y, &mask)) + { + pw->previous_mouse_x = root_x; + pw->previous_mouse_y = root_y; + if (si->prefs.verbose_p) + fprintf (stderr, "%s: %d: mouse is at %d,%d.\n", + blurb(), pw->prompt_screen->number, + pw->previous_mouse_x, pw->previous_mouse_y); + } + else if (si->prefs.verbose_p) + fprintf (stderr, "%s: %d: unable to determine mouse position?\n", + blurb(), pw->prompt_screen->number); + } + + /* Before mapping the window, save a pixmap of the current screen. + When we lower the window, we + restore these bits. This works, because the running screenhack + has already been sent SIGSTOP, so we know nothing else is drawing + right now! */ + { + XGCValues gcv; + GC gc; + pw->save_under = XCreatePixmap (si->dpy, + pw->prompt_screen->screensaver_window, + pw->prompt_screen->width, + pw->prompt_screen->height, + pw->prompt_screen->current_depth); + gcv.function = GXcopy; + gc = XCreateGC (si->dpy, pw->save_under, GCFunction, &gcv); + XCopyArea (si->dpy, pw->prompt_screen->screensaver_window, + pw->save_under, gc, + 0, 0, + pw->prompt_screen->width, pw->prompt_screen->height, + 0, 0); + XFreeGC (si->dpy, gc); + } + + si->pw_data = pw; + return 0; +} + + +/** + * info_msg and prompt may be NULL. + */ +static int +make_passwd_window (saver_info *si, + const char *info_msg, + const char *prompt, + Bool echo) +{ + XSetWindowAttributes attrs; + unsigned long attrmask = 0; + passwd_dialog_data *pw; + Screen *screen; + Colormap cmap; + Dimension max_string_width_px; + saver_screen_info *ssi = &si->screens [mouse_screen (si)]; + + cleanup_passwd_window (si); + + if (! ssi) /* WTF? Trying to prompt while no screens connected? */ + return -1; + + if (!si->pw_data) + if (new_passwd_window (si) < 0) + return -1; + + if (!(pw = si->pw_data)) + return -1; + + pw->ratio = 1.0; + + pw->prompt_screen = ssi; + if (si->prefs.verbose_p) + fprintf (stderr, "%s: %d: creating password dialog (\"%s\")\n", + blurb(), pw->prompt_screen->number, + info_msg ? info_msg : ""); + + screen = pw->prompt_screen->screen; + cmap = DefaultColormapOfScreen (screen); + + pw->echo_input = echo; + + max_string_width_px = ssi->width + - pw->shadow_width * 4 + - pw->border_width * 2 + - pw->thermo_width + - pw->preferred_logo_width + - pw->internal_border * 2; + /* As the string wraps it makes the window taller which makes the logo wider + * which leaves less room for the text which makes the string wrap. Uh-oh, a + * loop. By wrapping at a bit less than the available width, there's some + * room for the dialog to grow without going off the edge of the screen. */ + max_string_width_px *= 0.75; + + pw->info_label = mlstring_new(info_msg ? info_msg : pw->body_label, + pw->label_font, max_string_width_px); + + { + int direction, ascent, descent; + XCharStruct overall; + + pw->width = 0; + pw->height = 0; + + /* Measure the heading_label. */ + XTextExtents (pw->heading_font, + pw->heading_label, strlen(pw->heading_label), + &direction, &ascent, &descent, &overall); + if (overall.width > pw->width) pw->width = overall.width; + pw->height += ascent + descent; + + /* Measure the uname_label. */ + if ((strlen(pw->uname_label)) && pw->show_uname_p) + { + XTextExtents (pw->uname_font, + pw->uname_label, strlen(pw->uname_label), + &direction, &ascent, &descent, &overall); + if (overall.width > pw->width) pw->width = overall.width; + pw->height += ascent + descent; + } + + { + Dimension w2 = 0, w3 = 0, button_w = 0; + Dimension h2 = 0, h3 = 0, button_h = 0; + const char *passwd_string = SAMPLE_INPUT; + + /* Measure the user_label. */ + XTextExtents (pw->label_font, + pw->user_label, strlen(pw->user_label), + &direction, &ascent, &descent, &overall); + if (overall.width > w2) w2 = overall.width; + h2 += ascent + descent; + + /* Measure the info_label. */ + if (pw->info_label->overall_width > pw->width) pw->width = pw->info_label->overall_width; + h2 += pw->info_label->overall_height; + + /* Measure the user string. */ + XTextExtents (pw->passwd_font, + si->user, strlen(si->user), + &direction, &ascent, &descent, &overall); + overall.width += (pw->shadow_width * 4); + ascent += (pw->shadow_width * 4); + if (overall.width > w3) w3 = overall.width; + h3 += ascent + descent; + + /* Measure the (dummy) passwd_string. */ + if (prompt) + { + XTextExtents (pw->passwd_font, + passwd_string, strlen(passwd_string), + &direction, &ascent, &descent, &overall); + overall.width += (pw->shadow_width * 4); + ascent += (pw->shadow_width * 4); + if (overall.width > w3) w3 = overall.width; + h3 += ascent + descent; + + /* Measure the prompt_label. */ + max_string_width_px -= w3; + pw->prompt_label = mlstring_new(prompt, pw->label_font, max_string_width_px); + + if (pw->prompt_label->overall_width > w2) w2 = pw->prompt_label->overall_width; + + h2 += pw->prompt_label->overall_height; + + w2 = w2 + w3 + (pw->shadow_width * 2); + h2 = MAX (h2, h3); + } + + /* The "Unlock" button. */ + XTextExtents (pw->label_font, + pw->unlock_label, strlen(pw->unlock_label), + &direction, &ascent, &descent, &overall); + button_w = overall.width; + button_h = ascent + descent; + + /* Add some horizontal padding inside the button. */ + button_w += ascent; + + button_w += ((ascent + descent) / 2) + (pw->shadow_width * 2); + button_h += ((ascent + descent) / 2) + (pw->shadow_width * 2); + + pw->unlock_button_width = button_w; + pw->unlock_button_height = button_h; + + w2 = MAX (w2, button_w); + h2 += button_h * 1.5; + + /* The "New Login" button */ + pw->login_button_width = 0; + pw->login_button_height = 0; + + if (pw->login_button_p) + { + pw->login_button_enabled_p = True; + + /* Measure the "New Login" button */ + XTextExtents (pw->button_font, pw->login_label, + strlen (pw->login_label), + &direction, &ascent, &descent, &overall); + button_w = overall.width; + button_h = ascent + descent; + + /* Add some horizontal padding inside the buttons. */ + button_w += ascent; + + button_w += ((ascent + descent) / 2) + (pw->shadow_width * 2); + button_h += ((ascent + descent) / 2) + (pw->shadow_width * 2); + + pw->login_button_width = button_w; + pw->login_button_height = button_h; + + if (button_h > pw->unlock_button_height) + h2 += (button_h * 1.5 - pw->unlock_button_height * 1.5); + + /* Use (2 * shadow_width) spacing between the buttons. Another + (2 * shadow_width) is required to account for button shadows. */ + w2 = MAX (w2, button_w + pw->unlock_button_width + (pw->shadow_width * 4)); + } + + if (w2 > pw->width) pw->width = w2; + pw->height += h2; + } + + pw->width += (pw->internal_border * 2); + pw->height += (pw->internal_border * 4); + + pw->width += pw->thermo_width + (pw->shadow_width * 3); + + if (pw->preferred_logo_height > pw->height) + pw->height = pw->logo_height = pw->preferred_logo_height; + else if (pw->height > pw->preferred_logo_height) + pw->logo_height = pw->height; + + pw->logo_width = pw->logo_height; + + pw->width += pw->logo_width; + } + + attrmask |= CWOverrideRedirect; attrs.override_redirect = True; + + attrmask |= CWEventMask; + attrs.event_mask = (ExposureMask | KeyPressMask | + ButtonPressMask | ButtonReleaseMask); + + /* Figure out where on the desktop to place the window so that it will + actually be visible; this takes into account virtual viewports as + well as Xinerama. */ + { + saver_screen_info *ssi = &si->screens [mouse_screen (si)]; + int x = ssi->x; + int y = ssi->y; + int w = ssi->width; + int h = ssi->height; + if (si->prefs.debug_p) w /= 2; + pw->x = x + ((w + pw->width) / 2) - pw->width; + pw->y = y + ((h + pw->height) / 2) - pw->height; + if (pw->x < x) pw->x = x; + if (pw->y < y) pw->y = y; + } + + pw->border_width = get_integer_resource (si->dpy, "passwd.borderWidth", + "Dialog.BorderWidth"); + + /* Only create the window the first time around */ + if (!si->passwd_dialog) + { + si->passwd_dialog = + XCreateWindow (si->dpy, + RootWindowOfScreen(screen), + pw->x, pw->y, pw->width, pw->height, pw->border_width, + DefaultDepthOfScreen (screen), InputOutput, + DefaultVisualOfScreen(screen), + attrmask, &attrs); + XSetWindowBackground (si->dpy, si->passwd_dialog, pw->background); + + /* We use the default visual, not ssi->visual, so that the logo pixmap's + visual matches that of the si->passwd_dialog window. */ + pw->logo_pixmap = xscreensaver_logo (ssi->screen, + /* ssi->current_visual, */ + DefaultVisualOfScreen(screen), + si->passwd_dialog, cmap, + pw->background, + &pw->logo_pixels, &pw->logo_npixels, + &pw->logo_clipmask, True); + } + else /* On successive prompts, just resize the window */ + { + XWindowChanges wc; + unsigned int mask = CWX | CWY | CWWidth | CWHeight; + + wc.x = pw->x; + wc.y = pw->y; + wc.width = pw->width; + wc.height = pw->height; + + XConfigureWindow (si->dpy, si->passwd_dialog, mask, &wc); + } + + restore_background(si); + + XMapRaised (si->dpy, si->passwd_dialog); + XSync (si->dpy, False); + + move_mouse_grab (si, si->passwd_dialog, + pw->passwd_cursor, + pw->prompt_screen->number); + undo_vp_motion (si); + + si->pw_data = pw; + + if (cmap) + XInstallColormap (si->dpy, cmap); + draw_passwd_window (si); + + return 0; +} + + +static void +draw_passwd_window (saver_info *si) +{ + passwd_dialog_data *pw = si->pw_data; + XGCValues gcv; + GC gc1, gc2; + int spacing, height; + int x1, x2, x3, y1, y2; + int sw; + int tb_height; + + /* Force redraw */ + pw->passwd_changed_p = True; + pw->button_state_changed_p = True; + + /* This height is the height of all the elements, not to be confused with + * the overall window height which is pw->height. It is used to compute + * the amount of spacing (padding) between elements. */ + height = (pw->heading_font->ascent + pw->heading_font->descent + + pw->info_label->overall_height + + MAX (((pw->label_font->ascent + pw->label_font->descent) + + (pw->prompt_label ? pw->prompt_label->overall_height : 0)), + ((pw->passwd_font->ascent + pw->passwd_font->descent) + + (pw->shadow_width * 2)) * (pw->prompt_label ? 2 : 1)) + + pw->date_font->ascent + pw->date_font->descent); + + if ((strlen(pw->uname_label)) && pw->show_uname_p) + height += (pw->uname_font->ascent + pw->uname_font->descent); /* for uname */ + + height += ((pw->button_font->ascent + pw->button_font->descent) * 2 + + 2 * pw->shadow_width); + + spacing = ((pw->height - 2 * pw->shadow_width + - pw->internal_border - height) + / 10); + + if (spacing < 0) spacing = 0; + + gcv.foreground = pw->foreground; + gc1 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv); + gc2 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv); + x1 = pw->logo_width + pw->thermo_width + (pw->shadow_width * 3); + x3 = pw->width - (pw->shadow_width * 2); + y1 = (pw->shadow_width * 2) + spacing + spacing; + + /* top heading + */ + XSetFont (si->dpy, gc1, pw->heading_font->fid); + sw = string_width (pw->heading_font, pw->heading_label); + x2 = (x1 + ((x3 - x1 - sw) / 2)); + y1 += spacing + pw->heading_font->ascent + pw->heading_font->descent; + XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1, + pw->heading_label, strlen(pw->heading_label)); + + /* uname below top heading + */ + if ((strlen(pw->uname_label)) && pw->show_uname_p) + { + XSetFont (si->dpy, gc1, pw->uname_font->fid); + y1 += spacing + pw->uname_font->ascent + pw->uname_font->descent; + sw = string_width (pw->uname_font, pw->uname_label); + x2 = (x1 + ((x3 - x1 - sw) / 2)); + XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1, + pw->uname_label, strlen(pw->uname_label)); + } + + /* the info_label (below uname) + */ + x2 = (x1 + ((x3 - x1 - pw->info_label->overall_width) / 2)); + y1 += spacing + pw->info_label->font_height / 2; + mlstring_draw(si->dpy, si->passwd_dialog, gc1, pw->info_label, + x2, y1); + y1 += pw->info_label->overall_height; + + + tb_height = (pw->passwd_font->ascent + pw->passwd_font->descent + + (pw->shadow_width * 4)); + + /* the "User:" prompt + */ + y2 = y1; + XSetForeground (si->dpy, gc1, pw->foreground); + XSetFont (si->dpy, gc1, pw->label_font->fid); + y1 += (spacing + tb_height + pw->shadow_width); + x2 = (x1 + pw->internal_border + + MAX(string_width (pw->label_font, pw->user_label), + pw->prompt_label ? pw->prompt_label->overall_width : 0)); + XDrawString (si->dpy, si->passwd_dialog, gc1, + x2 - string_width (pw->label_font, pw->user_label), + y1 - pw->passwd_font->descent, + pw->user_label, strlen(pw->user_label)); + + /* the prompt_label prompt + */ + if (pw->prompt_label) + { + y1 += tb_height - pw->label_font->ascent + pw->shadow_width; + mlstring_draw(si->dpy, si->passwd_dialog, gc1, pw->prompt_label, + x2 - pw->prompt_label->overall_width, y1); + } + + /* the "user name" text field + */ + y1 = y2; + XSetForeground (si->dpy, gc1, pw->passwd_foreground); + XSetForeground (si->dpy, gc2, pw->passwd_background); + XSetFont (si->dpy, gc1, pw->passwd_font->fid); + y1 += (spacing + tb_height); + x2 += (pw->shadow_width * 4); + + pw->passwd_field_width = x3 - x2 - pw->internal_border; + pw->passwd_field_height = (pw->passwd_font->ascent + + pw->passwd_font->descent + + pw->shadow_width); + + XFillRectangle (si->dpy, si->passwd_dialog, gc2, + x2 - pw->shadow_width, + y1 - (pw->passwd_font->ascent + pw->passwd_font->descent), + pw->passwd_field_width, pw->passwd_field_height); + XDrawString (si->dpy, si->passwd_dialog, gc1, + x2, + y1 - pw->passwd_font->descent, + si->user, strlen(si->user)); + + /* the password/prompt text field + */ + if (pw->prompt_label) + { + y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width * 2); + + pw->passwd_field_x = x2 - pw->shadow_width; + pw->passwd_field_y = y1 - (pw->passwd_font->ascent + + pw->passwd_font->descent); + } + + /* The shadow around the text fields + */ + y1 = y2; + y1 += (spacing + (pw->shadow_width * 3)); + x1 = x2 - (pw->shadow_width * 2); + x2 = pw->passwd_field_width + (pw->shadow_width * 2); + y2 = pw->passwd_field_height + (pw->shadow_width * 2); + + draw_shaded_rectangle (si->dpy, si->passwd_dialog, + x1, y1, x2, y2, + pw->shadow_width, + pw->shadow_bottom, pw->shadow_top); + + if (pw->prompt_label) + { + y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width * 2); + draw_shaded_rectangle (si->dpy, si->passwd_dialog, + x1, y1, x2, y2, + pw->shadow_width, + pw->shadow_bottom, pw->shadow_top); + } + + + /* The date, below the text fields + */ + { + char buf[100]; + time_t now = time ((time_t *) 0); + struct tm *tm = localtime (&now); + memset (buf, 0, sizeof(buf)); + strftime (buf, sizeof(buf)-1, pw->date_label, tm); + + XSetFont (si->dpy, gc1, pw->date_font->fid); + y1 += pw->shadow_width; + y1 += (spacing + tb_height); + y1 += spacing/2; + sw = string_width (pw->date_font, buf); + x2 = x1 + x2 - sw; + XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1, buf, strlen(buf)); + } + + /* Set up the GCs for the "New Login" and "Unlock" buttons. + */ + XSetForeground(si->dpy, gc1, pw->button_foreground); + XSetForeground(si->dpy, gc2, pw->button_background); + XSetFont(si->dpy, gc1, pw->button_font->fid); + + /* The "Unlock" button */ + x2 = pw->width - pw->internal_border - (pw->shadow_width * 2); + + /* right aligned button */ + x1 = x2 - pw->unlock_button_width; + + /* Add half the difference between y1 and the internal edge. + * It actually looks better if the internal border is ignored. */ + y1 += ((pw->height - MAX (pw->unlock_button_height, pw->login_button_height) + - spacing - y1) + / 2); + + pw->unlock_button_x = x1; + pw->unlock_button_y = y1; + + /* The "New Login" button + */ + if (pw->login_button_p) + { + /* Using the same GC as for the Unlock button */ + + sw = string_width (pw->button_font, pw->login_label); + + /* left aligned button */ + x1 = (pw->logo_width + pw->thermo_width + (pw->shadow_width * 3) + + pw->internal_border); + + pw->login_button_x = x1; + pw->login_button_y = y1; + } + + /* The logo + */ + x1 = pw->shadow_width * 6; + y1 = pw->shadow_width * 6; + x2 = pw->logo_width - (pw->shadow_width * 12); + y2 = pw->logo_height - (pw->shadow_width * 12); + + if (pw->logo_pixmap) + { + Window root; + int x, y; + unsigned int w, h, bw, d; + XGetGeometry (si->dpy, pw->logo_pixmap, &root, &x, &y, &w, &h, &bw, &d); + XSetForeground (si->dpy, gc1, pw->foreground); + XSetBackground (si->dpy, gc1, pw->background); + XSetClipMask (si->dpy, gc1, pw->logo_clipmask); + XSetClipOrigin (si->dpy, gc1, x1 + ((x2 - (int)w) / 2), y1 + ((y2 - (int)h) / 2)); + if (d == 1) + XCopyPlane (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1, + 0, 0, w, h, + x1 + ((x2 - (int)w) / 2), + y1 + ((y2 - (int)h) / 2), + 1); + else + XCopyArea (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1, + 0, 0, w, h, + x1 + ((x2 - (int)w) / 2), + y1 + ((y2 - (int)h) / 2)); + } + + /* The thermometer + */ + XSetForeground (si->dpy, gc1, pw->thermo_foreground); + XSetForeground (si->dpy, gc2, pw->thermo_background); + + pw->thermo_field_x = pw->logo_width + pw->shadow_width; + pw->thermo_field_y = pw->shadow_width * 5; + pw->thermo_field_height = pw->height - (pw->shadow_width * 10); + +#if 0 + /* Solid border inside the logo box. */ + XSetForeground (si->dpy, gc1, pw->foreground); + XDrawRectangle (si->dpy, si->passwd_dialog, gc1, x1, y1, x2-1, y2-1); +#endif + + /* The shadow around the logo + */ + draw_shaded_rectangle (si->dpy, si->passwd_dialog, + pw->shadow_width * 4, + pw->shadow_width * 4, + pw->logo_width - (pw->shadow_width * 8), + pw->logo_height - (pw->shadow_width * 8), + pw->shadow_width, + pw->shadow_bottom, pw->shadow_top); + + /* The shadow around the thermometer + */ + draw_shaded_rectangle (si->dpy, si->passwd_dialog, + pw->logo_width, + pw->shadow_width * 4, + pw->thermo_width + (pw->shadow_width * 2), + pw->height - (pw->shadow_width * 8), + pw->shadow_width, + pw->shadow_bottom, pw->shadow_top); + +#if 1 + /* Solid border inside the thermometer. */ + XSetForeground (si->dpy, gc1, pw->foreground); + XDrawRectangle (si->dpy, si->passwd_dialog, gc1, + pw->thermo_field_x, pw->thermo_field_y, + pw->thermo_width - 1, pw->thermo_field_height - 1); +#endif + + /* The shadow around the whole window + */ + draw_shaded_rectangle (si->dpy, si->passwd_dialog, + 0, 0, pw->width, pw->height, pw->shadow_width, + pw->shadow_top, pw->shadow_bottom); + + XFreeGC (si->dpy, gc1); + XFreeGC (si->dpy, gc2); + + update_passwd_window (si, pw->passwd_string, pw->ratio); +} + +static void +draw_button(Display *dpy, + Drawable dialog, + XFontStruct *font, + unsigned long foreground, unsigned long background, + char *label, + int x, int y, + int width, int height, + int shadow_width, + Pixel shadow_light, Pixel shadow_dark, + Bool button_down) +{ + XGCValues gcv; + GC gc1, gc2; + int sw; + int label_x, label_y; + + gcv.foreground = foreground; + gcv.font = font->fid; + gc1 = XCreateGC(dpy, dialog, GCForeground|GCFont, &gcv); + gcv.foreground = background; + gc2 = XCreateGC(dpy, dialog, GCForeground, &gcv); + + XFillRectangle(dpy, dialog, gc2, + x, y, width, height); + + sw = string_width(font, label); + + label_x = x + ((width - sw) / 2); + label_y = (y + (height - (font->ascent + font->descent)) / 2 + font->ascent); + + if (button_down) + { + label_x += 2; + label_y += 2; + } + + XDrawString(dpy, dialog, gc1, label_x, label_y, label, strlen(label)); + + XFreeGC(dpy, gc1); + XFreeGC(dpy, gc2); + + draw_shaded_rectangle(dpy, dialog, x, y, width, height, + shadow_width, shadow_light, shadow_dark); +} + +static void +update_passwd_window (saver_info *si, const char *printed_passwd, float ratio) +{ + passwd_dialog_data *pw = si->pw_data; + XGCValues gcv; + GC gc1, gc2; + int x, y; + XRectangle rects[1]; + + pw->ratio = ratio; + gcv.foreground = pw->passwd_foreground; + gcv.font = pw->passwd_font->fid; + gc1 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground|GCFont, &gcv); + gcv.foreground = pw->passwd_background; + gc2 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv); + + if (printed_passwd) + { + char *s = strdup (printed_passwd); + if (pw->passwd_string) free (pw->passwd_string); + pw->passwd_string = s; + } + + if (pw->prompt_label) + { + + /* the "password" text field + */ + rects[0].x = pw->passwd_field_x; + rects[0].y = pw->passwd_field_y; + rects[0].width = pw->passwd_field_width; + rects[0].height = pw->passwd_field_height; + + /* The user entry (password) field is double buffered. + * This avoids flickering, particularly in synchronous mode. */ + + if (pw->passwd_changed_p) + { + pw->passwd_changed_p = False; + + if (pw->user_entry_pixmap) + { + XFreePixmap(si->dpy, pw->user_entry_pixmap); + pw->user_entry_pixmap = 0; + } + + pw->user_entry_pixmap = + XCreatePixmap (si->dpy, si->passwd_dialog, + rects[0].width, rects[0].height, + DefaultDepthOfScreen (pw->prompt_screen->screen)); + + XFillRectangle (si->dpy, pw->user_entry_pixmap, gc2, + 0, 0, rects[0].width, rects[0].height); + + XDrawString (si->dpy, pw->user_entry_pixmap, gc1, + pw->shadow_width, + pw->passwd_font->ascent, + pw->passwd_string, strlen(pw->passwd_string)); + + /* Ensure the new pixmap gets copied to the window */ + pw->i_beam = 0; + + } + + /* The I-beam + */ + if (pw->i_beam == 0) + { + /* Make the I-beam disappear */ + XCopyArea(si->dpy, pw->user_entry_pixmap, si->passwd_dialog, gc2, + 0, 0, rects[0].width, rects[0].height, + rects[0].x, rects[0].y); + } + else if (pw->i_beam == 1) + { + /* Make the I-beam appear */ + x = (rects[0].x + pw->shadow_width + + string_width (pw->passwd_font, pw->passwd_string)); + y = rects[0].y + pw->shadow_width; + + if (x > rects[0].x + rects[0].width - 1) + x = rects[0].x + rects[0].width - 1; + XDrawLine (si->dpy, si->passwd_dialog, gc1, + x, y, + x, y + pw->passwd_font->ascent + pw->passwd_font->descent-1); + } + + pw->i_beam = (pw->i_beam + 1) % 4; + + } + + /* the thermometer + */ + y = (pw->thermo_field_height - 2) * (1.0 - pw->ratio); + if (y > 0) + { + XFillRectangle (si->dpy, si->passwd_dialog, gc2, + pw->thermo_field_x + 1, + pw->thermo_field_y + 1, + pw->thermo_width-2, + y); + XSetForeground (si->dpy, gc1, pw->thermo_foreground); + XFillRectangle (si->dpy, si->passwd_dialog, gc1, + pw->thermo_field_x + 1, + pw->thermo_field_y + 1 + y, + pw->thermo_width-2, + MAX (0, pw->thermo_field_height - y - 2)); + } + + if (pw->button_state_changed_p) + { + pw->button_state_changed_p = False; + + /* The "Unlock" button + */ + draw_button(si->dpy, si->passwd_dialog, pw->button_font, + pw->button_foreground, pw->button_background, + pw->unlock_label, + pw->unlock_button_x, pw->unlock_button_y, + pw->unlock_button_width, pw->unlock_button_height, + pw->shadow_width, + (pw->unlock_button_down_p ? pw->shadow_bottom : pw->shadow_top), + (pw->unlock_button_down_p ? pw->shadow_top : pw->shadow_bottom), + pw->unlock_button_down_p); + + /* The "New Login" button + */ + if (pw->login_button_p) + { + draw_button(si->dpy, si->passwd_dialog, pw->button_font, + (pw->login_button_enabled_p + ? pw->passwd_foreground + : pw->shadow_bottom), + pw->button_background, + pw->login_label, + pw->login_button_x, pw->login_button_y, + pw->login_button_width, pw->login_button_height, + pw->shadow_width, + (pw->login_button_down_p + ? pw->shadow_bottom + : pw->shadow_top), + (pw->login_button_down_p + ? pw->shadow_top + : pw->shadow_bottom), + pw->login_button_down_p); + } + } + + XFreeGC (si->dpy, gc1); + XFreeGC (si->dpy, gc2); + XSync (si->dpy, False); +} + + +void +restore_background (saver_info *si) +{ + passwd_dialog_data *pw = si->pw_data; + saver_screen_info *ssi = pw->prompt_screen; + XGCValues gcv; + GC gc; + + gcv.function = GXcopy; + + gc = XCreateGC (si->dpy, ssi->screensaver_window, GCFunction, &gcv); + + XCopyArea (si->dpy, pw->save_under, + ssi->screensaver_window, gc, + 0, 0, + ssi->width, ssi->height, + 0, 0); + + XFreeGC (si->dpy, gc); +} + + +/* Frees anything created by make_passwd_window */ +static void +cleanup_passwd_window (saver_info *si) +{ + passwd_dialog_data *pw; + + if (!(pw = si->pw_data)) + return; + + if (pw->info_label) + { + mlstring_free(pw->info_label); + pw->info_label = 0; + } + + if (pw->prompt_label) + { + mlstring_free(pw->prompt_label); + pw->prompt_label = 0; + } + + memset (pw->typed_passwd, 0, sizeof(pw->typed_passwd)); + memset (pw->passwd_string, 0, strlen(pw->passwd_string)); + + if (pw->timer) + { + XtRemoveTimeOut (pw->timer); + pw->timer = 0; + } + + if (pw->user_entry_pixmap) + { + XFreePixmap(si->dpy, pw->user_entry_pixmap); + pw->user_entry_pixmap = 0; + } +} + + +static void +destroy_passwd_window (saver_info *si) +{ + saver_preferences *p = &si->prefs; + passwd_dialog_data *pw = si->pw_data; + saver_screen_info *ssi = pw->prompt_screen; + Colormap cmap = DefaultColormapOfScreen (ssi->screen); + Pixel black = BlackPixelOfScreen (ssi->screen); + Pixel white = WhitePixelOfScreen (ssi->screen); + XEvent event; + + cleanup_passwd_window (si); + + if (si->cached_passwd) + { + char *wipe = si->cached_passwd; + + while (*wipe) + *wipe++ = '\0'; + + free(si->cached_passwd); + si->cached_passwd = NULL; + } + + move_mouse_grab (si, RootWindowOfScreen (ssi->screen), + ssi->cursor, ssi->number); + + if (pw->passwd_cursor) + XFreeCursor (si->dpy, pw->passwd_cursor); + + if (p->verbose_p) + fprintf (stderr, "%s: %d: moving mouse back to %d,%d.\n", + blurb(), ssi->number, + pw->previous_mouse_x, pw->previous_mouse_y); + + XWarpPointer (si->dpy, None, RootWindowOfScreen (ssi->screen), + 0, 0, 0, 0, + pw->previous_mouse_x, pw->previous_mouse_y); + + while (XCheckMaskEvent (si->dpy, PointerMotionMask, &event)) + if (p->verbose_p) + fprintf (stderr, "%s: discarding MotionNotify event.\n", blurb()); + + if (si->passwd_dialog) + { + if (si->prefs.verbose_p) + fprintf (stderr, "%s: %d: destroying password dialog.\n", + blurb(), pw->prompt_screen->number); + + XDestroyWindow (si->dpy, si->passwd_dialog); + si->passwd_dialog = 0; + } + + if (pw->save_under) + { + restore_background(si); + XFreePixmap (si->dpy, pw->save_under); + pw->save_under = 0; + } + + if (pw->heading_label) free (pw->heading_label); + if (pw->body_label) free (pw->body_label); + if (pw->user_label) free (pw->user_label); + if (pw->date_label) free (pw->date_label); + if (pw->login_label) free (pw->login_label); + if (pw->unlock_label) free (pw->unlock_label); + if (pw->passwd_string) free (pw->passwd_string); + if (pw->uname_label) free (pw->uname_label); + + if (pw->heading_font) XFreeFont (si->dpy, pw->heading_font); + if (pw->body_font) XFreeFont (si->dpy, pw->body_font); + if (pw->label_font) XFreeFont (si->dpy, pw->label_font); + if (pw->passwd_font) XFreeFont (si->dpy, pw->passwd_font); + if (pw->date_font) XFreeFont (si->dpy, pw->date_font); + if (pw->button_font) XFreeFont (si->dpy, pw->button_font); + if (pw->uname_font) XFreeFont (si->dpy, pw->uname_font); + + if (pw->foreground != black && pw->foreground != white) + XFreeColors (si->dpy, cmap, &pw->foreground, 1, 0L); + if (pw->background != black && pw->background != white) + XFreeColors (si->dpy, cmap, &pw->background, 1, 0L); + if (!(pw->button_foreground == black || pw->button_foreground == white)) + XFreeColors (si->dpy, cmap, &pw->button_foreground, 1, 0L); + if (!(pw->button_background == black || pw->button_background == white)) + XFreeColors (si->dpy, cmap, &pw->button_background, 1, 0L); + if (pw->passwd_foreground != black && pw->passwd_foreground != white) + XFreeColors (si->dpy, cmap, &pw->passwd_foreground, 1, 0L); + if (pw->passwd_background != black && pw->passwd_background != white) + XFreeColors (si->dpy, cmap, &pw->passwd_background, 1, 0L); + if (pw->thermo_foreground != black && pw->thermo_foreground != white) + XFreeColors (si->dpy, cmap, &pw->thermo_foreground, 1, 0L); + if (pw->thermo_background != black && pw->thermo_background != white) + XFreeColors (si->dpy, cmap, &pw->thermo_background, 1, 0L); + if (pw->shadow_top != black && pw->shadow_top != white) + XFreeColors (si->dpy, cmap, &pw->shadow_top, 1, 0L); + if (pw->shadow_bottom != black && pw->shadow_bottom != white) + XFreeColors (si->dpy, cmap, &pw->shadow_bottom, 1, 0L); + + if (pw->logo_pixmap) + XFreePixmap (si->dpy, pw->logo_pixmap); + if (pw-> logo_clipmask) + XFreePixmap (si->dpy, pw->logo_clipmask); + if (pw->logo_pixels) + { + if (pw->logo_npixels) + XFreeColors (si->dpy, cmap, pw->logo_pixels, pw->logo_npixels, 0L); + free (pw->logo_pixels); + pw->logo_pixels = 0; + pw->logo_npixels = 0; + } + + if (pw->save_under) + XFreePixmap (si->dpy, pw->save_under); + + if (cmap) + XInstallColormap (si->dpy, cmap); + + memset (pw, 0, sizeof(*pw)); + free (pw); + si->pw_data = 0; +} + + +static Bool error_handler_hit_p = False; + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + error_handler_hit_p = True; + return 0; +} + + +#ifdef HAVE_XHPDISABLERESET +/* This function enables and disables the C-Sh-Reset hot-key, which + normally resets the X server (logging out the logged-in user.) + We don't want random people to be able to do that while the + screen is locked. + */ +static void +hp_lock_reset (saver_info *si, Bool lock_p) +{ + static Bool hp_locked_p = False; + + /* Calls to XHPDisableReset and XHPEnableReset must be balanced, + or BadAccess errors occur. (It's ok for this to be global, + since it affects the whole machine, not just the current screen.) + */ + if (hp_locked_p == lock_p) + return; + + if (lock_p) + XHPDisableReset (si->dpy); + else + XHPEnableReset (si->dpy); + hp_locked_p = lock_p; +} +#endif /* HAVE_XHPDISABLERESET */ + + +#ifdef HAVE_XF86MISCSETGRABKEYSSTATE + +/* This function enables and disables the Ctrl-Alt-KP_star and + Ctrl-Alt-KP_slash hot-keys, which (in XFree86 4.2) break any + grabs and/or kill the grabbing client. That would effectively + unlock the screen, so we don't like that. + + The Ctrl-Alt-KP_star and Ctrl-Alt-KP_slash hot-keys only exist + if AllowDeactivateGrabs and/or AllowClosedownGrabs are turned on + in XF86Config. I believe they are disabled by default. + + This does not affect any other keys (specifically Ctrl-Alt-BS or + Ctrl-Alt-F1) but I wish it did. Maybe it will someday. + */ +static void +xfree_lock_grab_smasher (saver_info *si, Bool lock_p) +{ + saver_preferences *p = &si->prefs; + int status; + int event, error; + XErrorHandler old_handler; + + if (!XF86MiscQueryExtension(si->dpy, &event, &error)) + return; + + XSync (si->dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + XSync (si->dpy, False); + status = XF86MiscSetGrabKeysState (si->dpy, !lock_p); + XSync (si->dpy, False); + if (error_handler_hit_p) status = 666; + + if (!lock_p && status == MiscExtGrabStateAlready) + status = MiscExtGrabStateSuccess; /* shut up, consider this success */ + + if (p->verbose_p && status != MiscExtGrabStateSuccess) + fprintf (stderr, "%s: error: XF86MiscSetGrabKeysState(%d) returned %s\n", + blurb(), !lock_p, + (status == MiscExtGrabStateSuccess ? "MiscExtGrabStateSuccess" : + status == MiscExtGrabStateLocked ? "MiscExtGrabStateLocked" : + status == MiscExtGrabStateAlready ? "MiscExtGrabStateAlready" : + status == 666 ? "an X error" : + "unknown value")); + + XSync (si->dpy, False); + XSetErrorHandler (old_handler); + XSync (si->dpy, False); +} +#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */ + + + +/* This function enables and disables the C-Alt-Plus and C-Alt-Minus + hot-keys, which normally change the resolution of the X server. + We don't want people to be able to switch the server resolution + while the screen is locked, because if they switch to a higher + resolution, it could cause part of the underlying desktop to become + exposed. + */ +#ifdef HAVE_XF86VMODE + +static void +xfree_lock_mode_switch (saver_info *si, Bool lock_p) +{ + static Bool any_mode_locked_p = False; + saver_preferences *p = &si->prefs; + int screen; + int real_nscreens = ScreenCount (si->dpy); + int event, error; + Bool status; + XErrorHandler old_handler; + + if (any_mode_locked_p == lock_p) + return; + if (!XF86VidModeQueryExtension (si->dpy, &event, &error)) + return; + + for (screen = 0; screen < real_nscreens; screen++) + { + XSync (si->dpy, False); + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + error_handler_hit_p = False; + status = XF86VidModeLockModeSwitch (si->dpy, screen, lock_p); + XSync (si->dpy, False); + XSetErrorHandler (old_handler); + if (error_handler_hit_p) status = False; + + if (status) + any_mode_locked_p = lock_p; + + if (!status && (p->verbose_p || !lock_p)) + /* Only print this when verbose, or when we locked but can't unlock. + I tried printing this message whenever it comes up, but + mode-locking always fails if DontZoom is set in XF86Config. */ + fprintf (stderr, "%s: %d: unable to %s mode switching!\n", + blurb(), screen, (lock_p ? "lock" : "unlock")); + else if (p->verbose_p) + fprintf (stderr, "%s: %d: %s mode switching.\n", + blurb(), screen, (lock_p ? "locked" : "unlocked")); + } +} +#endif /* HAVE_XF86VMODE */ + + +/* If the viewport has been scrolled since the screen was blanked, + then scroll it back to where it belongs. This function only exists + to patch over a very brief race condition. + */ +static void +undo_vp_motion (saver_info *si) +{ +#ifdef HAVE_XF86VMODE + saver_preferences *p = &si->prefs; + int screen; + int real_nscreens = ScreenCount (si->dpy); + int event, error; + + if (!XF86VidModeQueryExtension (si->dpy, &event, &error)) + return; + + for (screen = 0; screen < real_nscreens; screen++) + { + saver_screen_info *ssi = &si->screens[screen]; + int x, y; + Bool status; + + if (ssi->blank_vp_x == -1 && ssi->blank_vp_y == -1) + break; + if (!XF86VidModeGetViewPort (si->dpy, screen, &x, &y)) + return; + if (ssi->blank_vp_x == x && ssi->blank_vp_y == y) + return; + + /* We're going to move the viewport. The mouse has just been grabbed on + (and constrained to, thus warped to) the password window, so it is no + longer near the edge of the screen. However, wait a bit anyway, just + to make sure the server drains its last motion event, so that the + screen doesn't continue to scroll after we've reset the viewport. + */ + XSync (si->dpy, False); + usleep (250000); /* 1/4 second */ + XSync (si->dpy, False); + + status = XF86VidModeSetViewPort (si->dpy, screen, + ssi->blank_vp_x, ssi->blank_vp_y); + + if (!status) + fprintf (stderr, + "%s: %d: unable to move vp from (%d,%d) back to (%d,%d)!\n", + blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y); + else if (p->verbose_p) + fprintf (stderr, + "%s: %d: vp moved to (%d,%d); moved it back to (%d,%d).\n", + blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y); + } +#endif /* HAVE_XF86VMODE */ +} + + + +/* Interactions + */ + +static void +passwd_animate_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + int tick = 166; + passwd_dialog_data *pw = si->pw_data; + + if (!pw) return; + + pw->ratio -= (1.0 / ((double) si->prefs.passwd_timeout / (double) tick)); + if (pw->ratio < 0) + { + pw->ratio = 0; + if (si->unlock_state == ul_read) + si->unlock_state = ul_time; + } + + update_passwd_window (si, 0, pw->ratio); + + if (si->unlock_state == ul_read) + pw->timer = XtAppAddTimeOut (si->app, tick, passwd_animate_timer, + (XtPointer) si); + else + pw->timer = 0; + + idle_timer ((XtPointer) si, 0); +} + + +static XComposeStatus *compose_status; + +static void +handle_login_button (saver_info *si, XEvent *event) +{ + saver_preferences *p = &si->prefs; + Bool mouse_in_box = False; + Bool hit_p = False; + passwd_dialog_data *pw = si->pw_data; + saver_screen_info *ssi = pw->prompt_screen; + + if (! pw->login_button_enabled_p) + return; + + mouse_in_box = + (event->xbutton.x >= pw->login_button_x && + event->xbutton.x <= pw->login_button_x + pw->login_button_width && + event->xbutton.y >= pw->login_button_y && + event->xbutton.y <= pw->login_button_y + pw->login_button_height); + + if (ButtonRelease == event->xany.type && + pw->login_button_down_p && + mouse_in_box) + { + /* Only allow them to press the button once: don't want to + accidentally launch a dozen gdm choosers if the machine + is being slow. + */ + hit_p = True; + pw->login_button_enabled_p = False; + } + + pw->login_button_down_p = (mouse_in_box && + ButtonRelease != event->xany.type); + + update_passwd_window (si, 0, pw->ratio); + + if (hit_p) + fork_and_exec (ssi, p->new_login_command); +} + + +static void +handle_unlock_button (saver_info *si, XEvent *event) +{ + Bool mouse_in_box = False; + passwd_dialog_data *pw = si->pw_data; + + mouse_in_box = + (event->xbutton.x >= pw->unlock_button_x && + event->xbutton.x <= pw->unlock_button_x + pw->unlock_button_width && + event->xbutton.y >= pw->unlock_button_y && + event->xbutton.y <= pw->unlock_button_y + pw->unlock_button_height); + + if (ButtonRelease == event->xany.type && + pw->unlock_button_down_p && + mouse_in_box) + finished_typing_passwd (si, pw); + + pw->unlock_button_down_p = (mouse_in_box && + ButtonRelease != event->xany.type); +} + + +static void +finished_typing_passwd (saver_info *si, passwd_dialog_data *pw) +{ + if (si->unlock_state == ul_read) + { + update_passwd_window (si, "Checking...", pw->ratio); + XSync (si->dpy, False); + + si->unlock_state = ul_finished; + update_passwd_window (si, "", pw->ratio); + } +} + +static void +handle_passwd_key (saver_info *si, XKeyEvent *event) +{ + passwd_dialog_data *pw = si->pw_data; + int pw_size = sizeof (pw->typed_passwd) - 1; + char *typed_passwd = pw->typed_passwd; + char s[2]; + char *stars = 0; + int i; + int size = XLookupString (event, s, 1, 0, compose_status); + + if (size != 1) return; + + s[1] = 0; + + pw->passwd_changed_p = True; + + /* Add 10% to the time remaining every time a key is pressed. */ + pw->ratio += 0.1; + if (pw->ratio > 1) pw->ratio = 1; + + switch (*s) + { + case '\010': case '\177': /* Backspace */ + if (!*typed_passwd) + XBell (si->dpy, 0); + else + typed_passwd [strlen(typed_passwd)-1] = 0; + break; + + case '\025': case '\030': /* Erase line */ + memset (typed_passwd, 0, pw_size); + break; + + case '\012': case '\015': /* Enter */ + finished_typing_passwd(si, pw); + break; + + case '\033': /* Escape */ + si->unlock_state = ul_cancel; + break; + + default: + /* Though technically the only illegal characters in Unix passwords + are LF and NUL, most GUI programs (e.g., GDM) use regular text-entry + fields that only let you type printable characters. So, people + who use funky characters in their passwords are already broken. + We follow that precedent. + */ + if (isprint ((unsigned char) *s)) + { + i = strlen (typed_passwd); + if (i >= pw_size-1) + XBell (si->dpy, 0); + else + { + typed_passwd [i] = *s; + typed_passwd [i+1] = 0; + } + } + else + XBell (si->dpy, 0); + break; + } + + if (pw->echo_input) + { + /* If the input is wider than the text box, only show the last portion. + * This simulates a horizontally scrolling text field. */ + int chars_in_pwfield = (pw->passwd_field_width / + pw->passwd_font->max_bounds.width); + + if (strlen(typed_passwd) > chars_in_pwfield) + typed_passwd += (strlen(typed_passwd) - chars_in_pwfield); + + update_passwd_window(si, typed_passwd, pw->ratio); + } + else if (pw->show_stars_p) + { + i = strlen(typed_passwd); + stars = (char *) malloc(i+1); + memset (stars, '*', i); + stars[i] = 0; + update_passwd_window (si, stars, pw->ratio); + free (stars); + } + else + { + update_passwd_window (si, "", pw->ratio); + } +} + + +static void +passwd_event_loop (saver_info *si) +{ + saver_preferences *p = &si->prefs; + char *msg = 0; + XEvent event; + + passwd_animate_timer ((XtPointer) si, 0); + + while (si->unlock_state == ul_read) + { + XtAppNextEvent (si->app, &event); + if (event.xany.window == si->passwd_dialog && event.xany.type == Expose) + draw_passwd_window (si); + else if (event.xany.type == KeyPress) + { + handle_passwd_key (si, &event.xkey); + si->pw_data->caps_p = (event.xkey.state & LockMask); + } + else if (event.xany.type == ButtonPress || + event.xany.type == ButtonRelease) + { + si->pw_data->button_state_changed_p = True; + handle_unlock_button (si, &event); + if (si->pw_data->login_button_p) + handle_login_button (si, &event); + } + else + XtDispatchEvent (&event); + } + + switch (si->unlock_state) + { + case ul_cancel: msg = ""; break; + case ul_time: msg = "Timed out!"; break; + case ul_finished: msg = "Checking..."; break; + default: msg = 0; break; + } + + if (p->verbose_p) + switch (si->unlock_state) { + case ul_cancel: + fprintf (stderr, "%s: input cancelled.\n", blurb()); break; + case ul_time: + fprintf (stderr, "%s: input timed out.\n", blurb()); break; + case ul_finished: + fprintf (stderr, "%s: input finished.\n", blurb()); break; + default: break; + } + + if (msg) + { + si->pw_data->i_beam = 0; + update_passwd_window (si, msg, 0.0); + XSync (si->dpy, False); + + /* Swallow all pending KeyPress/KeyRelease events. */ + { + XEvent e; + while (XCheckMaskEvent (si->dpy, KeyPressMask|KeyReleaseMask, &e)) + ; + } + } +} + + +static void +handle_typeahead (saver_info *si) +{ + passwd_dialog_data *pw = si->pw_data; + int i; + if (!si->unlock_typeahead) + return; + + pw->passwd_changed_p = True; + + i = strlen (si->unlock_typeahead); + if (i >= sizeof(pw->typed_passwd) - 1) + i = sizeof(pw->typed_passwd) - 1; + + memcpy (pw->typed_passwd, si->unlock_typeahead, i); + pw->typed_passwd [i] = 0; + + memset (si->unlock_typeahead, '*', strlen(si->unlock_typeahead)); + si->unlock_typeahead[i] = 0; + update_passwd_window (si, si->unlock_typeahead, pw->ratio); + + free (si->unlock_typeahead); + si->unlock_typeahead = 0; +} + + +/** + * Returns a copy of the input string with trailing whitespace removed. + * Whitespace is anything considered so by isspace(). + * It is safe to call this with NULL, in which case NULL will be returned. + * The returned string (if not NULL) should be freed by the caller with free(). + */ +static char * +remove_trailing_whitespace(const char *str) +{ + size_t len; + char *newstr, *chr; + + if (!str) + return NULL; + + len = strlen(str); + + newstr = malloc(len + 1); + (void) strcpy(newstr, str); + + if (!newstr) + return NULL; + + chr = newstr + len; + while (isspace(*--chr) && chr >= newstr) + *chr = '\0'; + + return newstr; +} + + +/* + * The authentication conversation function. + * Like a PAM conversation function, this accepts multiple messages in a single + * round. It then splits them into individual messages for display on the + * passwd dialog. A message sequence of info or error followed by a prompt will + * be reduced into a single dialog window. + * + * Returns 0 on success or -1 if some problem occurred (cancelled auth, OOM, ...) + */ +int +gui_auth_conv(int num_msg, + const struct auth_message auth_msgs[], + struct auth_response **resp, + saver_info *si) +{ + int i; + const char *info_msg, *prompt; + struct auth_response *responses; + + if (si->unlock_state == ul_cancel || + si->unlock_state == ul_time) + /* If we've already cancelled or timed out in this PAM conversation, + don't prompt again even if PAM asks us to! */ + return -1; + + if (!(responses = calloc(num_msg, sizeof(struct auth_response)))) + goto fail; + + for (i = 0; i < num_msg; ++i) + { + info_msg = prompt = NULL; + + /* See if there is a following message that can be shown at the same + * time */ + if (auth_msgs[i].type == AUTH_MSGTYPE_INFO + && i+1 < num_msg + && ( auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_NOECHO + || auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_ECHO) + ) + { + info_msg = auth_msgs[i].msg; + prompt = auth_msgs[++i].msg; + } + else + { + if ( auth_msgs[i].type == AUTH_MSGTYPE_INFO + || auth_msgs[i].type == AUTH_MSGTYPE_ERROR) + info_msg = auth_msgs[i].msg; + else + prompt = auth_msgs[i].msg; + } + + { + char *info_msg_trimmed, *prompt_trimmed; + + /* Trailing whitespace looks bad in a GUI */ + info_msg_trimmed = remove_trailing_whitespace(info_msg); + prompt_trimmed = remove_trailing_whitespace(prompt); + + if (make_passwd_window(si, info_msg_trimmed, prompt_trimmed, + auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO + ? True : False) + < 0) + goto fail; + + if (info_msg_trimmed) + free(info_msg_trimmed); + + if (prompt_trimmed) + free(prompt_trimmed); + } + + compose_status = calloc (1, sizeof (*compose_status)); + if (!compose_status) + goto fail; + + si->unlock_state = ul_read; + + handle_typeahead (si); + passwd_event_loop (si); + + if (si->unlock_state == ul_cancel) + goto fail; + + responses[i].response = strdup(si->pw_data->typed_passwd); + + /* Cache the first response to a PROMPT_NOECHO to save prompting for + * each auth mechanism. */ + if (si->cached_passwd == NULL && + auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO) + si->cached_passwd = strdup(responses[i].response); + + free (compose_status); + compose_status = 0; + } + + *resp = responses; + + return (si->unlock_state == ul_finished) ? 0 : -1; + +fail: + if (compose_status) + free (compose_status); + + if (responses) + { + for (i = 0; i < num_msg; ++i) + if (responses[i].response) + free (responses[i].response); + free (responses); + } + + return -1; +} + + +void +auth_finished_cb (saver_info *si) +{ + char buf[1024]; + const char *s; + + /* If we have something to say, put the dialog back up for a few seconds + to display it. Otherwise, don't bother. + */ + + if (si->unlock_state == ul_fail && /* failed with caps lock on */ + si->pw_data && si->pw_data->caps_p) + s = "Authentication failed (Caps Lock?)"; + else if (si->unlock_state == ul_fail) /* failed without caps lock */ + s = "Authentication failed!"; + else if (si->unlock_state == ul_success && /* good, but report failures */ + si->unlock_failures > 0) + { + if (si->unlock_failures == 1) + s = "There has been\n1 failed login attempt."; + else + { + sprintf (buf, "There have been\n%d failed login attempts.", + si->unlock_failures); + s = buf; + } + si->unlock_failures = 0; + } + else /* good, with no failures, */ + goto END; /* or timeout, or cancel. */ + + make_passwd_window (si, s, NULL, True); + XSync (si->dpy, False); + + { + int secs = 4; + time_t start = time ((time_t *) 0); + XEvent event; + while (time ((time_t *) 0) < start + secs) + if (XPending (si->dpy)) + { + XNextEvent (si->dpy, &event); + if (event.xany.window == si->passwd_dialog && + event.xany.type == Expose) + draw_passwd_window (si); + else if (event.xany.type == ButtonPress || + event.xany.type == KeyPress) + break; + XSync (si->dpy, False); + } + else + usleep (250000); /* 1/4 second */ + } + + END: + if (si->pw_data) + destroy_passwd_window (si); +} + + +Bool +unlock_p (saver_info *si) +{ + saver_preferences *p = &si->prefs; + + if (!si->unlock_cb) + { + fprintf(stderr, "%s: Error: no unlock function specified!\n", blurb()); + return False; + } + + raise_window (si, True, True, True); + + xss_authenticate(si, p->verbose_p); + + return (si->unlock_state == ul_success); +} + + +void +set_locked_p (saver_info *si, Bool locked_p) +{ + si->locked_p = locked_p; + +#ifdef HAVE_XHPDISABLERESET + hp_lock_reset (si, locked_p); /* turn off/on C-Sh-Reset */ +#endif +#ifdef HAVE_XF86VMODE + xfree_lock_mode_switch (si, locked_p); /* turn off/on C-Alt-Plus */ +#endif +#ifdef HAVE_XF86MISCSETGRABKEYSSTATE + xfree_lock_grab_smasher (si, locked_p); /* turn off/on C-Alt-KP-*,/ */ +#endif + + store_saver_status (si); /* store locked-p */ +} + + +#else /* NO_LOCKING -- whole file */ + +void +set_locked_p (saver_info *si, Bool locked_p) +{ + if (locked_p) abort(); +} + +#endif /* !NO_LOCKING */ diff --git a/driver/mlstring.c b/driver/mlstring.c new file mode 100644 index 00000000..fdba1ee5 --- /dev/null +++ b/driver/mlstring.c @@ -0,0 +1,229 @@ +/* + * (c) 2007, Quest Software, Inc. All rights reserved. + * + * This file is part of XScreenSaver, + * Copyright (c) 1993-2009 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#include +#include + +#include + +#include "mlstring.h" + +#define LINE_SPACING 1.2 + +static mlstring * +mlstring_allocate(const char *msg); + +static void +mlstring_calculate(mlstring *str, XFontStruct *font); + +mlstring* +mlstring_new(const char *msg, XFontStruct *font, Dimension wrap_width) +{ + mlstring *newstr; + + if (!(newstr = mlstring_allocate(msg))) + return NULL; + + newstr->font_id = font->fid; + + mlstring_wrap(newstr, font, wrap_width); + + return newstr; +} + +mlstring * +mlstring_allocate(const char *msg) +{ + const char *s; + mlstring *ml; + struct mlstr_line *cur, *prev = NULL; + size_t linelength; + int the_end = 0; + + if (!msg) + return NULL; + + ml = calloc(1, sizeof(mlstring)); + + if (!ml) + return NULL; + + for (s = msg; !the_end; msg = ++s) + { + /* New string struct */ + cur = calloc(1, sizeof(struct mlstr_line)); + if (!cur) + goto fail; + + if (!ml->lines) + ml->lines = cur; + + /* Find the \n or end of string */ + while (*s != '\n') + { + if (*s == '\0') + { + the_end = 1; + break; + } + + ++s; + } + + linelength = s - msg; + + /* Duplicate the string */ + cur->line = malloc(linelength + 1); + if (!cur->line) + goto fail; + + strncpy(cur->line, msg, linelength); + cur->line[linelength] = '\0'; + + if (prev) + prev->next_line = cur; + prev = cur; + } + + return ml; + +fail: + + if (ml) + mlstring_free(ml); + + return NULL; +} + + +/* + * Frees an mlstring. + * This function does not have any unit tests. + */ +void +mlstring_free(mlstring *str) { + struct mlstr_line *cur, *next; + + for (cur = str->lines; cur; cur = next) { + next = cur->next_line; + free(cur->line); + free(cur); + } + + free(str); +} + + +void +mlstring_wrap(mlstring *mstring, XFontStruct *font, Dimension width) +{ + short char_width = font->max_bounds.width; + int line_length, wrap_at; + struct mlstr_line *mstr, *newml; + + /* An alternative implementation of this function would be to keep trying + * XTextWidth() on space-delimited substrings until the longest one less + * than 'width' is found, however there shouldn't be much difference + * between that, and this implementation. + */ + + for (mstr = mstring->lines; mstr; mstr = mstr->next_line) + { + if (XTextWidth(font, mstr->line, strlen(mstr->line)) > width) + { + /* Wrap it */ + line_length = width / char_width; + if (line_length == 0) + line_length = 1; + + /* First try to soft wrap by finding a space */ + for (wrap_at = line_length; wrap_at >= 0 && !isspace(mstr->line[wrap_at]); --wrap_at); + + if (wrap_at == -1) /* No space found, hard wrap */ + wrap_at = line_length; + else + wrap_at++; /* Leave the space at the end of the line. */ + + newml = calloc(1, sizeof(*newml)); + if (!newml) /* OOM, don't bother trying to wrap */ + break; + + if (NULL == (newml->line = strdup(mstr->line + wrap_at))) + { + /* OOM, jump ship */ + free(newml); + break; + } + + /* Terminate the existing string at its end */ + mstr->line[wrap_at] = '\0'; + + newml->next_line = mstr->next_line; + mstr->next_line = newml; + } + } + + mlstring_calculate(mstring, font); +} + +#undef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +/* + * Calculates the overall extents (width + height of the multi-line string). + * This function is called as part of mlstring_new(). + * It does not have any unit testing. + */ +void +mlstring_calculate(mlstring *str, XFontStruct *font) { + struct mlstr_line *line; + + str->font_height = font->ascent + font->descent; + str->overall_height = 0; + str->overall_width = 0; + + /* XXX: Should there be some baseline calculations to help XDrawString later on? */ + str->font_ascent = font->ascent; + + for (line = str->lines; line; line = line->next_line) + { + line->line_width = XTextWidth(font, line->line, strlen(line->line)); + str->overall_width = MAX(str->overall_width, line->line_width); + /* Don't add line spacing for the first line */ + str->overall_height += (font->ascent + font->descent) * + (line == str->lines ? 1 : LINE_SPACING); + } +} + +void +mlstring_draw(Display *dpy, Drawable dialog, GC gc, mlstring *string, int x, int y) { + struct mlstr_line *line; + + if (!string) + return; + + y += string->font_ascent; + + XSetFont(dpy, gc, string->font_id); + + for (line = string->lines; line; line = line->next_line) + { + XDrawString(dpy, dialog, gc, x, y, line->line, strlen(line->line)); + y += string->font_height * LINE_SPACING; + } +} + +/* vim:ts=8:sw=2:noet + */ diff --git a/driver/mlstring.h b/driver/mlstring.h new file mode 100644 index 00000000..ce362056 --- /dev/null +++ b/driver/mlstring.h @@ -0,0 +1,57 @@ +/* mlstring.h --- Multi-line strings for use with Xlib + * + * (c) 2007, Quest Software, Inc. All rights reserved. + * + * This file is part of XScreenSaver, + * Copyright (c) 1993-2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ +#ifndef MLSTRING_H +#define MLSTRING_H + +#include + +/* mlstring means multi-line string */ + +struct mlstr_line; + +typedef struct mlstring mlstring; +struct mlstring { + struct mlstr_line *lines; /* linked list */ + Dimension overall_height; + Dimension overall_width; + /* XXX: Perhaps it is simpler to keep a reference to the XFontStruct */ + int font_ascent; + int font_height; + Font font_id; +}; + +struct mlstr_line { + char *line; + Dimension line_width; + struct mlstr_line *next_line; +}; + +mlstring * +mlstring_new(const char *str, XFontStruct *font, Dimension wrap_width); + +/* Does not have to be called manually */ +void +mlstring_wrap(mlstring *mstr, XFontStruct *font, Dimension width); + +void +mlstring_free(mlstring *str); + +void +mlstring_draw(Display *dpy, Drawable dialog, GC gc, mlstring *string, int x, int y); + +#endif +/* vim:ts=8:sw=2:noet + */ diff --git a/driver/passwd-helper.c b/driver/passwd-helper.c new file mode 100644 index 00000000..a3a6b924 --- /dev/null +++ b/driver/passwd-helper.c @@ -0,0 +1,162 @@ +/* passwd-helper.c --- verifying typed passwords with external helper program + * written by Olaf Kirch + * xscreensaver, Copyright (c) 1993-2005 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* The idea here is to be able to run xscreensaver without any setuid bits. + * Password verification happens through an external program that you feed + * your password to on stdin. The external command is invoked with a user + * name argument. + * + * The external helper does whatever authentication is necessary. Currently, + * SuSE uses "unix2_chkpwd", which is a variation of "unix_chkpwd" from the + * PAM distribution. + * + * Normally, the password helper should just authenticate the calling user + * (i.e. based on the caller's real uid). This is in order to prevent + * brute-forcing passwords in a shadow environment. A less restrictive + * approach would be to allow verifying other passwords as well, but always + * with a 2 second delay or so. (Not sure what SuSE's "unix2_chkpwd" + * currently does.) + * -- Olaf Kirch , 16-Dec-2003 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef NO_LOCKING /* whole file */ + +#include /* not used for much... */ + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include +#include +#include + +#include + +static int +ext_run (const char *user, const char *typed_passwd, int verbose_p) +{ + int pfd[2], status; + pid_t pid; + + if (pipe(pfd) < 0) + return 0; + + if (verbose_p) + fprintf (stderr, "%s: ext_run (%s, %s)\n", + blurb(), PASSWD_HELPER_PROGRAM, user); + + block_sigchld(); + + if ((pid = fork()) < 0) { + close(pfd[0]); + close(pfd[1]); + return 0; + } + + if (pid == 0) { + close(pfd[1]); + if (pfd[0] != 0) + dup2(pfd[0], 0); + + /* Helper is invoked as helper service-name [user] */ + execlp(PASSWD_HELPER_PROGRAM, PASSWD_HELPER_PROGRAM, "xscreensaver", user, NULL); + if (verbose_p) + fprintf(stderr, "%s: %s\n", PASSWD_HELPER_PROGRAM, + strerror(errno)); + exit(1); + } + + close(pfd[0]); + + /* Write out password to helper process */ + if (!typed_passwd) + typed_passwd = ""; + write(pfd[1], typed_passwd, strlen(typed_passwd)); + close(pfd[1]); + + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + if (verbose_p) + fprintf(stderr, "%s: ext_run: waitpid failed: %s\n", + blurb(), strerror(errno)); + unblock_sigchld(); + return 0; + } + + unblock_sigchld(); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return 0; + return 1; +} + + + +/* This can be called at any time, and says whether the typed password + belongs to either the logged in user (real uid, not effective); or + to root. + */ +int +ext_passwd_valid_p (const char *typed_passwd, int verbose_p) +{ + struct passwd *pw; + int res = 0; + + if ((pw = getpwuid(getuid())) != NULL) + res = ext_run (pw->pw_name, typed_passwd, verbose_p); + endpwent(); + +#ifdef ALLOW_ROOT_PASSWD + if (!res) + res = ext_run ("root", typed_passwd, verbose_p); +#endif /* ALLOW_ROOT_PASSWD */ + + return res; +} + + +int +ext_priv_init (int argc, char **argv, int verbose_p) +{ + /* Make sure the passwd helper exists */ + if (access(PASSWD_HELPER_PROGRAM, X_OK) < 0) { + fprintf(stderr, + "%s: warning: %s does not exist.\n" + "%s: password authentication via " + "external helper will not work.\n", + blurb(), PASSWD_HELPER_PROGRAM, blurb()); + return 0; + } + return 1; +} + +#endif /* NO_LOCKING -- whole file */ diff --git a/driver/passwd-kerberos.c b/driver/passwd-kerberos.c new file mode 100644 index 00000000..202e0eb1 --- /dev/null +++ b/driver/passwd-kerberos.c @@ -0,0 +1,251 @@ +/* kpasswd.c --- verify kerberos passwords. + * written by Nat Lanza (magus@cs.cmu.edu) for + * xscreensaver, Copyright (c) 1993-2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef NO_LOCKING /* whole file */ + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include +#include + +/* I'm not sure if this is exactly the right test... + Might __APPLE__ be defined if this is apple hardware, but not + an Apple OS? + + Thanks to Alexei Kosut for the MacOS X code. + */ +#ifdef __APPLE__ +# define HAVE_DARWIN +#endif + + +#if defined(HAVE_DARWIN) +# include +#elif defined(HAVE_KERBEROS5) +# include +# include +#else /* !HAVE_KERBEROS5 (meaning Kerberos 4) */ +# include +# include +#endif /* !HAVE_KERBEROS5 */ + +#if !defined(VMS) && !defined(HAVE_ADJUNCT_PASSWD) +# include +#endif + + +#ifdef __bsdi__ +# include +# if _BSDI_VERSION >= 199608 +# define BSD_AUTH +# endif +#endif /* __bsdi__ */ + +/* blargh */ +#undef Bool +#undef True +#undef False +#define Bool int +#define True 1 +#define False 0 + +/* The user information we need to store */ +#ifdef HAVE_DARWIN + static KLPrincipal princ; +#else /* !HAVE_DARWIN */ + static char realm[REALM_SZ]; + static char name[ANAME_SZ]; + static char inst[INST_SZ]; + static const char *tk_file; +#endif /* !HAVE_DARWIN */ + +/* warning suppression: duplicated in passwd.c */ +extern Bool kerberos_lock_init (int argc, char **argv, Bool verbose_p); +extern Bool kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p); + + +/* Called at startup to grab user, instance, and realm information + from the user's ticketfile (remember, name.inst@realm). Since we're + using tf_get_pname(), this should work even if your kerberos username + isn't the same as your local username. We grab the ticket at startup + time so that even if your ticketfile dies while the screen's locked + we'll still have the information to unlock it. + + Problems: the password dialog currently displays local username, so if + you have some non-standard name/instance when you run xscreensaver, + you'll need to remember what it was when unlocking, or else you lose. + + Also, we use des_string_to_key(), so if you have an AFS password + (encrypted with ka_StringToKey()), you'll lose. Get a kerberos password; + it isn't that hard. + + Like the original lock_init, we return false if something went wrong. + We don't use the arguments we're given, though. + */ +Bool +kerberos_lock_init (int argc, char **argv, Bool verbose_p) +{ +# ifdef HAVE_DARWIN + + KLBoolean found; + return ((klNoErr == (KLCacheHasValidTickets (NULL, kerberosVersion_Any, + &found, &princ, NULL))) + && found); + +# else /* !HAVE_DARWIN */ + + /* Perhaps we should be doing it the Mac way (above) all the time? + The following code assumes Unix-style file-based Kerberos credentials + cache, which Mac OS X doesn't use. But is there any real reason to + do it this way at all, even on other Unixen? + */ + int k_errno; + + memset(name, 0, sizeof(name)); + memset(inst, 0, sizeof(inst)); + + /* find out where the user's keeping his tickets. + squirrel it away for later use. */ + tk_file = tkt_string(); + + /* open ticket file or die trying. */ + if ((k_errno = tf_init(tk_file, R_TKT_FIL))) { + return False; + } + + /* same with principal and instance names */ + if ((k_errno = tf_get_pname(name)) || + (k_errno = tf_get_pinst(inst))) { + return False; + } + + /* close the ticketfile to release the lock on it. */ + tf_close(); + + /* figure out what realm we're authenticated to. this ought + to be the local realm, but it pays to be sure. */ + if ((k_errno = krb_get_tf_realm(tk_file, realm))) { + return False; + } + + /* last-minute sanity check on what we got. */ + if ((strlen(name)+strlen(inst)+strlen(realm)+3) > + (REALM_SZ + ANAME_SZ + INST_SZ + 3)) { + return False; + } + + /* success */ + return True; + +# endif /* !HAVE_DARWIN */ +} + + +/* des_string_to_key() wants this. If C didn't suck, we could have an + anonymous function do this. Even a local one. But it does, so here + we are. Calling it ive_got_your_local_function_right_here_buddy() + would have been rude. + */ +#ifndef HAVE_DARWIN +static int +key_to_key(char *user, char *instance, char *realm, char *passwd, C_Block key) +{ + memcpy(key, passwd, sizeof(des_cblock)); + return (0); +} +#endif /* !HAVE_DARWIN */ + +/* Called to see if the user's typed password is valid. We do this by asking + the kerberos server for a ticket and checking to see if it gave us one. + We need to move the ticketfile first, or otherwise we end up updating the + user's tkfile with new tickets. This would break services like zephyr that + like to stay authenticated, and it would screw with AFS authentication at + some sites. So, we do a quick, painful hack with a tmpfile. + */ +Bool +kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p) +{ +# ifdef HAVE_DARWIN + return (klNoErr == + KLAcquireNewInitialTicketsWithPassword (princ, NULL, + typed_passwd, NULL)); +# else /* !HAVE_DARWIN */ + + /* See comments in kerberos_lock_init -- should we do it the Mac Way + on all systems? + */ + C_Block mitkey; + Bool success; + char *newtkfile; + int fh = -1; + + /* temporarily switch to a new ticketfile. + I'm not using tmpnam() because it isn't entirely portable. + this could probably be fixed with autoconf. */ + newtkfile = malloc(80 * sizeof(char)); + memset(newtkfile, 0, sizeof(newtkfile)); + + sprintf(newtkfile, "/tmp/xscrn-%i.XXXXXX", getpid()); + + if( (fh = mkstemp(newtkfile)) < 0) + { + free(newtkfile); + return(False); + } + if( fchmod(fh, 0600) < 0) + { + free(newtkfile); + return(False); + } + + + krb_set_tkt_string(newtkfile); + + /* encrypt the typed password. if you have an AFS password instead + of a kerberos one, you lose *right here*. If you want to use AFS + passwords, you can use ka_StringToKey() instead. As always, ymmv. */ + des_string_to_key(typed_passwd, mitkey); + + if (krb_get_in_tkt(name, inst, realm, "krbtgt", realm, DEFAULT_TKT_LIFE, + key_to_key, NULL, (char *) mitkey) != 0) { + success = False; + } else { + success = True; + } + + /* quickly block out the tempfile and password to prevent snooping, + then restore the old ticketfile and cleean up a bit. */ + + dest_tkt(); + krb_set_tkt_string(tk_file); + free(newtkfile); + memset(mitkey, 0, sizeof(mitkey)); + close(fh); /* #### tom: should the file be removed? */ + + + /* Did we verify successfully? */ + return success; + +# endif /* !HAVE_DARWIN */ +} + +#endif /* NO_LOCKING -- whole file */ diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c new file mode 100644 index 00000000..3b4c64f9 --- /dev/null +++ b/driver/passwd-pam.c @@ -0,0 +1,493 @@ +/* passwd-pam.c --- verifying typed passwords with PAM + * (Pluggable Authentication Modules.) + * written by Bill Nottingham (and jwz) for + * xscreensaver, Copyright (c) 1993-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * Some PAM resources: + * + * PAM home page: + * http://www.us.kernel.org/pub/linux/libs/pam/ + * + * PAM FAQ: + * http://www.us.kernel.org/pub/linux/libs/pam/FAQ + * + * PAM Application Developers' Guide: + * http://www.us.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_ADG.html + * + * PAM Mailing list archives: + * http://www.linuxhq.com/lnxlists/linux-pam/ + * + * Compatibility notes, especially between Linux and Solaris: + * http://www.contrib.andrew.cmu.edu/u/shadow/pam.html + * + * The Open Group's PAM API documentation: + * http://www.opengroup.org/onlinepubs/8329799/pam_start.htm + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef NO_LOCKING /* whole file */ + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +extern char *blurb(void); + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "auth.h" + +extern sigset_t block_sigchld (void); +extern void unblock_sigchld (void); + +/* blargh */ +#undef Bool +#undef True +#undef False +#define Bool int +#define True 1 +#define False 0 + +#undef countof +#define countof(x) (sizeof((x))/sizeof(*(x))) + +/* Some time between Red Hat 4.2 and 7.0, the words were transposed + in the various PAM_x_CRED macro names. Yay! + */ +#ifndef PAM_REFRESH_CRED +# define PAM_REFRESH_CRED PAM_CRED_REFRESH +#endif + +static int pam_conversation (int nmsgs, + const struct pam_message **msg, + struct pam_response **resp, + void *closure); + +void pam_try_unlock(saver_info *si, Bool verbose_p, + Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)); + +Bool pam_priv_init (int argc, char **argv, Bool verbose_p); + +#ifdef HAVE_PAM_FAIL_DELAY + /* We handle delays ourself.*/ + /* Don't set this to 0 (Linux bug workaround.) */ +# define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1) +#else /* !HAVE_PAM_FAIL_DELAY */ +# define PAM_NO_DELAY(pamh) /* */ +#endif /* !HAVE_PAM_FAIL_DELAY */ + + +/* On SunOS 5.6, and on Linux with PAM 0.64, pam_strerror() takes two args. + On some other Linux systems with some other version of PAM (e.g., + whichever Debian release comes with a 2.2.5 kernel) it takes one arg. + I can't tell which is more "recent" or "correct" behavior, so configure + figures out which is in use for us. Shoot me! + */ +#ifdef PAM_STRERROR_TWO_ARGS +# define PAM_STRERROR(pamh, status) pam_strerror((pamh), (status)) +#else /* !PAM_STRERROR_TWO_ARGS */ +# define PAM_STRERROR(pamh, status) pam_strerror((status)) +#endif /* !PAM_STRERROR_TWO_ARGS */ + + +/* PAM sucks in that there is no way to tell whether a particular service + is configured at all. That is, there is no way to tell the difference + between "authentication of the FOO service is not allowed" and "the + user typed the wrong password." + + On RedHat 5.1 systems, if a service name is not known, it defaults to + being not allowed (because the fallback service, /etc/pam.d/other, is + set to `pam_deny'.) + + On Solaris 2.6 systems, unknown services default to authenticating normally. + + So, we could simply require that the person who installs xscreensaver + set up an "xscreensaver" PAM service. However, if we went that route, + it would have a really awful failure mode: the failure mode would be that + xscreensaver was willing to *lock* the screen, but would be unwilling to + *unlock* the screen. (With the non-PAM password code, the analagous + situation -- security not being configured properly, for example do to the + executable not being installed as setuid root -- the failure mode is much + more palettable, in that xscreensaver will refuse to *lock* the screen, + because it can know up front that there is no password that will work.) + + Another route would be to have the service name to consult be computed at + compile-time (perhaps with a configure option.) However, that doesn't + really solve the problem, because it means that the same executable might + work fine on one machine, but refuse to unlock when run on another + machine. + + Another alternative would be to look in /etc/pam.conf or /etc/pam.d/ at + runtime to see what services actually exist. But I think that's no good, + because who is to say that the PAM info is actually specified in those + files? Opening and reading those files is not a part of the PAM client + API, so it's not guarenteed to work on any given system. + + An alternative I tried was to specify a list of services to try, and to + try them all in turn ("xscreensaver", "xlock", "xdm", and "login"). + This worked, but it was slow (and I also had to do some contortions to + work around bugs in Linux PAM 0.64-3.) + + So what we do today is, try PAM once, and if that fails, try the usual + getpwent() method. So if PAM doesn't work, it will at least make an + attempt at looking up passwords in /etc/passwd or /etc/shadow instead. + + This all kind of blows. I'm not sure what else to do. + */ + + +/* On SunOS 5.6, the `pam_conv.appdata_ptr' slot seems to be ignored, and + the `closure' argument to pc.conv always comes in as random garbage. + So we get around this by using a global variable instead. Shoot me! + + (I've been told this is bug 4092227, and is fixed in Solaris 7.) + (I've also been told that it's fixed in Solaris 2.6 by patch 106257-05.) + */ +static void *suns_pam_implementation_blows = 0; + + +/** + * This function is the PAM conversation driver. It conducts a full + * authentication round by invoking the GUI with various prompts. + */ +void +pam_try_unlock(saver_info *si, Bool verbose_p, + Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)) +{ + const char *service = PAM_SERVICE_NAME; + pam_handle_t *pamh = 0; + int status = -1; + struct pam_conv pc; + sigset_t set; + struct timespec timeout; + + pc.conv = &pam_conversation; + pc.appdata_ptr = (void *) si; + + /* On SunOS 5.6, the `appdata_ptr' slot seems to be ignored, and the + `closure' argument to pc.conv always comes in as random garbage. */ + suns_pam_implementation_blows = (void *) si; + + + /* Initialize PAM. + */ + status = pam_start (service, si->user, &pc, &pamh); + if (verbose_p) + fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n", + blurb(), service, si->user, + status, PAM_STRERROR (pamh, status)); + if (status != PAM_SUCCESS) goto DONE; + + /* #### We should set PAM_TTY to the display we're using, but we + don't have that handy from here. So set it to :0.0, which is a + good guess (and has the bonus of counting as a "secure tty" as + far as PAM is concerned...) + */ + { + char *tty = strdup (":0.0"); + status = pam_set_item (pamh, PAM_TTY, tty); + if (verbose_p) + fprintf (stderr, "%s: pam_set_item (p, PAM_TTY, \"%s\") ==> %d (%s)\n", + blurb(), tty, status, PAM_STRERROR(pamh, status)); + free (tty); + } + + /* Try to authenticate as the current user. + We must turn off our SIGCHLD handler for the duration of the call to + pam_authenticate(), because in some cases, the underlying PAM code + will do this: + + 1: fork a setuid subprocess to do some dirty work; + 2: read a response from that subprocess; + 3: waitpid(pid, ...) on that subprocess. + + If we (the ignorant parent process) have a SIGCHLD handler, then there's + a race condition between steps 2 and 3: if the subprocess exits before + waitpid() was called, then our SIGCHLD handler fires, and gets notified + of the subprocess death; then PAM's call to waitpid() fails, because the + process has already been reaped. + + I consider this a bug in PAM, since the caller should be able to have + whatever signal handlers it wants -- the PAM documentation doesn't say + "oh by the way, if you use PAM, you can't use SIGCHLD." + */ + + PAM_NO_DELAY(pamh); + + if (verbose_p) + fprintf (stderr, "%s: pam_authenticate (...) ...\n", blurb()); + + timeout.tv_sec = 0; + timeout.tv_nsec = 1; + set = block_sigchld(); + status = pam_authenticate (pamh, 0); +# ifdef HAVE_SIGTIMEDWAIT + sigtimedwait (&set, NULL, &timeout); + /* #### What is the portable thing to do if we don't have it? */ +# endif /* HAVE_SIGTIMEDWAIT */ + unblock_sigchld(); + + if (verbose_p) + fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n", + blurb(), status, PAM_STRERROR(pamh, status)); + + if (status == PAM_SUCCESS) /* Win! */ + { + int status2; + + /* We don't actually care if the account modules fail or succeed, + * but we need to run them anyway because certain pam modules + * depend on side effects of the account modules getting run. + */ + status2 = pam_acct_mgmt (pamh, 0); + + if (verbose_p) + fprintf (stderr, "%s: pam_acct_mgmt (...) ==> %d (%s)\n", + blurb(), status2, PAM_STRERROR(pamh, status2)); + + /* HPUX for some reason likes to make PAM defines different from + * everyone else's. */ +#ifdef PAM_AUTHTOKEN_REQD + if (status2 == PAM_AUTHTOKEN_REQD) +#else + if (status2 == PAM_NEW_AUTHTOK_REQD) +#endif + { + status2 = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (verbose_p) + fprintf (stderr, "%s: pam_chauthtok (...) ==> %d (%s)\n", + blurb(), status2, PAM_STRERROR(pamh, status2)); + } + + /* Each time we successfully authenticate, refresh credentials, + for Kerberos/AFS/DCE/etc. If this fails, just ignore that + failure and blunder along; it shouldn't matter. + + Note: this used to be PAM_REFRESH_CRED instead of + PAM_REINITIALIZE_CRED, but Jason Heiss + says that the Linux PAM library ignores that one, and only refreshes + credentials when using PAM_REINITIALIZE_CRED. + */ + status2 = pam_setcred (pamh, PAM_REINITIALIZE_CRED); + if (verbose_p) + fprintf (stderr, "%s: pam_setcred (...) ==> %d (%s)\n", + blurb(), status2, PAM_STRERROR(pamh, status2)); + } + + DONE: + if (pamh) + { + int status2 = pam_end (pamh, status); + pamh = 0; + if (verbose_p) + fprintf (stderr, "%s: pam_end (...) ==> %d (%s)\n", + blurb(), status2, + (status2 == PAM_SUCCESS ? "Success" : "Failure")); + } + + if (status == PAM_SUCCESS) + si->unlock_state = ul_success; /* yay */ + else if (si->unlock_state == ul_cancel || + si->unlock_state == ul_time) + ; /* more specific failures ok */ + else + si->unlock_state = ul_fail; /* generic failure */ +} + + +Bool +pam_priv_init (int argc, char **argv, Bool verbose_p) +{ + /* We have nothing to do at init-time. + However, we might as well do some error checking. + If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock" + does not exist, warn that PAM probably isn't going to work. + + This is a priv-init instead of a non-priv init in case the directory + is unreadable or something (don't know if that actually happens.) + */ + const char dir[] = "/etc/pam.d"; + const char file[] = "/etc/pam.d/" PAM_SERVICE_NAME; + const char file2[] = "/etc/pam.conf"; + struct stat st; + +# ifndef S_ISDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +# endif + + if (stat (dir, &st) == 0 && S_ISDIR(st.st_mode)) + { + if (stat (file, &st) != 0) + fprintf (stderr, + "%s: warning: %s does not exist.\n" + "%s: password authentication via PAM is unlikely to work.\n", + blurb(), file, blurb()); + } + else if (stat (file2, &st) == 0) + { + FILE *f = fopen (file2, "r"); + if (f) + { + Bool ok = False; + char buf[255]; + while (fgets (buf, sizeof(buf), f)) + if (strstr (buf, PAM_SERVICE_NAME)) + { + ok = True; + break; + } + fclose (f); + if (!ok) + { + fprintf (stderr, + "%s: warning: %s does not list the `%s' service.\n" + "%s: password authentication via PAM is unlikely to work.\n", + blurb(), file2, PAM_SERVICE_NAME, blurb()); + } + } + /* else warn about file2 existing but being unreadable? */ + } + else + { + fprintf (stderr, + "%s: warning: neither %s nor %s exist.\n" + "%s: password authentication via PAM is unlikely to work.\n", + blurb(), file2, file, blurb()); + } + + /* Return true anyway, just in case. */ + return True; +} + + +static int +pam_conversation (int nmsgs, + const struct pam_message **msg, + struct pam_response **resp, + void *vsaver_info) +{ + int i, ret = -1; + struct auth_message *messages = 0; + struct auth_response *authresp = 0; + struct pam_response *pam_responses; + saver_info *si = (saver_info *) vsaver_info; + Bool verbose_p; + + /* On SunOS 5.6, the `closure' argument always comes in as random garbage. */ + si = (saver_info *) suns_pam_implementation_blows; + + verbose_p = si->prefs.verbose_p; + + /* Converting the PAM prompts into the XScreenSaver native format. + * It was a design goal to collapse (INFO,PROMPT) pairs from PAM + * into a single call to the unlock_cb function. The unlock_cb function + * does that, but only if it is passed several prompts at a time. Most PAM + * modules only send a single prompt at a time, but because there is no way + * of telling whether there will be more prompts to follow, we can only ever + * pass along whatever was passed in here. + */ + + messages = calloc(nmsgs, sizeof(struct auth_message)); + pam_responses = calloc(nmsgs, sizeof(*pam_responses)); + + if (!pam_responses || !messages) + goto end; + + if (verbose_p) + fprintf (stderr, "%s: pam_conversation (", blurb()); + + for (i = 0; i < nmsgs; ++i) + { + if (verbose_p && i > 0) fprintf (stderr, ", "); + + messages[i].msg = msg[i]->msg; + + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: messages[i].type = AUTH_MSGTYPE_PROMPT_NOECHO; + if (verbose_p) fprintf (stderr, "ECHO_OFF"); + break; + case PAM_PROMPT_ECHO_ON: messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO; + if (verbose_p) fprintf (stderr, "ECHO_ON"); + break; + case PAM_ERROR_MSG: messages[i].type = AUTH_MSGTYPE_ERROR; + if (verbose_p) fprintf (stderr, "ERROR_MSG"); + break; + case PAM_TEXT_INFO: messages[i].type = AUTH_MSGTYPE_INFO; + if (verbose_p) fprintf (stderr, "TEXT_INFO"); + break; + default: messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO; + if (verbose_p) fprintf (stderr, "PROMPT_ECHO"); + break; + } + + if (verbose_p) + fprintf (stderr, "=\"%s\"", msg[i]->msg ? msg[i]->msg : "(null)"); + } + + if (verbose_p) + fprintf (stderr, ") ...\n"); + + ret = si->unlock_cb(nmsgs, messages, &authresp, si); + + /* #### If the user times out, or hits ESC or Cancel, we return PAM_CONV_ERR, + and PAM logs this as an authentication failure. It would be nice if + there was some way to indicate that this was a "cancel" rather than + a "fail", so that it wouldn't show up in syslog, but I think the + only options are PAM_SUCCESS and PAM_CONV_ERR. (I think that + PAM_ABORT means "internal error", not "cancel".) Bleh. + */ + + if (ret == 0) + { + for (i = 0; i < nmsgs; ++i) + pam_responses[i].resp = authresp[i].response; + } + +end: + if (messages) + free(messages); + + if (authresp) + free(authresp); + + if (verbose_p) + fprintf (stderr, "%s: pam_conversation (...) ==> %s\n", blurb(), + (ret == 0 ? "PAM_SUCCESS" : "PAM_CONV_ERR")); + + if (ret == 0) + { + *resp = pam_responses; + return PAM_SUCCESS; + } + + /* Failure only */ + if (pam_responses) + free(pam_responses); + + return PAM_CONV_ERR; +} + +#endif /* NO_LOCKING -- whole file */ diff --git a/driver/passwd-pwent.c b/driver/passwd-pwent.c new file mode 100644 index 00000000..bb0edfc2 --- /dev/null +++ b/driver/passwd-pwent.c @@ -0,0 +1,312 @@ +/* passwd-pwent.c --- verifying typed passwords with the OS. + * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef NO_LOCKING /* whole file */ + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_CRYPT_H +# include +#endif + +#include +#include +#include +#ifndef VMS +# include +# include +#else /* VMS */ +# include "vms-pwd.h" +#endif /* VMS */ + + +#ifdef __bsdi__ +# include +# if _BSDI_VERSION >= 199608 +# define BSD_AUTH +# endif +#endif /* __bsdi__ */ + + +#if defined(HAVE_SHADOW_PASSWD) /* passwds live in /etc/shadow */ + +# include +# define PWTYPE struct spwd * +# define PWPSLOT sp_pwdp +# define GETPW getspnam + +#elif defined(HAVE_ENHANCED_PASSWD) /* passwds live in /tcb/files/auth/ */ + /* M.Matsumoto */ +# include +# include + +# define PWTYPE struct pr_passwd * +# define PWPSLOT ufld.fd_encrypt +# define GETPW getprpwnam + +#elif defined(HAVE_ADJUNCT_PASSWD) + +# include +# include +# include + +# define PWTYPE struct passwd_adjunct * +# define PWPSLOT pwa_passwd +# define GETPW getpwanam + +#elif defined(HAVE_HPUX_PASSWD) + +# include +# include + +# define PWTYPE struct s_passwd * +# define PWPSLOT pw_passwd +# define GETPW getspwnam + +# define HAVE_BIGCRYPT + +#endif + + +/* blargh */ +#undef Bool +#undef True +#undef False +#define Bool int +#define True 1 +#define False 0 + + +extern const char *blurb(void); + +static char *encrypted_root_passwd = 0; +static char *encrypted_user_passwd = 0; + +#ifdef VMS +# define ROOT "SYSTEM" +#else +# define ROOT "root" +#endif + +#ifndef VMS +Bool pwent_priv_init (int argc, char **argv, Bool verbose_p); +Bool pwent_lock_init (int argc, char **argv, Bool verbose_p); +Bool pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p); +#endif + + +#ifndef VMS + +static char * +user_name (void) +{ + /* I think that just checking $USER here is not the best idea. */ + + const char *u = 0; + + /* It has been reported that getlogin() returns the wrong user id on some + very old SGI systems... And I've seen it return the string "rlogin" + sometimes! Screw it, using getpwuid() should be enough... + */ +/* u = (char *) getlogin (); + */ + + /* getlogin() fails if not attached to a terminal; in that case, use + getpwuid(). (Note that in this case, we're not doing shadow stuff, since + all we're interested in is the name, not the password. So that should + still work. Right?) */ + if (!u || !*u) + { + struct passwd *p = getpwuid (getuid ()); + u = (p ? p->pw_name : 0); + } + + return (u ? strdup(u) : 0); +} + +#else /* VMS */ + +static char * +user_name (void) +{ + char *u = getenv("USER"); + return (u ? strdup(u) : 0); +} + +#endif /* VMS */ + + +static Bool +passwd_known_p (const char *pw) +{ + return (pw && + pw[0] != '*' && /* This would be sensible... */ + strlen(pw) > 4); /* ...but this is what Solaris does. */ +} + + +static char * +get_encrypted_passwd(const char *user) +{ + char *result = 0; + +#ifdef PWTYPE + if (user && *user && !result) + { /* First check the shadow passwords. */ + PWTYPE p = GETPW((char *) user); + if (p && passwd_known_p (p->PWPSLOT)) + result = strdup(p->PWPSLOT); + } +#endif /* PWTYPE */ + + if (user && *user && !result) + { /* Check non-shadow passwords too. */ + struct passwd *p = getpwnam(user); + if (p && passwd_known_p (p->pw_passwd)) + result = strdup(p->pw_passwd); + } + + /* The manual for passwd(4) on HPUX 10.10 says: + + Password aging is put in effect for a particular user if his + encrypted password in the password file is followed by a comma and + a nonnull string of characters from the above alphabet. This + string defines the "age" needed to implement password aging. + + So this means that passwd->pw_passwd isn't simply a string of cyphertext, + it might have trailing junk. So, if there is a comma in the string, and + that comma is beyond position 13, terminate the string before the comma. + */ + if (result && strlen(result) > 13) + { + char *s = strchr (result+13, ','); + if (s) + *s = 0; + } + +#ifndef HAVE_PAM + /* We only issue this warning if not compiled with support for PAM. + If we're using PAM, it's not unheard of that normal pwent passwords + would be unavailable. */ + + if (!result) + fprintf (stderr, "%s: couldn't get password of \"%s\"\n", + blurb(), (user ? user : "(null)")); +#endif /* !HAVE_PAM */ + + return result; +} + + + +/* This has to be called before we've changed our effective user ID, + because it might need privileges to get at the encrypted passwords. + Returns false if we weren't able to get any passwords, and therefore, + locking isn't possible. (It will also have written to stderr.) + */ + +#ifndef VMS + +Bool +pwent_priv_init (int argc, char **argv, Bool verbose_p) +{ + char *u; + +#ifdef HAVE_ENHANCED_PASSWD + set_auth_parameters(argc, argv); + check_auth_parameters(); +#endif /* HAVE_DEC_ENHANCED */ + + u = user_name(); + encrypted_user_passwd = get_encrypted_passwd(u); + encrypted_root_passwd = get_encrypted_passwd(ROOT); + if (u) free (u); + + if (encrypted_user_passwd) + return True; + else + return False; +} + + +Bool +pwent_lock_init (int argc, char **argv, Bool verbose_p) +{ + if (encrypted_user_passwd) + return True; + else + return False; +} + + + +static Bool +passwds_match_p (const char *cleartext, const char *ciphertext) +{ + char *s = 0; /* note that on some systems, crypt() may return null */ + + s = (char *) crypt (cleartext, ciphertext); + if (s && !strcmp (s, ciphertext)) + return True; + +#ifdef HAVE_BIGCRYPT + /* There seems to be no way to tell at runtime if an HP machine is in + "trusted" mode, and thereby, which of crypt() or bigcrypt() we should + be calling to compare passwords. So call them both, and see which + one works. */ + + s = (char *) bigcrypt (cleartext, ciphertext); + if (s && !strcmp (s, ciphertext)) + return True; + +#endif /* HAVE_BIGCRYPT */ + + return False; +} + + + +/* This can be called at any time, and says whether the typed password + belongs to either the logged in user (real uid, not effective); or + to root. + */ +Bool +pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p) +{ + if (encrypted_user_passwd && + passwds_match_p (typed_passwd, encrypted_user_passwd)) + return True; + +#ifdef ALLOW_ROOT_PASSWD + /* do not allow root to have a null password. */ + else if (typed_passwd[0] && + encrypted_root_passwd && + passwds_match_p (typed_passwd, encrypted_root_passwd)) + return True; +#endif /* ALLOW_ROOT_PASSWD */ + + else + return False; +} + +#else /* VMS */ +Bool pwent_lock_init (int argc, char **argv, Bool verbose_p) { return True; } +#endif /* VMS */ + +#endif /* NO_LOCKING -- whole file */ diff --git a/driver/passwd.c b/driver/passwd.c new file mode 100644 index 00000000..fd42c552 --- /dev/null +++ b/driver/passwd.c @@ -0,0 +1,334 @@ +/* passwd.c --- verifying typed passwords with the OS. + * xscreensaver, Copyright (c) 1993-2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef NO_LOCKING /* whole file */ + +#include +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifndef VMS +# include /* for getpwuid() */ +#else /* VMS */ +# include "vms-pwd.h" +#endif /* VMS */ + +#ifdef HAVE_SYSLOG +# include +#endif /* HAVE_SYSLOG */ + +#include + +#include "xscreensaver.h" +#include "auth.h" + +extern const char *blurb(void); +extern void check_for_leaks (const char *where); + + +/* blargh */ +#undef Bool +#undef True +#undef False +#define Bool int +#define True 1 +#define False 0 + +#undef countof +#define countof(x) (sizeof((x))/sizeof(*(x))) + +struct auth_methods { + const char *name; + Bool (*init) (int argc, char **argv, Bool verbose_p); + Bool (*priv_init) (int argc, char **argv, Bool verbose_p); + Bool (*valid_p) (const char *typed_passwd, Bool verbose_p); + void (*try_unlock) (saver_info *si, Bool verbose_p, + Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)); + Bool initted_p; + Bool priv_initted_p; +}; + + +#ifdef HAVE_KERBEROS +extern Bool kerberos_lock_init (int argc, char **argv, Bool verbose_p); +extern Bool kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p); +#endif +#ifdef HAVE_PAM +extern Bool pam_priv_init (int argc, char **argv, Bool verbose_p); +extern void pam_try_unlock (saver_info *si, Bool verbose_p, + Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)); +#endif +#ifdef PASSWD_HELPER_PROGRAM +extern Bool ext_priv_init (int argc, char **argv, Bool verbose_p); +extern Bool ext_passwd_valid_p (const char *typed_passwd, Bool verbose_p); +#endif +extern Bool pwent_lock_init (int argc, char **argv, Bool verbose_p); +extern Bool pwent_priv_init (int argc, char **argv, Bool verbose_p); +extern Bool pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p); + +Bool lock_priv_init (int argc, char **argv, Bool verbose_p); +Bool lock_init (int argc, char **argv, Bool verbose_p); +Bool passwd_valid_p (const char *typed_passwd, Bool verbose_p); + +/* The authorization methods to try, in order. + Note that the last one (the pwent version) is actually two auth methods, + since that code tries shadow passwords, and then non-shadow passwords. + (It's all in the same file since the APIs are randomly nearly-identical.) + */ +struct auth_methods methods[] = { +# ifdef HAVE_PAM + { "PAM", 0, pam_priv_init, 0, pam_try_unlock, + False, False }, +# endif +# ifdef HAVE_KERBEROS + { "Kerberos", kerberos_lock_init, 0, kerberos_passwd_valid_p, 0, + False, False }, +# endif +# ifdef PASSWD_HELPER_PROGRAM + { "external", 0, ext_priv_init, ext_passwd_valid_p, 0, + False, False }, +# endif + { "normal", pwent_lock_init, pwent_priv_init, pwent_passwd_valid_p, 0, + False, False } +}; + + +Bool +lock_priv_init (int argc, char **argv, Bool verbose_p) +{ + int i; + Bool any_ok = False; + for (i = 0; i < countof(methods); i++) + { + if (!methods[i].priv_init) + methods[i].priv_initted_p = True; + else + methods[i].priv_initted_p = methods[i].priv_init (argc, argv, + verbose_p); + + if (methods[i].priv_initted_p) + any_ok = True; + else if (verbose_p) + fprintf (stderr, "%s: initialization of %s passwords failed.\n", + blurb(), methods[i].name); + } + return any_ok; +} + + +Bool +lock_init (int argc, char **argv, Bool verbose_p) +{ + int i; + Bool any_ok = False; + for (i = 0; i < countof(methods); i++) + { + if (!methods[i].priv_initted_p) /* Bail if lock_priv_init failed. */ + continue; + + if (!methods[i].init) + methods[i].initted_p = True; + else + methods[i].initted_p = methods[i].init (argc, argv, verbose_p); + + if (methods[i].initted_p) + any_ok = True; + else if (verbose_p) + fprintf (stderr, "%s: initialization of %s passwords failed.\n", + blurb(), methods[i].name); + } + return any_ok; +} + + +/* A basic auth driver that simply prompts for a password then runs it through + * valid_p to determine whether the password is correct. + */ +static void +try_unlock_password(saver_info *si, + Bool verbose_p, + Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)) +{ + struct auth_message message; + struct auth_response *response = NULL; + + memset(&message, 0, sizeof(message)); + + if (verbose_p) + fprintf(stderr, "%s: non-PAM password auth.\n", blurb()); + + /* Call the auth_conv function with "Password:", then feed + * the result into valid_p() + */ + message.type = AUTH_MSGTYPE_PROMPT_NOECHO; + message.msg = "Password:"; + + si->unlock_cb(1, &message, &response, si); + + if (!response) + return; + + if (valid_p (response->response, verbose_p)) + si->unlock_state = ul_success; /* yay */ + else if (si->unlock_state == ul_cancel || + si->unlock_state == ul_time) + ; /* more specific failures ok */ + else + si->unlock_state = ul_fail; /* generic failure */ + + if (response->response) + free(response->response); + free(response); +} + + +/* Write a password failure to the system log. + */ +static void +do_syslog (saver_info *si, Bool verbose_p) +{ +# ifdef HAVE_SYSLOG + struct passwd *pw = getpwuid (getuid ()); + char *d = DisplayString (si->dpy); + char *u = (pw && pw->pw_name ? pw->pw_name : "???"); + int opt = 0; + int fac = 0; + +# ifdef LOG_PID + opt = LOG_PID; +# endif + +# if defined(LOG_AUTHPRIV) + fac = LOG_AUTHPRIV; +# elif defined(LOG_AUTH) + fac = LOG_AUTH; +# else + fac = LOG_DAEMON; +# endif + + if (!d) d = ""; + +# undef FMT +# define FMT "FAILED LOGIN %d ON DISPLAY \"%s\", FOR \"%s\"" + + if (verbose_p) + fprintf (stderr, "%s: syslog: " FMT "\n", blurb(), + si->unlock_failures, d, u); + + openlog (progname, opt, fac); + syslog (LOG_NOTICE, FMT, si->unlock_failures, d, u); + closelog (); + +# endif /* HAVE_SYSLOG */ +} + + + +/** + * Runs through each authentication driver calling its try_unlock function. + * Called xss_authenticate() because AIX beat us to the name authenticate(). + */ +void +xss_authenticate(saver_info *si, Bool verbose_p) +{ + int i, j; + + si->unlock_state = ul_read; + + for (i = 0; i < countof(methods); i++) + { + if (!methods[i].initted_p) + continue; + + if (si->cached_passwd != NULL && methods[i].valid_p) + si->unlock_state = (methods[i].valid_p(si->cached_passwd, verbose_p) == True) + ? ul_success : ul_fail; + else if (methods[i].try_unlock != NULL) + methods[i].try_unlock(si, verbose_p, methods[i].valid_p); + else if (methods[i].valid_p) + try_unlock_password(si, verbose_p, methods[i].valid_p); + else /* Ze goggles, zey do nozing! */ + fprintf(stderr, "%s: authentication method %s does nothing.\n", + blurb(), methods[i].name); + + check_for_leaks (methods[i].name); + + /* If password authentication failed, but the password was NULL + (meaning the user just hit RET) then treat that as "cancel". + This means that if the password is literally NULL, it will + work; but if not, then NULL passwords are treated as cancel. + */ + if (si->unlock_state == ul_fail && + si->cached_passwd && + !*si->cached_passwd) + { + fprintf (stderr, "%s: assuming null password means cancel.\n", + blurb()); + si->unlock_state = ul_cancel; + } + + if (si->unlock_state == ul_success) + { + /* If we successfully authenticated by method N, but attempting + to authenticate by method N-1 failed, mention that (since if + an earlier authentication method fails and a later one succeeds, + something screwy is probably going on.) + */ + if (verbose_p && i > 0) + { + for (j = 0; j < i; j++) + if (methods[j].initted_p) + fprintf (stderr, + "%s: authentication via %s failed.\n", + blurb(), methods[j].name); + fprintf (stderr, + "%s: authentication via %s succeeded.\n", + blurb(), methods[i].name); + } + goto DONE; /* Successfully authenticated! */ + } + else if (si->unlock_state == ul_cancel || + si->unlock_state == ul_time) + { + /* If any auth method gets a cancel or timeout, don't try the + next auth method! We're done! */ + fprintf (stderr, + "%s: authentication via %s %s.\n", + blurb(), methods[i].name, + (si->unlock_state == ul_cancel + ? "cancelled" : "timed out")); + goto DONE; + } + } + + if (verbose_p) + fprintf(stderr, "%s: All authentication mechanisms failed.\n", blurb()); + + if (si->unlock_state == ul_fail) + { + si->unlock_failures++; + do_syslog (si, verbose_p); + } + +DONE: + if (si->auth_finished_cb) + si->auth_finished_cb (si); +} + +#endif /* NO_LOCKING -- whole file */ diff --git a/driver/pdf2jpeg.m b/driver/pdf2jpeg.m new file mode 100644 index 00000000..d681b4a5 --- /dev/null +++ b/driver/pdf2jpeg.m @@ -0,0 +1,152 @@ +/* pdf2jpeg -- converts a PDF file to a JPEG file, using Cocoa + * + * Copyright (c) 2003, 2008 by Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * Inspired by clues provided by Jan Kujawa and Jonathan Hendry. + */ + +#import +#include +#include + +int +main (int argc, char** argv) +{ + const char *progname = argv[0]; + const char *infile = 0, *outfile = 0; + double compression = 0.85; + double scale = 1.0; + int verbose = 0; + int i; + + for (i = 1; i < argc; i++) + { + char c; + if (argv[i][0] == '-' && argv[i][1] == '-') + argv[i]++; + if (!strcmp (argv[i], "-q") || + !strcmp (argv[i], "-qual") || + !strcmp (argv[i], "-quality")) + { + int q; + if (1 != sscanf (argv[++i], " %d %c", &q, &c) || + q < 5 || q > 100) + { + fprintf (stderr, "%s: quality must be 5 - 100 (%d)\n", + progname, q); + goto USAGE; + } + compression = q / 100.0; + } + else if (!strcmp (argv[i], "-scale")) + { + float s; + if (1 != sscanf (argv[++i], " %f %c", &s, &c) || + s <= 0 || s > 50) + { + fprintf (stderr, "%s: scale must be 0.0 - 50.0 (%f)\n", + progname, s); + goto USAGE; + } + scale = s; + } + else if (!strcmp (argv[i], "-verbose")) + verbose++; + else if (!strcmp (argv[i], "-v") || + !strcmp (argv[i], "-vv") || + !strcmp (argv[i], "-vvv")) + verbose += strlen(argv[i])-1; + else if (argv[i][0] == '-') + { + fprintf (stderr, "%s: unknown option %s\n", progname, argv[i]); + goto USAGE; + } + else if (!infile) + infile = argv[i]; + else if (!outfile) + outfile = argv[i]; + else + { + USAGE: + fprintf (stderr, + "usage: %s [-verbose] [-scale N] [-quality NN] " + "infile.pdf outfile.jpg\n", + progname); + exit (1); + } + } + + if (!infile || !outfile) + goto USAGE; + + + // Much of Cocoa needs one of these to be available. + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + //Need an NSApp instance to make [NSImage TIFFRepresentation] work + NSApp = [NSApplication sharedApplication]; + [NSApp autorelease]; + + if (verbose) + fprintf (stderr, "%s: reading %s...\n", progname, infile); + + // Load the PDF file into an NSData object: + NSData *pdf_data = [NSData dataWithContentsOfFile: + [NSString stringWithCString:infile + encoding:NSUTF8StringEncoding]]; + + // Create an NSPDFImageRep from the data: + NSPDFImageRep *pdf_rep = [NSPDFImageRep imageRepWithData:pdf_data]; + + // Create an NSImage instance + NSRect rect; + rect.size = [pdf_rep size]; + rect.size.width *= scale; + rect.size.height *= scale; + rect.origin.x = rect.origin.y = 0; + NSImage *image = [[NSImage alloc] initWithSize:rect.size]; + + // Draw the PDFImageRep in the NSImage + [image lockFocus]; + [pdf_rep drawInRect:rect]; + [image unlockFocus]; + + // Load the NSImage's contents into an NSBitmapImageRep: + NSBitmapImageRep *bit_rep = [NSBitmapImageRep + imageRepWithData:[image TIFFRepresentation]]; + + // Write the bitmapImageRep to a JPEG file: + if (bit_rep == nil) + { + fprintf (stderr, "%s: error converting image?\n", argv[0]); + exit (1); + } + + if (verbose) + fprintf (stderr, "%s: writing %s (%d%% quality)...\n", + progname, outfile, (int) (compression * 100)); + + NSDictionary *props = [NSDictionary + dictionaryWithObject: + [NSNumber numberWithFloat:compression] + forKey:NSImageCompressionFactor]; + NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType + properties:props]; + + [jpeg_data writeToFile: + [NSString stringWithCString:outfile + encoding:NSUTF8StringEncoding] + atomically:YES]; + [image release]; + + [pool release]; + exit (0); +} diff --git a/driver/pdf2jpeg.man b/driver/pdf2jpeg.man new file mode 100644 index 00000000..9d80dd76 --- /dev/null +++ b/driver/pdf2jpeg.man @@ -0,0 +1,43 @@ +.TH XScreenSaver 1 "07-Sep-2003 (4.13)" "X Version 11" +.SH NAME +pdf2jpeg - converts a PDF file to a JPEG file using Cocoa +.SH SYNOPSIS +.B pdf2jpeg +[\--verbose] [\--quality \fINN\fP] infile.pdf outfile.jpg +.SH DESCRIPTION +This reads a PDF file (for example, as written by the +.BR screencapture (1) +program) and writes a JPEG file. +.SH OPTIONS +.I pdf2jpeg +accepts the following options: +.TP 4 +.B --verbose +Print diagnostics. +.TP 4 +.B --quality \fINN\fP +JPEG compression factor. Default 85%. +.SH BUGS +The input and output files must be files: pipes don't work. + +This program is Cocoa-specific, so it won't work on non-MacOS systems. + +This shouldn't need to be a part of the XScreenSaver distribution at +all, but Apple is COMPLETELY INSANE and made +.BR screencapture (1) +only write PDFs, with no simple way to convert that to something +less crazy. +.SH SEE ALSO +.BR screencapture (1), +.BR xscreensaver\-getimage\-desktop (1) +.SH COPYRIGHT +Copyright \(co 2003 by Jamie Zawinski. Permission to use, copy, +modify, distribute, and sell this software and its documentation for +any purpose is hereby granted without fee, provided that the above +copyright notice appear in all copies and that both that copyright +notice and this permission notice appear in supporting documentation. +No representations are made about the suitability of this software for +any purpose. It is provided "as is" without express or implied +warranty. +.SH AUTHOR +Jamie Zawinski , 20-Oct-03. diff --git a/driver/prefs.c b/driver/prefs.c new file mode 100644 index 00000000..1b093148 --- /dev/null +++ b/driver/prefs.c @@ -0,0 +1,1638 @@ +/* dotfile.c --- management of the ~/.xscreensaver file. + * xscreensaver, Copyright (c) 1998-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include /* for PATH_MAX */ + +#include +#include + +#ifndef VMS +# include +#else /* VMS */ +# include "vms-pwd.h" +#endif /* VMS */ + + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + + +/* Just in case there's something pathological about stat.h... */ +#ifndef S_IRUSR +# define S_IRUSR 00400 +#endif +#ifndef S_IWUSR +# define S_IWUSR 00200 +#endif +#ifndef S_IXUSR +# define S_IXUSR 00100 +#endif +#ifndef S_IXGRP +# define S_IXGRP 00010 +#endif +#ifndef S_IXOTH +# define S_IXOTH 00001 +#endif + + +#include "prefs.h" +#include "resources.h" + +/* don't use realpath() on fedora system */ +#ifdef _FORTIFY_SOURCE +#undef HAVE_REALPATH +#endif + + +extern char *progname; +extern char *progclass; +extern const char *blurb (void); + + + +static void get_screenhacks (Display *, saver_preferences *); +static char *format_command (const char *cmd, Bool wrap_p); +static void merge_system_screenhacks (Display *, saver_preferences *, + screenhack **system_list, int count); +static void stop_the_insanity (saver_preferences *p); + + +static char * +chase_symlinks (const char *file) +{ +# ifdef HAVE_REALPATH + if (file) + { +# ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# define PATH_MAX 2048 +# endif +# endif + char buf[PATH_MAX]; + if (realpath (file, buf)) + return strdup (buf); + +/* sprintf (buf, "%.100s: realpath %.200s", blurb(), file); + perror(buf);*/ + } +# endif /* HAVE_REALPATH */ + return 0; +} + + +static Bool +i_am_a_nobody (uid_t uid) +{ + struct passwd *p; + + p = getpwnam ("nobody"); + if (! p) p = getpwnam ("noaccess"); + if (! p) p = getpwnam ("daemon"); + + if (! p) /* There is no nobody? */ + return False; + + return (uid == p->pw_uid); +} + + +const char * +init_file_name (void) +{ + static char *file = 0; + + if (!file) + { + uid_t uid = getuid (); + struct passwd *p = getpwuid (uid); + + if (i_am_a_nobody (uid)) + /* If we're running as nobody, then use root's .xscreensaver file + (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely + to be different -- if we didn't do this, then xscreensaver-demo + would appear to have no effect when the luser is running as root.) + */ + uid = 0; + + p = getpwuid (uid); + + if (!p || !p->pw_name || !*p->pw_name) + { + fprintf (stderr, "%s: couldn't get user info of uid %d\n", + blurb(), getuid ()); + file = ""; + } + else if (!p->pw_dir || !*p->pw_dir) + { + fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n", + blurb(), (p->pw_name ? p->pw_name : "???")); + file = ""; + } + else + { + const char *home = p->pw_dir; + const char *name = ".xscreensaver"; + file = (char *) malloc(strlen(home) + strlen(name) + 2); + strcpy(file, home); + if (!*home || home[strlen(home)-1] != '/') + strcat(file, "/"); + strcat(file, name); + } + } + + if (file && *file) + return file; + else + return 0; +} + + +static const char * +init_file_tmp_name (void) +{ + static char *file = 0; + if (!file) + { + const char *name = init_file_name(); + const char *suffix = ".tmp"; + + char *n2 = chase_symlinks (name); + if (n2) name = n2; + + if (!name || !*name) + file = ""; + else + { + file = (char *) malloc(strlen(name) + strlen(suffix) + 2); + strcpy(file, name); + strcat(file, suffix); + } + + if (n2) free (n2); + } + + if (file && *file) + return file; + else + return 0; +} + +static int +get_byte_resource (Display *dpy, char *name, char *class) +{ + char *s = get_string_resource (dpy, name, class); + char *s2 = s; + int n = 0; + if (!s) return 0; + + while (isspace(*s2)) s2++; + while (*s2 >= '0' && *s2 <= '9') + { + n = (n * 10) + (*s2 - '0'); + s2++; + } + while (isspace(*s2)) s2++; + if (*s2 == 'k' || *s2 == 'K') n <<= 10; + else if (*s2 == 'm' || *s2 == 'M') n <<= 20; + else if (*s2 == 'g' || *s2 == 'G') n <<= 30; + else if (*s2) + { + LOSE: + fprintf (stderr, "%s: %s must be a number of bytes, not \"%s\".\n", + progname, name, s); + free (s); + return 0; + } + s2++; + if (*s2 == 'b' || *s2 == 'B') s2++; + while (isspace(*s2)) s2++; + if (*s2) goto LOSE; + + free (s); + return n; +} + + +static const char * const prefs[] = { + "timeout", + "cycle", + "lock", + "lockVTs", /* not saved */ + "lockTimeout", + "passwdTimeout", + "visualID", + "installColormap", + "verbose", + "timestamp", + "splash", + "splashDuration", + "quad", + "demoCommand", + "prefsCommand", + "newLoginCommand", + "helpURL", /* not saved */ + "loadURL", /* not saved */ + "newLoginCommand", /* not saved */ + "nice", + "memoryLimit", + "fade", + "unfade", + "fadeSeconds", + "fadeTicks", + "captureStderr", + "captureStdout", /* not saved -- obsolete */ + "logFile", /* not saved */ + "ignoreUninstalledPrograms", + "font", + "dpmsEnabled", + "dpmsStandby", + "dpmsSuspend", + "dpmsOff", + "grabDesktopImages", + "grabVideoFrames", + "chooseRandomImages", + "imageDirectory", + "mode", + "selected", + "textMode", + "textLiteral", + "textFile", + "textProgram", + "textURL", + "", + "programs", + "", + "pointerPollTime", + "pointerHysteresis", + "windowCreationTimeout", + "initialDelay", + "sgiSaverExtension", + "mitSaverExtension", /* not saved -- obsolete */ + "xidleExtension", + "GetViewPortIsFullOfLies", + "procInterrupts", + "overlayStderr", + "overlayTextBackground", /* not saved -- X resources only */ + "overlayTextForeground", /* not saved -- X resources only */ + "bourneShell", /* not saved -- X resources only */ + 0 +}; + +static char * +strip (char *s) +{ + char *s2; + while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n') + s++; + for (s2 = s; *s2; s2++) + ; + for (s2--; s2 >= s; s2--) + if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') + *s2 = 0; + else + break; + return s; +} + + +/* Reading + */ + +static int +handle_entry (XrmDatabase *db, const char *key, const char *value, + const char *filename, int line) +{ + int i; + for (i = 0; prefs[i]; i++) + if (*prefs[i] && !strcasecmp(key, prefs[i])) + { + char *val = strdup(value); + char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10); + strcpy(spec, progclass); + strcat(spec, "."); + strcat(spec, prefs[i]); + + XrmPutStringResource (db, spec, val); + + free(spec); + free(val); + return 0; + } + + fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n", + blurb(), filename, line, key); + return 1; +} + + +static int +parse_init_file (saver_preferences *p) +{ + time_t write_date = 0; + const char *name = init_file_name(); + int line = 0; + struct stat st; + FILE *in; + int buf_size = 1024; + char *buf; + + if (!name) return 0; + + if (stat(name, &st) != 0) + { + p->init_file_date = 0; + return 0; + } + + in = fopen(name, "r"); + if (!in) + { + char *buf = (char *) malloc(1024 + strlen(name)); + sprintf(buf, "%s: error reading \"%s\"", blurb(), name); + perror(buf); + free(buf); + return -1; + } + + if (fstat (fileno(in), &st) == 0) + { + write_date = st.st_mtime; + } + else + { + char *buf = (char *) malloc(1024 + strlen(name)); + sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name); + perror(buf); + free(buf); + return -1; + } + + buf = (char *) malloc(buf_size); + + while (fgets (buf, buf_size-1, in)) + { + char *key, *value; + int L = strlen(buf); + + line++; + while (L > 2 && + (buf[L-1] != '\n' || /* whole line didn't fit in buffer */ + buf[L-2] == '\\')) /* or line ended with backslash */ + { + if (buf[L-2] == '\\') /* backslash-newline gets swallowed */ + { + buf[L-2] = 0; + L -= 2; + } + buf_size += 1024; + buf = (char *) realloc(buf, buf_size); + if (!buf) exit(1); + + line++; + if (!fgets (buf + L, buf_size-L-1, in)) + break; + L = strlen(buf); + } + + /* Now handle other backslash escapes. */ + { + int i, j; + for (i = 0; buf[i]; i++) + if (buf[i] == '\\') + { + switch (buf[i+1]) + { + case 'n': buf[i] = '\n'; break; + case 'r': buf[i] = '\r'; break; + case 't': buf[i] = '\t'; break; + default: buf[i] = buf[i+1]; break; + } + for (j = i+2; buf[j]; j++) + buf[j-1] = buf[j]; + buf[j-1] = 0; + } + } + + key = strip(buf); + + if (*key == '#' || *key == '!' || *key == ';' || + *key == '\n' || *key == 0) + continue; + + value = strchr (key, ':'); + if (!value) + { + fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(), + name, line, key); + continue; + } + else + { + *value++ = 0; + value = strip(value); + } + + if (!p->db) abort(); + handle_entry (&p->db, key, value, name, line); + } + fclose (in); + free(buf); + + p->init_file_date = write_date; + return 0; +} + + +Bool +init_file_changed_p (saver_preferences *p) +{ + const char *name = init_file_name(); + struct stat st; + + if (!name) return False; + + if (stat(name, &st) != 0) + return False; + + if (p->init_file_date == st.st_mtime) + return False; + + return True; +} + + +/* Writing + */ + +static int +tab_to (FILE *out, int from, int to) +{ + int tab_width = 8; + int to_mod = (to / tab_width) * tab_width; + while (from < to_mod) + { + fprintf(out, "\t"); + from = (((from / tab_width) + 1) * tab_width); + } + while (from < to) + { + fprintf(out, " "); + from++; + } + return from; +} + +static char * +stab_to (char *out, int from, int to) +{ + int tab_width = 8; + int to_mod = (to / tab_width) * tab_width; + while (from < to_mod) + { + *out++ = '\t'; + from = (((from / tab_width) + 1) * tab_width); + } + while (from < to) + { + *out++ = ' '; + from++; + } + return out; +} + +static int +string_columns (const char *string, int length, int start) +{ + int tab_width = 8; + int col = start; + const char *end = string + length; + while (string < end) + { + if (*string == '\n') + col = 0; + else if (*string == '\t') + col = (((col / tab_width) + 1) * tab_width); + else + col++; + string++; + } + return col; +} + + +static void +write_entry (FILE *out, const char *key, const char *value) +{ + char *v = strdup(value ? value : ""); + char *v2 = v; + char *nl = 0; + int col; + Bool programs_p = (!strcmp(key, "programs")); + int tab = (programs_p ? 32 : 16); + Bool first = True; + + fprintf(out, "%s:", key); + col = strlen(key) + 1; + + if (strlen(key) > 14) + col = tab_to (out, col, 20); + + while (1) + { + if (!programs_p) + v2 = strip(v2); + nl = strchr(v2, '\n'); + if (nl) + *nl = 0; + + if (first && programs_p) + { + col = tab_to (out, col, 77); + fprintf (out, " \\\n"); + col = 0; + } + + if (first) + first = False; + else + { + col = tab_to (out, col, 75); + fprintf (out, " \\n\\\n"); + col = 0; + } + + if (!programs_p) + col = tab_to (out, col, tab); + + if (programs_p && + string_columns(v2, strlen (v2), col) + col > 75) + { + int L = strlen (v2); + int start = 0; + int end = start; + while (start < L) + { + while (v2[end] == ' ' || v2[end] == '\t') + end++; + while (v2[end] != ' ' && v2[end] != '\t' && + v2[end] != '\n' && v2[end] != 0) + end++; + if (string_columns (v2 + start, (end - start), col) >= 74) + { + col = tab_to (out, col, 75); + fprintf(out, " \\\n"); + col = tab_to (out, 0, tab + 2); + while (v2[start] == ' ' || v2[start] == '\t') + start++; + } + + col = string_columns (v2 + start, (end - start), col); + while (start < end) + fputc(v2[start++], out); + } + } + else + { + fprintf (out, "%s", v2); + col += string_columns(v2, strlen (v2), col); + } + + if (nl) + v2 = nl + 1; + else + break; + } + + fprintf(out, "\n"); + free(v); +} + +int +write_init_file (Display *dpy, + saver_preferences *p, const char *version_string, + Bool verbose_p) +{ + int status = -1; + const char *name = init_file_name(); + const char *tmp_name = init_file_tmp_name(); + char *n2 = chase_symlinks (name); + struct stat st; + int i, j; + + /* Kludge, since these aren't in the saver_preferences struct as strings... + */ + char *visual_name; + char *programs; + Bool overlay_stderr_p; + char *stderr_font; + FILE *out; + + if (!name) goto END; + + if (n2) name = n2; + + /* Throttle the various timeouts to reasonable values before writing + the file to disk. */ + stop_the_insanity (p); + + + if (verbose_p) + fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name); + + unlink (tmp_name); + out = fopen(tmp_name, "w"); + if (!out) + { + char *buf = (char *) malloc(1024 + strlen(name)); + sprintf(buf, "%s: error writing \"%s\"", blurb(), name); + perror(buf); + free(buf); + goto END; + } + + /* Give the new .xscreensaver file the same permissions as the old one; + except ensure that it is readable and writable by owner, and not + executable. Extra hack: if we're running as root, make the file + be world-readable (so that the daemon, running as "nobody", will + still be able to read it.) + */ + if (stat(name, &st) == 0) + { + mode_t mode = st.st_mode; + mode |= S_IRUSR | S_IWUSR; /* read/write by user */ + mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); /* executable by none */ + + if (getuid() == (uid_t) 0) /* read by group/other */ + mode |= S_IRGRP | S_IROTH; + + if (fchmod (fileno(out), mode) != 0) + { + char *buf = (char *) malloc(1024 + strlen(name)); + sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(), + tmp_name, (unsigned int) mode); + perror(buf); + free(buf); + goto END; + } + } + + /* Kludge, since these aren't in the saver_preferences struct... */ + visual_name = get_string_resource (dpy, "visualID", "VisualID"); + programs = 0; + overlay_stderr_p = get_boolean_resource (dpy, "overlayStderr", "Boolean"); + stderr_font = get_string_resource (dpy, "font", "Font"); + + i = 0; + { + char *ss; + char **hack_strings = (char **) + calloc (p->screenhacks_count, sizeof(char *)); + + for (j = 0; j < p->screenhacks_count; j++) + { + hack_strings[j] = format_hack (dpy, p->screenhacks[j], True); + i += strlen (hack_strings[j]); + i += 2; + } + + ss = programs = (char *) malloc(i + 10); + *ss = 0; + for (j = 0; j < p->screenhacks_count; j++) + { + strcat (ss, hack_strings[j]); + free (hack_strings[j]); + ss += strlen(ss); + *ss++ = '\n'; + *ss = 0; + } + free (hack_strings); + } + + { + struct passwd *pw = getpwuid (getuid ()); + char *whoami = (pw && pw->pw_name && *pw->pw_name + ? pw->pw_name + : ""); + time_t now = time ((time_t *) 0); + char *timestr = (char *) ctime (&now); + char *nl = (char *) strchr (timestr, '\n'); + if (nl) *nl = 0; + fprintf (out, + "# %s Preferences File\n" + "# Written by %s %s for %s on %s.\n" + "# http://www.jwz.org/xscreensaver/\n" + "\n", + progclass, progname, version_string, whoami, timestr); + } + + for (j = 0; prefs[j]; j++) + { + char buf[255]; + const char *pr = prefs[j]; + enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time + } type = pref_str; + const char *s = 0; + int i = 0; + Bool b = False; + Time t = 0; + + if (pr && !*pr) + { + fprintf(out, "\n"); + continue; + } + +# undef CHECK +# define CHECK(X) else if (!strcmp(pr, X)) + if (!pr || !*pr) ; + CHECK("timeout") type = pref_time, t = p->timeout; + CHECK("cycle") type = pref_time, t = p->cycle; + CHECK("lock") type = pref_bool, b = p->lock_p; + CHECK("lockVTs") continue; /* don't save, unused */ + CHECK("lockTimeout") type = pref_time, t = p->lock_timeout; + CHECK("passwdTimeout") type = pref_time, t = p->passwd_timeout; + CHECK("visualID") type = pref_str, s = visual_name; + CHECK("installColormap") type = pref_bool, b = p->install_cmap_p; + CHECK("verbose") type = pref_bool, b = p->verbose_p; + CHECK("timestamp") type = pref_bool, b = p->timestamp_p; + CHECK("splash") type = pref_bool, b = p->splash_p; + CHECK("splashDuration") type = pref_time, t = p->splash_duration; +# ifdef QUAD_MODE + CHECK("quad") type = pref_bool, b = p->quad_p; +# else /* !QUAD_MODE */ + CHECK("quad") continue; /* don't save */ +# endif /* !QUAD_MODE */ + CHECK("demoCommand") type = pref_str, s = p->demo_command; + CHECK("prefsCommand") type = pref_str, s = p->prefs_command; +/* CHECK("helpURL") type = pref_str, s = p->help_url; */ + CHECK("helpURL") continue; /* don't save */ +/* CHECK("loadURL") type = pref_str, s = p->load_url_command; */ + CHECK("loadURL") continue; /* don't save */ +/* CHECK("newLoginCommand") type = pref_str, s = p->new_login_command; */ + CHECK("newLoginCommand") continue; /* don't save */ + CHECK("nice") type = pref_int, i = p->nice_inferior; + CHECK("memoryLimit") type = pref_byte, i = p->inferior_memory_limit; + CHECK("fade") type = pref_bool, b = p->fade_p; + CHECK("unfade") type = pref_bool, b = p->unfade_p; + CHECK("fadeSeconds") type = pref_time, t = p->fade_seconds; + CHECK("fadeTicks") type = pref_int, i = p->fade_ticks; + CHECK("captureStderr") type = pref_bool, b = p->capture_stderr_p; + CHECK("captureStdout") continue; /* don't save */ + CHECK("logFile") continue; /* don't save */ + CHECK("ignoreUninstalledPrograms") + type = pref_bool, b = p->ignore_uninstalled_p; + + CHECK("font") type = pref_str, s = stderr_font; + + CHECK("dpmsEnabled") type = pref_bool, b = p->dpms_enabled_p; + CHECK("dpmsStandby") type = pref_time, t = p->dpms_standby; + CHECK("dpmsSuspend") type = pref_time, t = p->dpms_suspend; + CHECK("dpmsOff") type = pref_time, t = p->dpms_off; + + CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p; + CHECK("grabVideoFrames") type =pref_bool, b = p->grab_video_p; + CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p; + CHECK("imageDirectory") type =pref_str, s = p->image_directory; + + CHECK("mode") type = pref_str, + s = (p->mode == ONE_HACK ? "one" : + p->mode == BLANK_ONLY ? "blank" : + p->mode == DONT_BLANK ? "off" : + p->mode == RANDOM_HACKS_SAME + ? "random-same" + : "random"); + CHECK("selected") type = pref_int, i = p->selected_hack; + + CHECK("textMode") type = pref_str, + s = (p->tmode == TEXT_URL ? "url" : + p->tmode == TEXT_LITERAL ? "literal" : + p->tmode == TEXT_FILE ? "file" : + p->tmode == TEXT_PROGRAM ? "program" : + "date"); + CHECK("textLiteral") type = pref_str, s = p->text_literal; + CHECK("textFile") type = pref_str, s = p->text_file; + CHECK("textProgram") type = pref_str, s = p->text_program; + CHECK("textURL") type = pref_str, s = p->text_url; + + CHECK("programs") type = pref_str, s = programs; + CHECK("pointerPollTime") type = pref_time, t = p->pointer_timeout; + CHECK("pointerHysteresis")type = pref_int, i = p->pointer_hysteresis; + CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout; + CHECK("initialDelay") type = pref_time, t = p->initial_delay; + CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension; + CHECK("mitSaverExtension") continue; /* don't save */ + CHECK("xidleExtension") type = pref_bool, b = p->use_xidle_extension; + CHECK("procInterrupts") type = pref_bool, b = p->use_proc_interrupts; + CHECK("GetViewPortIsFullOfLies") type = pref_bool, + b = p->getviewport_full_of_lies_p; + CHECK("overlayStderr") type = pref_bool, b = overlay_stderr_p; + CHECK("overlayTextBackground") continue; /* don't save */ + CHECK("overlayTextForeground") continue; /* don't save */ + CHECK("bourneShell") continue; + else abort(); +# undef CHECK + + switch (type) + { + case pref_str: + break; + case pref_int: + sprintf(buf, "%d", i); + s = buf; + break; + case pref_bool: + s = b ? "True" : "False"; + break; + case pref_time: + { + unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000); + if (sec >= 60) + { + min += (sec / 60); + sec %= 60; + } + if (min >= 60) + { + hour += (min / 60); + min %= 60; + } + sprintf (buf, "%u:%02u:%02u", hour, min, sec); + s = buf; + } + break; + case pref_byte: + { + if (i >= (1<<30) && i == ((i >> 30) << 30)) + sprintf(buf, "%dG", i >> 30); + else if (i >= (1<<20) && i == ((i >> 20) << 20)) + sprintf(buf, "%dM", i >> 20); + else if (i >= (1<<10) && i == ((i >> 10) << 10)) + sprintf(buf, "%dK", i >> 10); + else + sprintf(buf, "%d", i); + s = buf; + } + break; + default: + abort(); + break; + } + + if (pr && (!strcmp(pr, "mode") || !strcmp(pr, "textMode"))) + fprintf(out, "\n"); + + write_entry (out, pr, s); + } + + fprintf(out, "\n"); + + if (visual_name) free(visual_name); + if (stderr_font) free(stderr_font); + if (programs) free(programs); + + if (fclose(out) == 0) + { + time_t write_date = 0; + + if (stat(tmp_name, &st) == 0) + { + write_date = st.st_mtime; + } + else + { + char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name)); + sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name); + perror(buf); + unlink (tmp_name); + free(buf); + goto END; + } + + if (rename (tmp_name, name) != 0) + { + char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name)); + sprintf(buf, "%s: error renaming \"%s\" to \"%s\"", + blurb(), tmp_name, name); + perror(buf); + unlink (tmp_name); + free(buf); + goto END; + } + else + { + p->init_file_date = write_date; + + /* Since the .xscreensaver file is used for IPC, let's try and make + sure that the bits actually land on the disk right away. */ + sync (); + + status = 0; /* wrote and renamed successfully! */ + } + } + else + { + char *buf = (char *) malloc(1024 + strlen(name)); + sprintf(buf, "%s: error closing \"%s\"", blurb(), name); + perror(buf); + free(buf); + unlink (tmp_name); + goto END; + } + + END: + if (n2) free (n2); + return status; +} + + +/* Parsing the resource database + */ + +void +free_screenhack (screenhack *hack) +{ + if (hack->visual) free (hack->visual); + if (hack->name) free (hack->name); + free (hack->command); + memset (hack, 0, sizeof(*hack)); + free (hack); +} + +static void +free_screenhack_list (screenhack **list, int count) +{ + int i; + if (!list) return; + for (i = 0; i < count; i++) + if (list[i]) + free_screenhack (list[i]); + free (list); +} + + + +/* Populate `saver_preferences' with the contents of the resource database. + Note that this may be called multiple times -- it is re-run each time + the ~/.xscreensaver file is reloaded. + + This function can be very noisy, since it issues resource syntax errors + and so on. + */ +void +load_init_file (Display *dpy, saver_preferences *p) +{ + static Bool first_time = True; + + screenhack **system_default_screenhacks = 0; + int system_default_screenhack_count = 0; + + if (first_time) + { + /* Get the programs resource before the .xscreensaver file has been + parsed and merged into the resource database for the first time: + this is the value of *programs from the app-defaults file. + Then clear it out so that it will be parsed again later, after + the init file has been read. + */ + get_screenhacks (dpy, p); + system_default_screenhacks = p->screenhacks; + system_default_screenhack_count = p->screenhacks_count; + p->screenhacks = 0; + p->screenhacks_count = 0; + } + + if (parse_init_file (p) != 0) /* file might have gone away */ + if (!first_time) return; + + first_time = False; + + p->xsync_p = get_boolean_resource (dpy, "synchronous", "Synchronous"); + p->verbose_p = get_boolean_resource (dpy, "verbose", "Boolean"); + p->timestamp_p = get_boolean_resource (dpy, "timestamp", "Boolean"); + p->lock_p = get_boolean_resource (dpy, "lock", "Boolean"); + p->fade_p = get_boolean_resource (dpy, "fade", "Boolean"); + p->unfade_p = get_boolean_resource (dpy, "unfade", "Boolean"); + p->fade_seconds = 1000 * get_seconds_resource (dpy, "fadeSeconds", "Time"); + p->fade_ticks = get_integer_resource (dpy, "fadeTicks", "Integer"); + p->install_cmap_p = get_boolean_resource (dpy, "installColormap", "Boolean"); + p->nice_inferior = get_integer_resource (dpy, "nice", "Nice"); + p->inferior_memory_limit = get_byte_resource (dpy, "memoryLimit", + "MemoryLimit"); + p->splash_p = get_boolean_resource (dpy, "splash", "Boolean"); +# ifdef QUAD_MODE + p->quad_p = get_boolean_resource (dpy, "quad", "Boolean"); +# endif + p->capture_stderr_p = get_boolean_resource (dpy, "captureStderr", "Boolean"); + p->ignore_uninstalled_p = get_boolean_resource (dpy, + "ignoreUninstalledPrograms", + "Boolean"); + + p->initial_delay = 1000 * get_seconds_resource (dpy, "initialDelay", "Time"); + p->splash_duration = 1000 * get_seconds_resource (dpy, "splashDuration", "Time"); + p->timeout = 1000 * get_minutes_resource (dpy, "timeout", "Time"); + p->lock_timeout = 1000 * get_minutes_resource (dpy, "lockTimeout", "Time"); + p->cycle = 1000 * get_minutes_resource (dpy, "cycle", "Time"); + p->passwd_timeout = 1000 * get_seconds_resource (dpy, "passwdTimeout", "Time"); + p->pointer_timeout = 1000 * get_seconds_resource (dpy, "pointerPollTime", "Time"); + p->pointer_hysteresis = get_integer_resource (dpy, "pointerHysteresis","Integer"); + p->notice_events_timeout = 1000*get_seconds_resource(dpy, + "windowCreationTimeout", + "Time"); + + p->dpms_enabled_p = get_boolean_resource (dpy, "dpmsEnabled", "Boolean"); + p->dpms_standby = 1000 * get_minutes_resource (dpy, "dpmsStandby", "Time"); + p->dpms_suspend = 1000 * get_minutes_resource (dpy, "dpmsSuspend", "Time"); + p->dpms_off = 1000 * get_minutes_resource (dpy, "dpmsOff", "Time"); + + p->grab_desktop_p = get_boolean_resource (dpy, "grabDesktopImages", "Boolean"); + p->grab_video_p = get_boolean_resource (dpy, "grabVideoFrames", "Boolean"); + p->random_image_p = get_boolean_resource (dpy, "chooseRandomImages", "Boolean"); + p->image_directory = get_string_resource (dpy, + "imageDirectory", + "ImageDirectory"); + + p->text_literal = get_string_resource (dpy, "textLiteral", "TextLiteral"); + p->text_file = get_string_resource (dpy, "textFile", "TextFile"); + p->text_program = get_string_resource (dpy, "textProgram", "TextProgram"); + p->text_url = get_string_resource (dpy, "textURL", "TextURL"); + + p->shell = get_string_resource (dpy, "bourneShell", "BourneShell"); + + p->demo_command = get_string_resource(dpy, "demoCommand", "URL"); + p->prefs_command = get_string_resource(dpy, "prefsCommand", "URL"); + p->help_url = get_string_resource(dpy, "helpURL", "URL"); + p->load_url_command = get_string_resource(dpy, "loadURL", "LoadURL"); + p->new_login_command = get_string_resource(dpy, + "newLoginCommand", + "NewLoginCommand"); + + /* If "*splash" is unset, default to true. */ + { + char *s = get_string_resource (dpy, "splash", "Boolean"); + if (s) + free (s); + else + p->splash_p = True; + } + + /* If "*grabDesktopImages" is unset, default to true. */ + { + char *s = get_string_resource (dpy, "grabDesktopImages", "Boolean"); + if (s) + free (s); + else + p->grab_desktop_p = True; + } + + p->use_xidle_extension = get_boolean_resource (dpy, "xidleExtension","Boolean"); +#if 0 /* ignore this, it is evil. */ + p->use_mit_saver_extension = get_boolean_resource (dpy, + "mitSaverExtension", + "Boolean"); +#endif + p->use_sgi_saver_extension = get_boolean_resource (dpy, + "sgiSaverExtension", + "Boolean"); + p->use_proc_interrupts = get_boolean_resource (dpy, + "procInterrupts", "Boolean"); + + p->getviewport_full_of_lies_p = + get_boolean_resource (dpy, "GetViewPortIsFullOfLies", "Boolean"); + + get_screenhacks (dpy, p); /* Parse the "programs" resource. */ + + { + char *s = get_string_resource (dpy, "selected", "Integer"); + if (!s || !*s) + p->selected_hack = -1; + else + p->selected_hack = get_integer_resource (dpy, "selected", "Integer"); + if (s) free (s); + if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count) + p->selected_hack = -1; + } + + { + char *s = get_string_resource (dpy, "mode", "Mode"); + if (s && !strcasecmp (s, "one")) p->mode = ONE_HACK; + else if (s && !strcasecmp (s, "blank")) p->mode = BLANK_ONLY; + else if (s && !strcasecmp (s, "off")) p->mode = DONT_BLANK; + else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME; + else p->mode = RANDOM_HACKS; + if (s) free (s); + } + + { + char *s = get_string_resource (dpy, "textMode", "TextMode"); + if (s && !strcasecmp (s, "url")) p->tmode = TEXT_URL; + else if (s && !strcasecmp (s, "literal")) p->tmode = TEXT_LITERAL; + else if (s && !strcasecmp (s, "file")) p->tmode = TEXT_FILE; + else if (s && !strcasecmp (s, "program")) p->tmode = TEXT_PROGRAM; + else p->tmode = TEXT_DATE; + if (s) free (s); + } + + if (system_default_screenhack_count) /* note: first_time is also true */ + { + merge_system_screenhacks (dpy, p, system_default_screenhacks, + system_default_screenhack_count); + free_screenhack_list (system_default_screenhacks, + system_default_screenhack_count); + system_default_screenhacks = 0; + system_default_screenhack_count = 0; + } + + if (p->debug_p) + { + p->xsync_p = True; + p->verbose_p = True; + p->timestamp_p = True; + p->initial_delay = 0; + } + + /* Throttle the various timeouts to reasonable values after reading the + disk file. */ + stop_the_insanity (p); +} + + +/* If there are any hacks in the system-wide defaults that are not in + the ~/.xscreensaver file, add the new ones to the end of the list. + This does *not* actually save the file. + */ +static void +merge_system_screenhacks (Display *dpy, saver_preferences *p, + screenhack **system_list, int system_count) +{ + /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy, + so fuck it. */ + + int made_space = 0; + int i; + for (i = 0; i < system_count; i++) + { + int j; + Bool matched_p = False; + + for (j = 0; j < p->screenhacks_count; j++) + { + char *name; + if (!system_list[i]->name) + system_list[i]->name = make_hack_name (dpy, + system_list[i]->command); + + name = p->screenhacks[j]->name; + if (!name) + name = make_hack_name (dpy, p->screenhacks[j]->command); + + matched_p = !strcasecmp (name, system_list[i]->name); + + if (name != p->screenhacks[j]->name) + free (name); + + if (matched_p) + break; + } + + if (!matched_p) + { + /* We have an entry in the system-wide list that is not in the + user's .xscreensaver file. Add it to the end. + Note that p->screenhacks is a single malloc block, not a + linked list, so we have to realloc it. + */ + screenhack *oh = system_list[i]; + screenhack *nh = (screenhack *) malloc (sizeof(screenhack)); + + if (made_space == 0) + { + made_space = 10; + p->screenhacks = (screenhack **) + realloc (p->screenhacks, + (p->screenhacks_count + made_space + 1) + * sizeof(screenhack)); + if (!p->screenhacks) abort(); + } + + nh->enabled_p = oh->enabled_p; + nh->visual = oh->visual ? strdup(oh->visual) : 0; + nh->name = oh->name ? strdup(oh->name) : 0; + nh->command = oh->command ? strdup(oh->command) : 0; + + p->screenhacks[p->screenhacks_count++] = nh; + p->screenhacks[p->screenhacks_count] = 0; + made_space--; + +#if 0 + fprintf (stderr, "%s: noticed new hack: %s\n", blurb(), + (nh->name ? nh->name : make_hack_name (dpy, nh->command))); +#endif + } + } +} + + + +/* Parsing the programs resource. + */ + +screenhack * +parse_screenhack (const char *line) +{ + screenhack *h = (screenhack *) calloc (1, sizeof(*h)); + const char *s; + + h->enabled_p = True; + + while (isspace(*line)) line++; /* skip whitespace */ + if (*line == '-') /* handle "-" */ + { + h->enabled_p = False; + line++; + while (isspace(*line)) line++; /* skip whitespace */ + } + + s = line; /* handle "visual:" */ + while (*line && *line != ':' && *line != '"' && !isspace(*line)) + line++; + if (*line != ':') + line = s; + else + { + h->visual = (char *) malloc (line-s+1); + strncpy (h->visual, s, line-s); + h->visual[line-s] = 0; + if (*line == ':') line++; /* skip ":" */ + while (isspace(*line)) line++; /* skip whitespace */ + } + + if (*line == '"') /* handle "name" */ + { + line++; + s = line; + while (*line && *line != '"') + line++; + h->name = (char *) malloc (line-s+1); + strncpy (h->name, s, line-s); + h->name[line-s] = 0; + if (*line == '"') line++; /* skip "\"" */ + while (isspace(*line)) line++; /* skip whitespace */ + } + + h->command = format_command (line, False); /* handle command */ + return h; +} + + +static char * +format_command (const char *cmd, Bool wrap_p) +{ + int tab = 30; + int col = tab; + char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1)); + const char *in = cmd; + char *out = cmd2; + while (*in) + { + /* shrink all whitespace to one space, for the benefit of the "demo" + mode display. We only do this when we can easily tell that the + whitespace is not significant (no shell metachars). + */ + switch (*in) + { + case '\'': case '"': case '`': case '\\': + /* Metachars are scary. Copy the rest of the line unchanged. */ + while (*in) + *out++ = *in++, col++; + break; + + case ' ': case '\t': + /* Squeeze all other whitespace down to one space. */ + while (*in == ' ' || *in == '\t') + in++; + *out++ = ' ', col++; + break; + + default: + /* Copy other chars unchanged. */ + *out++ = *in++, col++; + break; + } + } + + *out = 0; + + /* Strip trailing whitespace */ + while (out > cmd2 && isspace (out[-1])) + *(--out) = 0; + + return cmd2; +} + + +/* Returns a new string describing the shell command. + This may be just the name of the program, capitalized. + It also may be something from the resource database (gotten + by looking for "hacks.XYZ.name", where XYZ is the program.) + */ +char * +make_hack_name (Display *dpy, const char *shell_command) +{ + char *s = strdup (shell_command); + char *s2; + char res_name[255]; + + for (s2 = s; *s2; s2++) /* truncate at first whitespace */ + if (isspace (*s2)) + { + *s2 = 0; + break; + } + + s2 = strrchr (s, '/'); /* if pathname, take last component */ + if (s2) + { + s2 = strdup (s2+1); + free (s); + s = s2; + } + + if (strlen (s) > 50) /* 51 is hereby defined as "unreasonable" */ + s[50] = 0; + + sprintf (res_name, "hacks.%s.name", s); /* resource? */ + s2 = get_string_resource (dpy, res_name, res_name); + if (s2) + { + free (s); + return s2; + } + + for (s2 = s; *s2; s2++) /* if it has any capitals, return it */ + if (*s2 >= 'A' && *s2 <= 'Z') + return s; + + if (s[0] >= 'a' && s[0] <= 'z') /* else cap it */ + s[0] -= 'a'-'A'; + if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z') /* (magic leading X) */ + s[1] -= 'a'-'A'; + if (s[0] == 'G' && s[1] == 'l' && + s[2] >= 'a' && s[2] <= 'z') /* (magic leading GL) */ + s[1] -= 'a'-'A', + s[2] -= 'a'-'A'; + return s; +} + + +char * +format_hack (Display *dpy, screenhack *hack, Bool wrap_p) +{ + int tab = 32; + int size; + char *h2, *out, *s; + int col = 0; + + char *def_name = make_hack_name (dpy, hack->command); + + /* Don't ever write out a name for a hack if it's the same as the default. + */ + if (hack->name && !strcmp (hack->name, def_name)) + { + free (hack->name); + hack->name = 0; + } + free (def_name); + + size = (2 * (strlen(hack->command) + + (hack->visual ? strlen(hack->visual) : 0) + + (hack->name ? strlen(hack->name) : 0) + + tab)); + h2 = (char *) malloc (size); + out = h2; + + if (!hack->enabled_p) *out++ = '-'; /* write disabled flag */ + + if (hack->visual && *hack->visual) /* write visual name */ + { + if (hack->enabled_p) *out++ = ' '; + *out++ = ' '; + strcpy (out, hack->visual); + out += strlen (hack->visual); + *out++ = ':'; + *out++ = ' '; + } + + *out = 0; + col = string_columns (h2, strlen (h2), 0); + + if (hack->name && *hack->name) /* write pretty name */ + { + int L = (strlen (hack->name) + 2); + if (L + col < tab) + out = stab_to (out, col, tab - L - 2); + else + *out++ = ' '; + *out++ = '"'; + strcpy (out, hack->name); + out += strlen (hack->name); + *out++ = '"'; + *out = 0; + + col = string_columns (h2, strlen (h2), 0); + if (wrap_p && col >= tab) + out = stab_to (out, col, 77); + else + *out++ = ' '; + + if (out >= h2+size) abort(); + } + + *out = 0; + col = string_columns (h2, strlen (h2), 0); + out = stab_to (out, col, tab); /* indent */ + + if (out >= h2+size) abort(); + s = format_command (hack->command, wrap_p); + strcpy (out, s); + out += strlen (s); + free (s); + *out = 0; + + return h2; +} + + +static void +get_screenhacks (Display *dpy, saver_preferences *p) +{ + int i, j; + int start = 0; + int end = 0; + int size; + char *d; + + d = get_string_resource (dpy, "monoPrograms", "MonoPrograms"); + if (d && !*d) { free(d); d = 0; } + if (!d) + d = get_string_resource (dpy, "colorPrograms", "ColorPrograms"); + if (d && !*d) { free(d); d = 0; } + + if (d) + { + fprintf (stderr, + "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\ + see the manual for details.\n", blurb()); + free(d); + } + + d = get_string_resource (dpy, "programs", "Programs"); + + free_screenhack_list (p->screenhacks, p->screenhacks_count); + p->screenhacks = 0; + p->screenhacks_count = 0; + + if (!d || !*d) + return; + + size = strlen (d); + + + /* Count up the number of newlines (which will be equal to or larger than + one less than the number of hacks.) + */ + for (i = j = 0; d[i]; i++) + if (d[i] == '\n') + j++; + j++; + + p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *)); + + /* Iterate over the lines in `d' (the string with newlines) + and make new strings to stuff into the `screenhacks' array. + */ + p->screenhacks_count = 0; + while (start < size) + { + /* skip forward over whitespace. */ + while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n') + start++; + + /* skip forward to newline or end of string. */ + end = start; + while (d[end] != 0 && d[end] != '\n') + end++; + + /* null terminate. */ + d[end] = 0; + + p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start); + if (p->screenhacks_count >= i) + abort(); + + start = end+1; + } + + free (d); + + if (p->screenhacks_count == 0) + { + free (p->screenhacks); + p->screenhacks = 0; + } +} + + +/* Make sure all the values in the preferences struct are sane. + */ +static void +stop_the_insanity (saver_preferences *p) +{ + if (p->passwd_timeout <= 0) p->passwd_timeout = 30000; /* 30 secs */ + if (p->timeout < 15000) p->timeout = 15000; /* 15 secs */ + if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000; /* 2 secs */ + if (p->pointer_timeout <= 0) p->pointer_timeout = 5000; /* 5 secs */ + if (p->notice_events_timeout <= 0) + p->notice_events_timeout = 10000; /* 10 secs */ + if (p->fade_seconds <= 0 || p->fade_ticks <= 0) + p->fade_p = False; + if (! p->fade_p) p->unfade_p = False; + + /* The DPMS settings may have the value 0. + But if they are negative, or are a range less than 10 seconds, + reset them to sensible defaults. (Since that must be a mistake.) + */ + if (p->dpms_standby != 0 && + p->dpms_standby < 10 * 1000) + p->dpms_standby = 2 * 60 * 60 * 1000; /* 2 hours */ + if (p->dpms_suspend != 0 && + p->dpms_suspend < 10 * 1000) + p->dpms_suspend = 2 * 60 * 60 * 1000; /* 2 hours */ + if (p->dpms_off != 0 && + p->dpms_off < 10 * 1000) + p->dpms_off = 4 * 60 * 60 * 1000; /* 4 hours */ + + /* suspend may not be greater than off, unless off is 0. + standby may not be greater than suspend, unless suspend is 0. + */ + if (p->dpms_off != 0 && + p->dpms_suspend > p->dpms_off) + p->dpms_suspend = p->dpms_off; + if (p->dpms_suspend != 0 && + p->dpms_standby > p->dpms_suspend) + p->dpms_standby = p->dpms_suspend; + + /* These fixes above ignores the case + suspend = 0 and standby > off ... + */ + if (p->dpms_off != 0 && + p->dpms_standby > p->dpms_off) + p->dpms_standby = p->dpms_off; + + + if (p->dpms_standby == 0 && /* if *all* are 0, then DPMS is disabled */ + p->dpms_suspend == 0 && + p->dpms_off == 0) + p->dpms_enabled_p = False; + + + /* Set watchdog timeout to about half of the cycle timeout, but + don't let it be faster than 1/2 minute or slower than 1 minute. + */ + p->watchdog_timeout = p->cycle * 0.6; + if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000; /* 27 secs */ + if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000; /* 57 secs */ + + if (p->pointer_hysteresis < 0) p->pointer_hysteresis = 0; + if (p->pointer_hysteresis > 100) p->pointer_hysteresis = 100; +} diff --git a/driver/prefs.h b/driver/prefs.h new file mode 100644 index 00000000..eeb272ef --- /dev/null +++ b/driver/prefs.h @@ -0,0 +1,35 @@ +/* xscreensaver, Copyright (c) 1993-2006 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifndef __XSCREENSAVER_PREFS_H__ +#define __XSCREENSAVER_PREFS_H__ + +#include "types.h" + +extern void load_init_file (Display *, saver_preferences *); +extern Bool init_file_changed_p (saver_preferences *); +extern int write_init_file (Display *, + saver_preferences *, const char *version_string, + Bool verbose_p); +const char *init_file_name (void); + +extern screenhack *parse_screenhack (const char *line); +extern void free_screenhack (screenhack *); +extern char *format_hack (Display *, screenhack *, Bool wrap_p); +char *make_hack_name (Display *, const char *shell_command); + +/* From dpms.c */ +extern void sync_server_dpms_settings (Display *, Bool enabled_p, + int standby_secs, int suspend_secs, + int off_secs, + Bool verbose_p); + +#endif /* __XSCREENSAVER_PREFS_H__ */ diff --git a/driver/remote.c b/driver/remote.c new file mode 100644 index 00000000..1e67c973 --- /dev/null +++ b/driver/remote.c @@ -0,0 +1,580 @@ +/* xscreensaver-command, Copyright (c) 1991-2009 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_SELECT_H +# include +#endif /* HAVE_SYS_SELECT_H */ + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include /* for CARD32 */ +#include +#include +#include /* for XGetClassHint() */ +#include + +#include "remote.h" + +#ifdef _VROOT_H_ +ERROR! you must not include vroot.h in this file +#endif + +extern char *progname; +extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE; +extern Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_EXIT; +extern Atom XA_VROOT, XA_SELECT, XA_DEMO, XA_BLANK, XA_LOCK; + + +static XErrorHandler old_handler = 0; +static Bool got_badwindow = False; +static int +BadWindow_ehandler (Display *dpy, XErrorEvent *error) +{ + if (error->error_code == BadWindow) + { + got_badwindow = True; + return 0; + } + else + { + fprintf (stderr, "%s: ", progname); + if (!old_handler) abort(); + return (*old_handler) (dpy, error); + } +} + + + +static Window +find_screensaver_window (Display *dpy, char **version) +{ + int i; + Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy)); + Window root2, parent, *kids; + unsigned int nkids; + + if (version) *version = 0; + + if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) + abort (); + if (root != root2) + abort (); + if (parent) + abort (); + if (! (kids && nkids)) + return 0; + for (i = 0; i < nkids; i++) + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *v; + int status; + + /* We're walking the list of root-level windows and trying to find + the one that has a particular property on it. We need to trap + BadWindows errors while doing this, because it's possible that + some random window might get deleted in the meantime. (That + window won't have been the one we're looking for.) + */ + XSync (dpy, False); + if (old_handler) abort(); + got_badwindow = False; + old_handler = XSetErrorHandler (BadWindow_ehandler); + status = XGetWindowProperty (dpy, kids[i], + XA_SCREENSAVER_VERSION, + 0, 200, False, XA_STRING, + &type, &format, &nitems, &bytesafter, + &v); + XSync (dpy, False); + XSetErrorHandler (old_handler); + old_handler = 0; + + if (got_badwindow) + { + status = BadWindow; + got_badwindow = False; + } + + if (status == Success && type != None) + { + Window ret = kids[i]; + if (version) + *version = (char *) v; + XFree (kids); + return ret; + } + } + + if (kids) XFree (kids); + return 0; +} + + +static int +send_xscreensaver_command (Display *dpy, Atom command, long arg, + Window *window_ret, char **error_ret) +{ + int status = -1; + char *v = 0; + Window window = find_screensaver_window (dpy, &v); + XWindowAttributes xgwa; + char err[2048]; + + if (window_ret) + *window_ret = window; + + if (!window) + { + sprintf (err, "no screensaver is running on display %s", + DisplayString (dpy)); + + if (error_ret) + { + *error_ret = strdup (err); + status = -1; + goto DONE; + } + + if (command == XA_EXIT) + { + /* Don't print an error if xscreensaver is already dead. */ + status = 1; + goto DONE; + } + + fprintf (stderr, "%s: %s\n", progname, err); + status = -1; + goto DONE; + } + + /* Select for property change events, so that we can read the response. */ + XGetWindowAttributes (dpy, window, &xgwa); + XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask); + + if (command == XA_SCREENSAVER_STATUS || + command == XA_SCREENSAVER_VERSION) + { + XClassHint hint; + memset (&hint, 0, sizeof(hint)); + if (!v || !*v) + { + sprintf (err, "version property not set on window 0x%x?", + (unsigned int) window); + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + + status = -1; + goto DONE; + } + + XGetClassHint(dpy, window, &hint); + if (!hint.res_class) + { + sprintf (err, "class hints not set on window 0x%x?", + (unsigned int) window); + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + + status = -1; + goto DONE; + } + + fprintf (stdout, "%s %s", hint.res_class, v); + + if (command != XA_SCREENSAVER_STATUS) + { + fprintf (stdout, "\n"); + } + else + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + + if (XGetWindowProperty (dpy, + RootWindow (dpy, 0), + XA_SCREENSAVER_STATUS, + 0, 999, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type + && dataP) + { + Atom blanked; + time_t tt; + char *s; + Atom *data = (Atom *) dataP; + + if (type != XA_INTEGER || nitems < 3) + { + STATUS_LOSE: + if (data) free (data); + fprintf (stdout, "\n"); + fflush (stdout); + fprintf (stderr, "bad status format on root window.\n"); + status = -1; + goto DONE; + } + + blanked = (Atom) data[0]; + tt = (time_t) data[1]; + + if (tt <= (time_t) 666000000L) /* early 1991 */ + goto STATUS_LOSE; + + if (blanked == XA_BLANK) + fputs (": screen blanked since ", stdout); + else if (blanked == XA_LOCK) + fputs (": screen locked since ", stdout); + else if (blanked == 0) + /* suggestions for a better way to phrase this are welcome. */ + fputs (": screen non-blanked since ", stdout); + else + /* `blanked' has an unknown value - fail. */ + goto STATUS_LOSE; + + s = ctime(&tt); + if (s[strlen(s)-1] == '\n') + s[strlen(s)-1] = 0; + fputs (s, stdout); + + { + int nhacks = nitems - 2; + Bool any = False; + int i; + for (i = 0; i < nhacks; i++) + if (data[i + 2] > 0) + { + any = True; + break; + } + + if (any && nhacks == 1) + fprintf (stdout, " (hack #%d)\n", (int) data[2]); + else if (any) + { + fprintf (stdout, " (hacks: "); + for (i = 0; i < nhacks; i++) + { + fprintf (stdout, "#%d", (int) data[2 + i]); + if (i != nhacks-1) + fputs (", ", stdout); + } + fputs (")\n", stdout); + } + else + fputs ("\n", stdout); + } + + if (data) free (data); + } + else + { + if (dataP) XFree (dataP); + fprintf (stdout, "\n"); + fflush (stdout); + fprintf (stderr, "no saver status on root window.\n"); + status = -1; + goto DONE; + } + } + + /* No need to read a response for these commands. */ + status = 1; + goto DONE; + } + else + { + XEvent event; + long arg1 = arg; + long arg2 = 0; + + if (arg < 0) + abort(); + else if (arg == 0 && command == XA_SELECT) + abort(); + else if (arg != 0 && command == XA_DEMO) + { + arg1 = 5000; /* version number of the XA_DEMO protocol, */ + arg2 = arg; /* since it didn't use to take an argument. */ + } + + event.xany.type = ClientMessage; + event.xclient.display = dpy; + event.xclient.window = window; + event.xclient.message_type = XA_SCREENSAVER; + event.xclient.format = 32; + memset (&event.xclient.data, 0, sizeof(event.xclient.data)); + event.xclient.data.l[0] = (long) command; + event.xclient.data.l[1] = arg1; + event.xclient.data.l[2] = arg2; + if (! XSendEvent (dpy, window, False, 0L, &event)) + { + sprintf (err, "XSendEvent(dpy, 0x%x ...) failed.\n", + (unsigned int) window); + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + status = -1; + goto DONE; + } + } + + status = 0; + + DONE: + if (v) free (v); + XSync (dpy, 0); + return status; +} + + +static Bool +xscreensaver_command_event_p (Display *dpy, XEvent *event, XPointer arg) +{ + return (event->xany.type == PropertyNotify && + event->xproperty.state == PropertyNewValue && + event->xproperty.atom == XA_SCREENSAVER_RESPONSE); +} + + +static int +xscreensaver_command_response (Display *dpy, Window window, + Bool verbose_p, Bool exiting_p, + char **error_ret) +{ + int sleep_count = 0; + char err[2048]; + XEvent event; + Bool got_event = False; + + while (!(got_event = XCheckIfEvent(dpy, &event, + &xscreensaver_command_event_p, 0)) && + sleep_count++ < 10) + { + sleep(1); + } + + if (!got_event) + { + sprintf (err, "no response to command."); + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + + return -1; + } + else + { + Status st2; + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *msg = 0; + + XSync (dpy, False); + if (old_handler) abort(); + old_handler = XSetErrorHandler (BadWindow_ehandler); + st2 = XGetWindowProperty (dpy, window, + XA_SCREENSAVER_RESPONSE, + 0, 1024, True, + AnyPropertyType, + &type, &format, &nitems, &bytesafter, + &msg); + XSync (dpy, False); + XSetErrorHandler (old_handler); + old_handler = 0; + + if (got_badwindow) + { + if (exiting_p) + return 0; + + sprintf (err, "xscreensaver window unexpectedly deleted."); + + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + + return -1; + } + + if (st2 == Success && type != None) + { + if (type != XA_STRING || format != 8) + { + sprintf (err, "unrecognized response property."); + + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + + if (msg) XFree (msg); + return -1; + } + else if (!msg || (msg[0] != '+' && msg[0] != '-')) + { + sprintf (err, "unrecognized response message."); + + if (error_ret) + *error_ret = strdup (err); + else + fprintf (stderr, "%s: %s\n", progname, err); + + if (msg) XFree (msg); + return -1; + } + else + { + int ret = (msg[0] == '+' ? 0 : -1); + sprintf (err, "%s: %s\n", progname, (char *) msg+1); + + if (error_ret) + *error_ret = strdup (err); + else if (verbose_p || ret != 0) + fprintf ((ret < 0 ? stderr : stdout), "%s\n", err); + + XFree (msg); + return ret; + } + } + } + + return -1; /* warning suppression: not actually reached */ +} + + +int +xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p, + char **error_ret) +{ + Window w = 0; + int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret); + if (status == 0) + status = xscreensaver_command_response (dpy, w, verbose_p, + (command == XA_EXIT), + error_ret); + + fflush (stdout); + fflush (stderr); + return (status < 0 ? status : 0); +} + + +void +server_xscreensaver_version (Display *dpy, + char **version_ret, + char **user_ret, + char **host_ret) +{ + Window window = find_screensaver_window (dpy, 0); + + Atom type; + int format; + unsigned long nitems, bytesafter; + + if (version_ret) + *version_ret = 0; + if (user_ret) + *user_ret = 0; + if (host_ret) + *host_ret = 0; + + if (!window) + return; + + if (version_ret) + { + unsigned char *v = 0; + XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &v); + if (v) + { + *version_ret = strdup ((char *) v); + XFree (v); + } + } + + if (user_ret || host_ret) + { + unsigned char *id = 0; + const char *user = 0; + const char *host = 0; + + XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &id); + if (id && *id) + { + const char *old_tag = " on host "; + const char *s = strstr ((char *) id, old_tag); + if (s) + { + /* found ID of the form "1234 on host xyz". */ + user = 0; + host = s + strlen (old_tag); + } + else + { + char *o = 0, *p = 0, *c = 0; + o = strchr ((char *) id, '('); + if (o) p = strchr (o, '@'); + if (p) c = strchr (p, ')'); + if (c) + { + /* found ID of the form "1234 (user@host)". */ + user = o+1; + host = p+1; + *p = 0; + *c = 0; + } + } + + } + + if (user && *user && *user != '?') + *user_ret = strdup (user); + else + *user_ret = 0; + + if (host && *host && *host != '?') + *host_ret = strdup (host); + else + *host_ret = 0; + + if (id) + XFree (id); + } +} diff --git a/driver/remote.h b/driver/remote.h new file mode 100644 index 00000000..e1db3517 --- /dev/null +++ b/driver/remote.h @@ -0,0 +1,24 @@ +/* xscreensaver-command, Copyright (c) 1991-1998 + * by Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifndef _XSCREENSAVER_REMOTE_H_ +#define _XSCREENSAVER_REMOTE_H_ + +extern int xscreensaver_command (Display *dpy, Atom command, long arg, + Bool verbose_p, char **error_ret); + +extern void server_xscreensaver_version (Display *dpy, + char **version_ret, + char **user_ret, + char **host_ret); + +#endif /* _XSCREENSAVER_REMOTE_H_ */ diff --git a/driver/screens.c b/driver/screens.c new file mode 100644 index 00000000..0a7eade8 --- /dev/null +++ b/driver/screens.c @@ -0,0 +1,1077 @@ +/* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports. + * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* There are a bunch of different mechanisms for multiple monitors + * available in X. XScreenSaver needs to care about this for two + * reasons: first, to ensure that all visible areas go black; and + * second, so that the windows of screen savers exactly fill the + * glass of each monitor (instead of one saver spanning multiple + * monitors, or a monitor displaying only a sub-rectangle of the + * screen saver.) + * + * 1) Multi-screen: + * + * This is the original way. Each monitor gets its own display + * number. :0.0 is the first one, :0.1 is the next, etc. The + * value of $DISPLAY determines which screen windows open on by + * default. A single app can open windows on multiple screens + * with the same display connection, but windows cannot be moved + * from one screen to another. The mouse can be moved from one + * screen to another, though. Screens may be different depths + * (e.g., one can be TrueColor and one can be PseudoColor.) + * Screens cannot be resized or moved without restarting X. + * + * Everyone hates this way of doing things because of the + * inability to move a window from one screen to another without + * restarting the application. + * + * 2) Xinerama: + * + * There is a single giant root window that spans all the + * monitors. All monitors are the same depth, and windows can be + * moved around. Applications can learn which rectangles are + * actually visible on monitors by querying the Xinerama server + * extension. (If you don't do that, you end up with dialog + * boxes that try to appear in the middle of the screen actually + * spanning the gap between two monitors.) + * + * Xinerama doesn't work with DRI, which means that if you use + * it, you lose hardware acceleration on OpenGL programs. Also, + * screens can't be resized or moved without restarting X. + * + * 3) Vidmode Viewports: + * + * With this extension, the root window can be bigger than the + * monitor. Moving the mouse near the edges of the screen + * scrolls around, like a pan-and-scan movie. There can also be + * a hot key for changing the monitor's resolution (zooming + * in/out). + * + * Trying to combine this with Xinerama crashes the server, so + * you can only use this if you have only a single screen, or are + * in old-multi-screen mode. + * + * Also, half the time it doesn't work at all: it tends to lie + * about the size of the rectangle in use. + * + * 4) RANDR 1.0: + * + * The first version of the "Resize and Rotate" extension let you + * change the resolution of a screen on the fly. The root window + * would actually resize. However, it was also incompatible with + * Xinerama (did it crash, or just do nothing? I can't remember) + * so you needed to be in single-screen or old multi-screen mode. + * I believe RANDR could co-exist with Vidmode Viewports, but I'm + * not sure. + * + * 5) RANDR 1.2: + * + * Finally, RANDR added the functionality of Xinerama, plus some. + * Each X screen (in the sense of #1, "multi-screen") can have a + * number of sub-rectangles that are displayed on monitors, and + * each of those sub-rectangles can be displayed on more than one + * monitor. So it's possible (I think) to have a hybrid of + * multi-screen and Xinerama (e.g., to have two monitors running + * in one depth, and three monitors running in another?) + * Typically though, there will be a single X screen, with + * Xinerama-like division of that large root window onto multiple + * monitors. Also everything's dynamic: monitors can be added, + * removed, and resized at runtime. + * + * I believe that as of RANDR 1.2, the Xinerama extension still + * exists but only as a compatiblity layer: it's actually + * returning data from the RANDR extension. + * + * Though RANDR 1.2 allows the same image to be cloned onto more + * than one monitor, and also allows one monitor to show a + * subsection of something on another monitor (e.g., the + * rectangles can be enclosed or overlap). Since there's no way + * to put seperate savers on those duplicated-or-overlapping + * monitors, xscreensaver just ignores them (which allows them to + * display duplicates or overlaps). + * + * 5a) Nvidia fucks it up: + * + * Nvidia drivers as of Aug 2008 running in "TwinView" mode + * apparently report correct screen geometry via Xinerama, but + * report one giant screen via RANDR. The response from the + * nvidia developers is, "we don't support RANDR, use Xinerama + * instead." Which is a seriously lame answer. So, xscreensaver + * has to query *both* extensions, and make a guess as to which + * is to be believed. + * + * 5b) Also sometimes RANDR says stupid shit like, "You have one + * screen, and it has no available orientations or sizes." + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#ifdef HAVE_RANDR +# include +#endif /* HAVE_RANDR */ + +#ifdef HAVE_XINERAMA +# include +#endif /* HAVE_XINERAMA */ + +#ifdef HAVE_XF86VMODE +# include +#endif /* HAVE_XF86VMODE */ + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" +#include "visual.h" + + +typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP, + S_OFFSCREEN, S_DISABLED } monitor_sanity; + +/* 'typedef monitor' is in types.h */ +struct _monitor { + int id; + char *desc; + Screen *screen; + int x, y, width, height; + monitor_sanity sanity; /* I'm not crazy you're the one who's crazy */ + int enemy; /* which monitor it overlaps or duplicates */ + char *err; /* msg to print at appropriate later time; + exists only on monitor #0. */ +}; + +static Bool layouts_differ_p (monitor **a, monitor **b); + + +static void +free_monitors (monitor **monitors) +{ + monitor **m2 = monitors; + if (! monitors) return; + while (*m2) + { + if ((*m2)->desc) free ((*m2)->desc); + if ((*m2)->err) free ((*m2)->err); + free (*m2); + m2++; + } + free (monitors); +} + + +static char * +append (char *s1, const char *s2) +{ + char *s = (char *) malloc ((s1 ? strlen(s1) : 0) + + (s2 ? strlen(s2) : 0) + 3); + *s = 0; + if (s1) strcat (s, s1); + if (s1 && s2) strcat (s, "\n"); + if (s2) strcat (s, s2); + if (s1) free (s1); + return s; +} + + +#ifdef HAVE_XINERAMA + +static monitor ** +xinerama_scan_monitors (Display *dpy, char **errP) +{ + Screen *screen = DefaultScreenOfDisplay (dpy); + int event, error, nscreens, i; + XineramaScreenInfo *xsi; + monitor **monitors; + + if (! XineramaQueryExtension (dpy, &event, &error)) + return 0; + + if (! XineramaIsActive (dpy)) + return 0; + + xsi = XineramaQueryScreens (dpy, &nscreens); + if (!xsi) return 0; + + monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors)); + if (!monitors) return 0; + + for (i = 0; i < nscreens; i++) + { + monitor *m = (monitor *) calloc (1, sizeof (monitor)); + monitors[i] = m; + m->id = i; + m->screen = screen; + m->x = xsi[i].x_org; + m->y = xsi[i].y_org; + m->width = xsi[i].width; + m->height = xsi[i].height; + } + return monitors; +} + +#endif /* HAVE_XINERAMA */ + + +#ifdef HAVE_XF86VMODE + +static monitor ** +vidmode_scan_monitors (Display *dpy, char **errP) +{ + int event, error, nscreens, i; + monitor **monitors; + + /* Note that XF86VidModeGetViewPort() tends to be full of lies on laptops + that have a docking station or external monitor that runs in a different + resolution than the laptop's screen: + + http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=81593 + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208417 + http://bugs.xfree86.org/show_bug.cgi?id=421 + + Presumably this is fixed by using RANDR instead of VidMode. + */ + +# ifdef HAVE_XINERAMA + /* Attempts to use the VidMode extension when the Xinerama extension is + active can result in a server crash! Yay! */ + if (XQueryExtension (dpy, "XINERAMA", &error, &event, &error)) + return 0; +# endif /* !HAVE_XINERAMA */ + + if (! XF86VidModeQueryExtension (dpy, &event, &error)) + return 0; + + nscreens = ScreenCount (dpy); + monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors)); + if (!monitors) return 0; + + for (i = 0; i < nscreens; i++) + { + monitor *m = (monitor *) calloc (1, sizeof (monitor)); + XF86VidModeModeLine ml; + int dot; + Screen *screen = ScreenOfDisplay (dpy, i); + + monitors[i] = m; + m->id = i; + m->screen = screen; + + if (! safe_XF86VidModeGetViewPort (dpy, i, &m->x, &m->y)) + m->x = m->y = -1; + + if (XF86VidModeGetModeLine (dpy, i, &dot, &ml)) + { + m->width = ml.hdisplay; + m->height = ml.vdisplay; + } + + /* Apparently, though the server stores the X position in increments of + 1 pixel, it will only make changes to the *display* in some other + increment. With XF86_SVGA on a Thinkpad, the display only updates + in multiples of 8 pixels when in 8-bit mode, and in multiples of 4 + pixels in 16-bit mode. I don't know what it does in 24- and 32-bit + mode, because I don't have enough video memory to find out. + + I consider it a bug that XF86VidModeGetViewPort() is telling me the + server's *target* scroll position rather than the server's *actual* + scroll position. David Dawes agrees, and says they may fix this in + XFree86 4.0, but it's notrivial. + + He also confirms that this behavior is server-dependent, so the + actual scroll position cannot be reliably determined by the client. + So... that means the only solution is to provide a ``sandbox'' + around the blackout window -- we make the window be up to N pixels + larger than the viewport on both the left and right sides. That + means some part of the outer edges of each hack might not be + visible, but screw it. + + I'm going to guess that 16 pixels is enough, and that the Y dimension + doesn't have this problem. + + The drawback of doing this, of course, is that some of the screenhacks + will still look pretty stupid -- for example, "slidescreen" will cut + off the left and right edges of the grid, etc. + */ +# define FUDGE 16 + if (m->x > 0 && m->x < m->width - ml.hdisplay) + { + /* Not at left edge or right edge: + Round X position down to next lower multiple of FUDGE. + Increase width by 2*FUDGE in case some server rounds up. + */ + m->x = ((m->x - 1) / FUDGE) * FUDGE; + m->width += (FUDGE * 2); + } +# undef FUDGE + } + + return monitors; +} + +#endif /* HAVE_XF86VMODE */ + + +#ifdef HAVE_RANDR + +static monitor ** +randr_scan_monitors (Display *dpy, char **errP) +{ + int event, error, major, minor, nscreens, i, j; + monitor **monitors; + Bool new_randr_p = False; + + if (! XRRQueryExtension (dpy, &event, &error)) + return 0; + + if (! XRRQueryVersion (dpy, &major, &minor)) + return 0; + + if (major <= 0) /* Protocol was still in flux back then -- fuck it. */ + return 0; + +# ifdef HAVE_RANDR_12 + new_randr_p = (major > 1 || (major == 1 && minor >= 2)); +# endif + + if (! new_randr_p) + /* RANDR 1.0 -- no Xinerama-like virtual screens. */ + nscreens = ScreenCount (dpy); + else /* RANDR 1.2 or newer -- built-in Xinerama */ + { +# ifdef HAVE_RANDR_12 + int xsc = ScreenCount (dpy); + nscreens = 0; + /* Add up the virtual screens on each X screen. */ + for (i = 0; i < xsc; i++) + { + XRRScreenResources *res = + XRRGetScreenResources (dpy, RootWindow (dpy, i)); + nscreens += res->noutput; + XRRFreeScreenResources (res); + } +# endif /* HAVE_RANDR_12 */ + } + + if (nscreens <= 0) + { + *errP = append (*errP, + "WARNING: RANDR reported no screens! Ignoring it."); + return 0; + } + + monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors)); + if (!monitors) return 0; + + for (i = 0, j = 0; i < ScreenCount (dpy); i++) + { + Screen *screen = ScreenOfDisplay (dpy, i); + + if (! new_randr_p) /* RANDR 1.0 */ + { + XRRScreenConfiguration *rrc; + monitor *m = (monitor *) calloc (1, sizeof (monitor)); + monitors[i] = m; + m->screen = screen; + m->id = i; + + rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen)); + if (rrc) + { + SizeID size = -1; + Rotation rot = ~0; + XRRScreenSize *rrsizes; + int nsizes = 0; + + size = XRRConfigCurrentConfiguration (rrc, &rot); + rrsizes = XRRConfigSizes (rrc, &nsizes); + + if (nsizes <= 0) /* WTF? Shouldn't happen but does. */ + { + m->width = DisplayWidth (dpy, i); + m->height = DisplayHeight (dpy, i); + } + else if (rot & (RR_Rotate_90|RR_Rotate_270)) + { + m->width = rrsizes[size].height; + m->height = rrsizes[size].width; + } + else + { + m->width = rrsizes[size].width; + m->height = rrsizes[size].height; + } + + /* don't free 'rrsizes' */ + XRRFreeScreenConfigInfo (rrc); + } + } + else /* RANDR 1.2 or newer */ + { +# ifdef HAVE_RANDR_12 + int k; + XRRScreenResources *res = + XRRGetScreenResources (dpy, RootWindowOfScreen (screen)); + for (k = 0; k < res->noutput; k++, j++) + { + monitor *m = (monitor *) calloc (1, sizeof (monitor)); + XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, + res->outputs[k]); + RRCrtc crtc = (rroi->crtc ? rroi->crtc : + rroi->ncrtc ? rroi->crtcs[0] : 0); + XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0); + + monitors[j] = m; + m->screen = screen; + m->id = (i * 1000) + j; + m->desc = (rroi->name ? strdup (rroi->name) : 0); + + if (crtci) + { + /* Note: if the screen is rotated, XRRConfigSizes contains + the unrotated WxH, but XRRCrtcInfo contains rotated HxW. + */ + m->x = crtci->x; + m->y = crtci->y; + m->width = crtci->width; + m->height = crtci->height; + } + + if (rroi->connection == RR_Disconnected) + m->sanity = S_DISABLED; + /* #### do the same for RR_UnknownConnection? */ + + if (crtci) + XRRFreeCrtcInfo (crtci); + XRRFreeOutputInfo (rroi); + } + XRRFreeScreenResources (res); +# endif /* HAVE_RANDR_12 */ + } + } + + /* Work around more fucking brain damage. */ + { + int ok = 0; + int i = 0; + while (monitors[i]) + { + if (monitors[i]->width != 0 && monitors[i]->height != 0) + ok++; + i++; + } + if (! ok) + { + *errP = append (*errP, + "WARNING: RANDR says all screens are 0x0! Ignoring it."); + free_monitors (monitors); + monitors = 0; + } + } + + return monitors; +} + +#endif /* HAVE_RANDR */ + + +static monitor ** +basic_scan_monitors (Display *dpy, char **errP) +{ + int nscreens = ScreenCount (dpy); + int i; + monitor **monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors)); + if (!monitors) return 0; + + for (i = 0; i < nscreens; i++) + { + Screen *screen = ScreenOfDisplay (dpy, i); + monitor *m = (monitor *) calloc (1, sizeof (monitor)); + monitors[i] = m; + m->id = i; + m->screen = screen; + m->x = 0; + m->y = 0; + m->width = WidthOfScreen (screen); + m->height = HeightOfScreen (screen); + } + return monitors; +} + + +#if defined(HAVE_RANDR) && defined(HAVE_XINERAMA) + +/* From: Aaron Plattner + Date: August 7, 2008 10:21:25 AM PDT + To: linux-bugs@nvidia.com + + The NVIDIA X driver does not yet support RandR 1.2. The X server has + a compatibility layer in it that allows RandR 1.2 clients to talk to + RandR 1.1 drivers through an RandR 1.2 pseudo-output called "default". + This reports the total combined resolution of the TwinView display, + since it doesn't have any visibility into TwinView metamodes. There + is no way for the driver to prevent the server from turning on this + compatibility layer. + + The intention is for X client applications to continue to use the + Xinerama extension to query the screen geometry. RandR 1.2 reports + its own Xinerama info for this purpose. I would recommend against + modifying xscreensaver to try to get this information from RandR. + */ +static monitor ** +randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors, + char **errP) +{ + monitor **xinerama_monitors; + + if (!randr_monitors) + return 0; + + xinerama_monitors = xinerama_scan_monitors (dpy, errP); + if (!xinerama_monitors) + return randr_monitors; + + if (! layouts_differ_p (randr_monitors, xinerama_monitors)) + { + free_monitors (xinerama_monitors); + return randr_monitors; + } + else if ( randr_monitors[0] && !randr_monitors[1] && /* 1 monitor */ + xinerama_monitors[0] && xinerama_monitors[1]) /* >1 monitor */ + { + *errP = append (*errP, + "WARNING: RANDR reports 1 screen but Xinerama\n" + "\t\treports multiple. Believing Xinerama."); + free_monitors (randr_monitors); + return xinerama_monitors; + } + else + { + *errP = append (*errP, + "WARNING: RANDR and Xinerama report different\n" + "\t\tscreen layouts! Believing RANDR."); + free_monitors (xinerama_monitors); + return randr_monitors; + } +} + +#endif /* HAVE_RANDR && HAVE_XINERAMA */ + + +#ifdef DEBUG_MULTISCREEN + +/* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver + will pretend that it is changing the number of connected monitors + every few seconds, using the geometries in the following list, + for stress-testing purposes. + */ +static monitor ** +debug_scan_monitors (Display *dpy, char **errP) +{ + static const char * const geoms[] = { + "1600x1028+0+22", + "1024x768+0+22", + "800x600+0+22", + "800x600+0+22,800x600+800+22", + "800x600+0+22,800x600+800+22,800x600+300+622", + "800x600+0+22,800x600+800+22,800x600+0+622,800x600+800+622", + "640x480+0+22,640x480+640+22,640x480+0+502,640x480+640+502", + "640x480+240+22,640x480+0+502,640x480+640+502", + "640x480+0+200,640x480+640+200", + "800x600+400+22", + "320x200+0+22,320x200+320+22,320x200+640+22,320x200+960+22,320x200+0+222,320x200+320+222,320x200+640+222,320x200+960+222,320x200+0+422,320x200+320+422,320x200+640+422,320x200+960+422,320x200+0+622,320x200+320+622,320x200+640+622,320x200+960+622,320x200+0+822,320x200+320+822,320x200+640+822,320x200+960+822" + }; + static int index = 0; + monitor **monitors = (monitor **) calloc (100, sizeof(*monitors)); + int nscreens = 0; + Screen *screen = DefaultScreenOfDisplay (dpy); + + char *s = strdup (geoms[index]); + char *token = strtok (s, ","); + while (token) + { + monitor *m = calloc (1, sizeof (monitor)); + char c; + m->id = nscreens; + m->screen = screen; + if (4 != sscanf (token, "%dx%d+%d+%d%c", + &m->width, &m->height, &m->x, &m->y, &c)) + abort(); + m->width -= 2; + m->height -= 2; + monitors[nscreens++] = m; + token = strtok (0, ","); + } + free (s); + + index = (index+1) % countof(geoms); + return monitors; +} + +#endif /* DEBUG_MULTISCREEN */ + + +#ifdef QUAD_MODE +static monitor ** +quadruple (monitor **monitors, Bool debug_p, char **errP) +{ + int i, j, count = 0; + monitor **monitors2; + while (monitors[count]) + count++; + monitors2 = (monitor **) calloc (count * 4 + 1, sizeof(*monitors)); + if (!monitors2) abort(); + + for (i = 0, j = 0; i < count; i++) + { + int k; + for (k = 0; k < 4; k++) + { + monitors2[j+k] = (monitor *) calloc (1, sizeof (monitor)); + *monitors2[j+k] = *monitors[i]; + monitors2[j+k]->width /= (debug_p ? 4 : 2); + monitors2[j+k]->height /= 2; + monitors2[j+k]->id = (monitors[i]->id * 4) + k; + monitors2[j+k]->name = (monitors[i]->name + ? strdup (monitors[i]->name) : 0); + } + monitors2[j+1]->x += monitors2[j]->width; + monitors2[j+2]->y += monitors2[j]->height; + monitors2[j+3]->x += monitors2[j]->width; + monitors2[j+3]->y += monitors2[j]->height; + j += 4; + } + + free_monitors (monitors); + return monitors2; +} +#endif /* QUAD_MODE */ + + +static monitor ** +scan_monitors (saver_info *si) +{ + saver_preferences *p = &si->prefs; + monitor **monitors = 0; + char *err = 0; + +# ifdef DEBUG_MULTISCREEN + if (! monitors) monitors = debug_scan_monitors (si->dpy, &err); +# endif + +# ifdef HAVE_RANDR + if (! p->getviewport_full_of_lies_p) + if (! monitors) monitors = randr_scan_monitors (si->dpy, &err); + +# ifdef HAVE_XINERAMA + monitors = randr_versus_xinerama_fight (si->dpy, monitors, &err); +# endif +# endif /* HAVE_RANDR */ + +# ifdef HAVE_XF86VMODE + if (! monitors) monitors = vidmode_scan_monitors (si->dpy, &err); +# endif + +# ifdef HAVE_XINERAMA + if (! monitors) monitors = xinerama_scan_monitors (si->dpy, &err); +# endif + + if (! monitors) monitors = basic_scan_monitors (si->dpy, &err); + +# ifdef QUAD_MODE + if (p->quad_p) + monitors = quadruple (monitors, p->debug_p, &err); +# endif + + if (monitors && err) monitors[0]->err = err; + + return monitors; +} + + +static Bool +monitors_overlap_p (monitor *a, monitor *b) +{ + /* Two rectangles overlap if the max of the tops is less than the + min of the bottoms and the max of the lefts is less than the min + of the rights. + */ +# undef MAX +# undef MIN +# define MAX(A,B) ((A)>(B)?(A):(B)) +# define MIN(A,B) ((A)<(B)?(A):(B)) + + int maxleft = MAX(a->x, b->x); + int maxtop = MAX(a->y, b->y); + int minright = MIN(a->x + a->width - 1, b->x + b->width); + int minbot = MIN(a->y + a->height - 1, b->y + b->height); + return (maxtop < minbot && maxleft < minright); +} + + +static Bool +plausible_aspect_ratio_p (monitor **monitors) +{ + /* Modern wide-screen monitors come in the following aspect ratios: + + One monitor: If you tack a 640x480 monitor + onto the right, the ratio is: + 16 x 9 --> 1.78 + 852 x 480 --> 1.77 852+640 x 480 --> 3.11 "SD 480p" + 1280 x 720 --> 1.78 1280+640 x 720 --> 2.67 "HD 720p" + 1280 x 920 --> 1.39 1280+640 x 920 --> 2.09 + 1366 x 768 --> 1.78 1366+640 x 768 --> 2.61 "HD 768p" + 1440 x 900 --> 1.60 1440+640 x 900 --> 2.31 + 1680 x 1050 --> 1.60 1680+640 x 1050 --> 2.21 + 1690 x 1050 --> 1.61 1690+640 x 1050 --> 2.22 + 1920 x 1080 --> 1.78 1920+640 x 1080 --> 2.37 "HD 1080p" + 1920 x 1200 --> 1.60 1920+640 x 1200 --> 2.13 + 2560 x 1600 --> 1.60 2560+640 x 1600 --> 2.00 + + So that implies that if we ever see an aspect ratio >= 2.0, + we can be pretty sure that the X server is lying to us, and + that's actually two monitors, not one. + */ + if (monitors[0] && !monitors[1] && /* exactly 1 monitor */ + monitors[0]->height && + monitors[0]->width / (double) monitors[0]->height >= 1.9) + return False; + else + return True; +} + + +/* Mark the ones that overlap, etc. + */ +static void +check_monitor_sanity (monitor **monitors) +{ + int i, j, count = 0; + + while (monitors[count]) + count++; + +# define X1 monitors[i]->x +# define X2 monitors[j]->x +# define Y1 monitors[i]->y +# define Y2 monitors[j]->y +# define W1 monitors[i]->width +# define W2 monitors[j]->width +# define H1 monitors[i]->height +# define H2 monitors[j]->height + + /* If a monitor is enclosed by any other monitor, that's insane. + */ + for (i = 0; i < count; i++) + for (j = 0; j < count; j++) + if (i != j && + monitors[i]->sanity == S_SANE && + monitors[j]->sanity == S_SANE && + monitors[i]->screen == monitors[j]->screen && + X2 >= X1 && + Y2 >= Y1 && + (X2+W2) <= (X1+W1) && + (Y2+H2) <= (Y1+H1)) + { + if (X1 == X2 && + Y1 == Y2 && + W1 == W2 && + H1 == H2) + monitors[j]->sanity = S_DUPLICATE; + else + monitors[j]->sanity = S_ENCLOSED; + monitors[j]->enemy = i; + } + + /* After checking for enclosure, check for other lossage against earlier + monitors. We do enclosure first so that we make sure to pick the + larger one. + */ + for (i = 0; i < count; i++) + for (j = 0; j < i; j++) + { + if (monitors[i]->sanity != S_SANE) continue; /* already marked */ + if (monitors[j]->sanity != S_SANE) continue; + if (monitors[i]->screen != monitors[j]->screen) continue; + + if (monitors_overlap_p (monitors[i], monitors[j])) + { + monitors[i]->sanity = S_OVERLAP; + monitors[i]->enemy = j; + } + } + + /* Finally, make sure all monitors have sane positions and sizes. + Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044. + */ + for (i = 0; i < count; i++) + { + if (monitors[i]->sanity != S_SANE) continue; /* already marked */ + if (X1 < 0 || Y1 < 0 || + W1 <= 0 || H1 <= 0 || + X1+W1 >= 0x7FFF || Y1+H1 >= 0x7FFF) + { + monitors[i]->sanity = S_OFFSCREEN; + monitors[i]->enemy = 0; + } + } + +# undef X1 +# undef X2 +# undef Y1 +# undef Y2 +# undef W1 +# undef W2 +# undef H1 +# undef H2 +} + + +static Bool +layouts_differ_p (monitor **a, monitor **b) +{ + if (!a || !b) return True; + while (1) + { + if (!*a) break; + if (!*b) break; + if ((*a)->screen != (*b)->screen || + (*a)->x != (*b)->x || + (*a)->y != (*b)->y || + (*a)->width != (*b)->width || + (*a)->height != (*b)->height) + return True; + a++; + b++; + } + if (*a) return True; + if (*b) return True; + + return False; +} + + +void +describe_monitor_layout (saver_info *si) +{ + monitor **monitors = si->monitor_layout; + int count = 0; + int good_count = 0; + int bad_count = 0; + int implausible_p = !plausible_aspect_ratio_p (monitors); + + while (monitors[count]) + { + if (monitors[count]->sanity == S_SANE) + good_count++; + else + bad_count++; + count++; + } + + if (monitors[0]->err) /* deferred error msg */ + { + char *token = strtok (monitors[0]->err, "\n"); + while (token) + { + fprintf (stderr, "%s: %s\n", blurb(), token); + token = strtok (0, "\n"); + } + free (monitors[0]->err); + monitors[0]->err = 0; + } + + if (count == 0) + fprintf (stderr, "%s: no screens!\n", blurb()); + else + { + int i; + fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count); + for (i = 0; i < count; i++) + { + monitor *m = monitors[i]; + if (m->sanity != S_SANE) continue; + fprintf (stderr, "%s: %3d/%d: %dx%d+%d+%d", + blurb(), m->id, screen_number (m->screen), + m->width, m->height, m->x, m->y); + if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc); + fprintf (stderr, "\n"); + } + if (bad_count > 0) + { + fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count); + for (i = 0; i < count; i++) + { + monitor *m = monitors[i]; + monitor *e = monitors[m->enemy]; + if (m->sanity == S_SANE) continue; + fprintf (stderr, "%s: %3d/%d: %dx%d+%d+%d", + blurb(), m->id, screen_number (m->screen), + m->width, m->height, m->x, m->y); + if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc); + fprintf (stderr, " -- "); + switch (m->sanity) + { + case S_SANE: abort(); break; + case S_ENCLOSED: + fprintf (stderr, "enclosed by %d (%dx%d+%d+%d)\n", + e->id, e->width, e->height, e->x, e->y); + break; + case S_DUPLICATE: + fprintf (stderr, "duplicate of %d\n", e->id); + break; + case S_OVERLAP: + fprintf (stderr, "overlaps %d (%dx%d+%d+%d)\n", + e->id, e->width, e->height, e->x, e->y); + break; + case S_OFFSCREEN: + fprintf (stderr, "off screen (%dx%d)\n", + WidthOfScreen (e->screen), + HeightOfScreen (e->screen)); + break; + case S_DISABLED: + fprintf (stderr, "output disabled\n"); + break; + } + } + } + + if (implausible_p) + fprintf (stderr, + "%s: WARNING: single screen aspect ratio is %dx%d = %.2f\n" + "%s: probable X server bug in Xinerama/RANDR!\n", + blurb(), monitors[0]->width, monitors[0]->height, + monitors[0]->width / (double) monitors[0]->height, + blurb()); + } +} + + +/* Synchronize the contents of si->ssi to the current state of the monitors. + Doesn't change anything if nothing has changed; otherwise, alters and + reuses existing saver_screen_info structs as much as possible. + Returns True if anything changed. + */ +Bool +update_screen_layout (saver_info *si) +{ + monitor **monitors = scan_monitors (si); + int count = 0; + int good_count = 0; + int i, j; + int seen_screens[100] = { 0, }; + + if (! layouts_differ_p (monitors, si->monitor_layout)) + { + free_monitors (monitors); + return False; + } + + free_monitors (si->monitor_layout); + si->monitor_layout = monitors; + check_monitor_sanity (si->monitor_layout); + + while (monitors[count]) + { + if (monitors[count]->sanity == S_SANE) + good_count++; + count++; + } + + if (si->ssi_count == 0) + { + si->ssi_count = 10; + si->screens = (saver_screen_info *) + calloc (sizeof(*si->screens), si->ssi_count); + } + + if (si->ssi_count <= good_count) + { + si->ssi_count = good_count + 10; + si->screens = (saver_screen_info *) + realloc (si->screens, sizeof(*si->screens) * si->ssi_count); + memset (si->screens + si->nscreens, 0, + sizeof(*si->screens) * (si->ssi_count - si->nscreens)); + } + + if (! si->screens) abort(); + + si->nscreens = good_count; + + /* Regenerate the list of GL visuals as needed. */ + if (si->best_gl_visuals) + free (si->best_gl_visuals); + si->best_gl_visuals = 0; + + for (i = 0, j = 0; i < count; i++) + { + monitor *m = monitors[i]; + saver_screen_info *ssi = &si->screens[j]; + Screen *old_screen = ssi->screen; + int sn; + if (monitors[i]->sanity != S_SANE) continue; + + ssi->global = si; + ssi->number = j; + + sn = screen_number (m->screen); + ssi->screen = m->screen; + ssi->real_screen_number = sn; + ssi->real_screen_p = (seen_screens[sn] == 0); + seen_screens[sn]++; + + ssi->default_visual = + get_visual_resource (ssi->screen, "visualID", "VisualID", False); + ssi->current_visual = ssi->default_visual; + ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual); + + /* If the screen changed (or if this is the first time) we need + a new toplevel shell for this screen's depth. + */ + if (ssi->screen != old_screen) + initialize_screen_root_widget (ssi); + + ssi->poll_mouse_last_root_x = -1; + ssi->poll_mouse_last_root_y = -1; + + ssi->x = m->x; + ssi->y = m->y; + ssi->width = m->width; + ssi->height = m->height; + +# ifndef DEBUG_MULTISCREEN + { + saver_preferences *p = &si->prefs; + if (p->debug_p +# ifdef QUAD_MODE + && !p->quad_p +# endif + ) + ssi->width /= 2; + } +# endif + + j++; + } + + si->default_screen = &si->screens[0]; + return True; +} diff --git a/driver/screensaver-properties.desktop.in b/driver/screensaver-properties.desktop.in new file mode 100644 index 00000000..de425279 --- /dev/null +++ b/driver/screensaver-properties.desktop.in @@ -0,0 +1,8 @@ +[Desktop Entry] +Exec=xscreensaver-demo +Icon=xscreensaver +Terminal=false +_Name=Screensaver +_Comment=Change screensaver properties +Type=Application +Categories=Settings;DesktopSettings;Security;X-XFCE; diff --git a/driver/setuid.c b/driver/setuid.c new file mode 100644 index 00000000..3ac78e4f --- /dev/null +++ b/driver/setuid.c @@ -0,0 +1,361 @@ +/* setuid.c --- management of runtime privileges. + * xscreensaver, Copyright (c) 1993-1998, 2005 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include /* not used for much... */ + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" + +#ifndef EPERM +#include +#endif + +#include /* for getpwnam() and struct passwd */ +#include /* for getgrgid() and struct group */ + +static const char * +uid_gid_string (uid_t uid, gid_t gid) +{ + static char buf[255]; + struct passwd *p = 0; + struct group *g = 0; + p = getpwuid (uid); + g = getgrgid (gid); + sprintf (buf, "%.100s/%.100s (%ld/%ld)", + (p && p->pw_name ? p->pw_name : "???"), + (g && g->gr_name ? g->gr_name : "???"), + (long) uid, (long) gid); + return buf; +} + + +void +describe_uids (saver_info *si, FILE *out) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + uid_t euid = geteuid(); + gid_t egid = getegid(); + char *s1 = strdup (uid_gid_string (uid, gid)); + char *s2 = strdup (uid_gid_string (euid, egid)); + + if (si->orig_uid && *si->orig_uid && + (!!strcmp (si->orig_uid, s1) || + !!strcmp (si->orig_uid, s2))) + fprintf (out, "%s: initial effective uid/gid was %s\n", blurb(), + si->orig_uid); + + fprintf (out, "%s: running as %s", blurb(), s1); + if (uid != euid || gid != egid) + fprintf (out, "; effectively %s", s2); + fprintf(out, "\n"); + free(s1); + free(s2); +} + + +/* Returns true if we need to call setgroups(). + + Without calling setgroups(), the process will retain any supplementary + gids associated with the uid, e.g.: + + % groups root + root : root bin daemon sys adm disk wheel + + However, setgroups() can only be called by root, and returns EPERM + for other users even if the call would be a no-op (e.g., setting the + group list to the current list.) So, to avoid that spurious error, + before calling setgroups() we first check whether the current list + of groups contains only one element, our target group. If so, we + don't need to call setgroups(). + */ +static int +setgroups_needed_p (uid_t target_group) +{ + gid_t groups[1024]; + int n, size; + size = sizeof(groups) / sizeof(gid_t); + n = getgroups (size - 1, groups); + if (n < 0) + { + char buf [1024]; + sprintf (buf, "%s: getgroups(%ld, ...)", blurb(), (long int)(size - 1)); + perror (buf); + return 1; + } + else if (n == 0) /* an empty list means only egid is in effect. */ + return 0; + else if (n == 1 && groups[0] == target_group) /* one element, the target */ + return 0; + else /* more than one, or the wrong one. */ + return 1; +} + + +static int +set_ids_by_number (uid_t uid, gid_t gid, char **message_ret) +{ + int uid_errno = 0; + int gid_errno = 0; + int sgs_errno = 0; + struct passwd *p = getpwuid (uid); + struct group *g = getgrgid (gid); + + if (message_ret) + *message_ret = 0; + + /* Rumor has it that some implementations of of setuid() do nothing + when called with -1; therefore, if the "nobody" user has a uid of + -1, then that would be Really Bad. Rumor further has it that such + systems really ought to be using -2 for "nobody", since that works. + So, if we get a uid (or gid, for good measure) of -1, switch to -2 + instead. Note that this must be done after we've looked up the + user/group names with getpwuid(-1) and/or getgrgid(-1). + */ + if (gid == (gid_t) -1) gid = (gid_t) -2; + if (uid == (uid_t) -1) uid = (uid_t) -2; + + errno = 0; + if (setgroups_needed_p (gid) && + setgroups (1, &gid) < 0) + sgs_errno = errno ? errno : -1; + + errno = 0; + if (setgid (gid) != 0) + gid_errno = errno ? errno : -1; + + errno = 0; + if (setuid (uid) != 0) + uid_errno = errno ? errno : -1; + + if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0) + { + static char buf [1024]; + sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).", + (p && p->pw_name ? p->pw_name : "???"), + (g && g->gr_name ? g->gr_name : "???"), + (long) uid, (long) gid); + if (message_ret) + *message_ret = buf; + return 0; + } + else + { + char buf [1024]; + gid_t groups[1024]; + int n, size; + + if (sgs_errno) + { + sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)", + blurb(), + (g && g->gr_name ? g->gr_name : "???"), + (long) gid); + if (sgs_errno == -1) + fprintf(stderr, "%s: unknown error\n", buf); + else + { + errno = sgs_errno; + perror(buf); + } + + fprintf (stderr, "%s: effective group list: ", blurb()); + size = sizeof(groups) / sizeof(gid_t); + n = getgroups (size - 1, groups); + if (n < 0) + fprintf (stderr, "unknown!\n"); + else + { + int i; + fprintf (stderr, "["); + for (i = 0; i < n; i++) + { + g = getgrgid (groups[i]); + if (i > 0) fprintf (stderr, ", "); + if (g && g->gr_name) fprintf (stderr, "%s", g->gr_name); + else fprintf (stderr, "%ld", (long) groups[i]); + } + fprintf (stderr, "]\n"); + } + } + + if (gid_errno) + { + sprintf (buf, "%s: couldn't set gid to %.100s (%ld)", + blurb(), + (g && g->gr_name ? g->gr_name : "???"), + (long) gid); + if (gid_errno == -1) + fprintf(stderr, "%s: unknown error\n", buf); + else + { + errno = gid_errno; + perror(buf); + } + } + + if (uid_errno) + { + sprintf (buf, "%s: couldn't set uid to %.100s (%ld)", + blurb(), + (p && p->pw_name ? p->pw_name : "???"), + (long) uid); + if (uid_errno == -1) + fprintf(stderr, "%s: unknown error\n", buf); + else + { + errno = uid_errno; + perror(buf); + } + } + + return -1; + } +} + + +/* If we've been run as setuid or setgid to someone else (most likely root) + turn off the extra permissions so that random user-specified programs + don't get special privileges. (On some systems it is necessary to install + this program as setuid root in order to read the passwd file to implement + lock-mode.) + + *** WARNING: DO NOT DISABLE ANY OF THE FOLLOWING CODE! + If you do so, you will open a security hole. See the sections + of the xscreensaver manual titled "LOCKING AND ROOT LOGINS", + and "USING XDM". + */ +void +hack_uid (saver_info *si) +{ + + /* Discard privileges, and set the effective user/group ids to the + real user/group ids. That is, give up our "chmod +s" rights. + */ + { + uid_t euid = geteuid(); + gid_t egid = getegid(); + uid_t uid = getuid(); + gid_t gid = getgid(); + + si->orig_uid = strdup (uid_gid_string (euid, egid)); + + if (uid != euid || gid != egid) + if (set_ids_by_number (uid, gid, &si->uid_message) != 0) + saver_exit (si, 1, 0); + } + + + /* Locking can't work when running as root, because we have no way of + knowing what the user id of the logged in user is (so we don't know + whose password to prompt for.) + + *** WARNING: DO NOT DISABLE THIS CODE! + If you do so, you will open a security hole. See the sections + of the xscreensaver manual titled "LOCKING AND ROOT LOGINS", + and "USING XDM". + */ + if (getuid() == (uid_t) 0) + { + si->locking_disabled_p = True; + si->nolock_reason = "running as root"; + } + + + /* If we're running as root, switch to a safer user. This is above and + beyond the fact that we've disabling locking, above -- the theory is + that running graphics demos as root is just always a stupid thing + to do, since they have probably never been security reviewed and are + more likely to be buggy than just about any other kind of program. + (And that assumes non-malicious code. There are also attacks here.) + + *** WARNING: DO NOT DISABLE THIS CODE! + If you do so, you will open a security hole. See the sections + of the xscreensaver manual titled "LOCKING AND ROOT LOGINS", + and "USING XDM". + */ + if (getuid() == (uid_t) 0) + { + struct passwd *p; + + p = getpwnam ("nobody"); + if (! p) p = getpwnam ("noaccess"); + if (! p) p = getpwnam ("daemon"); + if (! p) + { + fprintf (stderr, + "%s: running as root, and couldn't find a safer uid.\n", + blurb()); + saver_exit(si, 1, 0); + } + + if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0) + saver_exit (si, -1, 0); + } + + + /* If there's anything even remotely funny looking about the passwd struct, + or if we're running as some other user from the list below (a + non-comprehensive selection of users known to be privileged in some way, + and not normal end-users) then disable locking. If it was possible, + switching to "nobody" would be the thing to do, but only root itself has + the privs to do that. + + *** WARNING: DO NOT DISABLE THIS CODE! + If you do so, you will open a security hole. See the sections + of the xscreensaver manual titled "LOCKING AND ROOT LOGINS", + and "USING XDM". + */ + { + uid_t uid = getuid (); /* get it again */ + struct passwd *p = getpwuid (uid); /* get it again */ + + if (!p || + uid == (uid_t) 0 || + uid == (uid_t) -1 || + uid == (uid_t) -2 || + p->pw_uid == (uid_t) 0 || + p->pw_uid == (uid_t) -1 || + p->pw_uid == (uid_t) -2 || + !p->pw_name || + !*p->pw_name || + !strcmp (p->pw_name, "root") || + !strcmp (p->pw_name, "nobody") || + !strcmp (p->pw_name, "noaccess") || + !strcmp (p->pw_name, "operator") || + !strcmp (p->pw_name, "daemon") || + !strcmp (p->pw_name, "bin") || + !strcmp (p->pw_name, "adm") || + !strcmp (p->pw_name, "sys") || + !strcmp (p->pw_name, "games")) + { + static char buf [1024]; + sprintf (buf, "running as %.100s", + (p && p->pw_name && *p->pw_name + ? p->pw_name : "")); + si->nolock_reason = buf; + si->locking_disabled_p = True; + si->dangerous_uid_p = True; + } + } +} diff --git a/driver/splash.c b/driver/splash.c new file mode 100644 index 00000000..491ebe80 --- /dev/null +++ b/driver/splash.c @@ -0,0 +1,860 @@ +/* xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "xscreensaver.h" +#include "resources.h" + +#undef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) + +void +draw_shaded_rectangle (Display *dpy, Window window, + int x, int y, + int width, int height, + int thickness, + unsigned long top_color, + unsigned long bottom_color) +{ + XPoint points[4]; + XGCValues gcv; + GC gc1, gc2; + if (thickness == 0) return; + + gcv.foreground = top_color; + gc1 = XCreateGC (dpy, window, GCForeground, &gcv); + gcv.foreground = bottom_color; + gc2 = XCreateGC (dpy, window, GCForeground, &gcv); + + points [0].x = x; + points [0].y = y; + points [1].x = x + width; + points [1].y = y; + points [2].x = x + width - thickness; + points [2].y = y + thickness; + points [3].x = x; + points [3].y = y + thickness; + XFillPolygon (dpy, window, gc1, points, 4, Convex, CoordModeOrigin); + + points [0].x = x; + points [0].y = y + thickness; + points [1].x = x; + points [1].y = y + height; + points [2].x = x + thickness; + points [2].y = y + height - thickness; + points [3].x = x + thickness; + points [3].y = y + thickness; + XFillPolygon (dpy, window, gc1, points, 4, Convex, CoordModeOrigin); + + points [0].x = x + width; + points [0].y = y; + points [1].x = x + width - thickness; + points [1].y = y + thickness; + points [2].x = x + width - thickness; + points [2].y = y + height - thickness; + points [3].x = x + width; + points [3].y = y + height - thickness; + XFillPolygon (dpy, window, gc2, points, 4, Convex, CoordModeOrigin); + + points [0].x = x; + points [0].y = y + height; + points [1].x = x + width; + points [1].y = y + height; + points [2].x = x + width; + points [2].y = y + height - thickness; + points [3].x = x + thickness; + points [3].y = y + height - thickness; + XFillPolygon (dpy, window, gc2, points, 4, Convex, CoordModeOrigin); + + XFreeGC (dpy, gc1); + XFreeGC (dpy, gc2); +} + + +int +string_width (XFontStruct *font, char *s) +{ + return XTextWidth(font, s, strlen(s)); +} + + +static void update_splash_window (saver_info *si); +static void draw_splash_window (saver_info *si); +static void destroy_splash_window (saver_info *si); +static void unsplash_timer (XtPointer closure, XtIntervalId *id); + +static void do_demo (saver_screen_info *ssi); +#ifdef PREFS_BUTTON +static void do_prefs (saver_screen_info *ssi); +#endif /* PREFS_BUTTON */ +static void do_help (saver_screen_info *ssi); + + +struct splash_dialog_data { + + saver_screen_info *prompt_screen; + XtIntervalId timer; + + Dimension width; + Dimension height; + + char *heading_label; + char *body_label; + char *body2_label; + char *demo_label; +#ifdef PREFS_BUTTON + char *prefs_label; +#endif /* PREFS_BUTTON */ + char *help_label; + + XFontStruct *heading_font; + XFontStruct *body_font; + XFontStruct *button_font; + + Pixel foreground; + Pixel background; + Pixel button_foreground; + Pixel button_background; + Pixel shadow_top; + Pixel shadow_bottom; + + Dimension logo_width; + Dimension logo_height; + Dimension internal_border; + Dimension shadow_width; + + Dimension button_width, button_height; + Dimension demo_button_x, demo_button_y; +#ifdef PREFS_BUTTON + Dimension prefs_button_x, prefs_button_y; +#endif /* PREFS_BUTTON */ + Dimension help_button_x, help_button_y; + + Pixmap logo_pixmap; + Pixmap logo_clipmask; + int logo_npixels; + unsigned long *logo_pixels; + + int pressed; +}; + + +void +make_splash_dialog (saver_info *si) +{ + int x, y, bw; + XSetWindowAttributes attrs; + unsigned long attrmask = 0; + splash_dialog_data *sp; + saver_screen_info *ssi; + Colormap cmap; + char *f; + + if (si->sp_data) + return; + if (!si->prefs.splash_p || + si->prefs.splash_duration <= 0) + return; + + ssi = &si->screens[mouse_screen (si)]; + + if (!ssi || !ssi->screen) + return; /* WTF? Trying to splash while no screens connected? */ + + cmap = DefaultColormapOfScreen (ssi->screen); + + sp = (splash_dialog_data *) calloc (1, sizeof(*sp)); + sp->prompt_screen = ssi; + + sp->heading_label = get_string_resource (si->dpy, + "splash.heading.label", + "Dialog.Label.Label"); + sp->body_label = get_string_resource (si->dpy, + "splash.body.label", + "Dialog.Label.Label"); + sp->body2_label = get_string_resource (si->dpy, + "splash.body2.label", + "Dialog.Label.Label"); + sp->demo_label = get_string_resource (si->dpy, + "splash.demo.label", + "Dialog.Button.Label"); +#ifdef PREFS_BUTTON + sp->prefs_label = get_string_resource (si->dpy, + "splash.prefs.label", + "Dialog.Button.Label"); +#endif /* PREFS_BUTTON */ + sp->help_label = get_string_resource (si->dpy, + "splash.help.label", + "Dialog.Button.Label"); + + if (!sp->heading_label) + sp->heading_label = strdup("ERROR: REESOURCES NOT INSTALLED CORRECTLY"); + if (!sp->body_label) + sp->body_label = strdup("ERROR: REESOURCES NOT INSTALLED CORRECTLY"); + if (!sp->body2_label) + sp->body2_label = strdup("ERROR: REESOURCES NOT INSTALLED CORRECTLY"); + if (!sp->demo_label) sp->demo_label = strdup("ERROR"); +#ifdef PREFS_BUTTON + if (!sp->prefs_label) sp->prefs_label = strdup("ERROR"); +#endif /* PREFS_BUTTON */ + if (!sp->help_label) sp->help_label = strdup("ERROR"); + + /* Put the version number in the label. */ + { + char *s = (char *) malloc (strlen(sp->heading_label) + 20); + sprintf(s, sp->heading_label, si->version); + free (sp->heading_label); + sp->heading_label = s; + } + + f = get_string_resource (si->dpy, "splash.headingFont", "Dialog.Font"); + sp->heading_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!sp->heading_font) sp->heading_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "splash.bodyFont", "Dialog.Font"); + sp->body_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!sp->body_font) sp->body_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + f = get_string_resource(si->dpy, "splash.buttonFont", "Dialog.Font"); + sp->button_font = XLoadQueryFont (si->dpy, (f ? f : "fixed")); + if (!sp->button_font) sp->button_font = XLoadQueryFont (si->dpy, "fixed"); + if (f) free (f); + + sp->foreground = get_pixel_resource (si->dpy, cmap, + "splash.foreground", + "Dialog.Foreground"); + sp->background = get_pixel_resource (si->dpy, cmap, + "splash.background", + "Dialog.Background"); + + if (sp->foreground == sp->background) + { + /* Make sure the error messages show up. */ + sp->foreground = BlackPixelOfScreen (ssi->screen); + sp->background = WhitePixelOfScreen (ssi->screen); + } + + sp->button_foreground = get_pixel_resource (si->dpy, cmap, + "splash.Button.foreground", + "Dialog.Button.Foreground"); + sp->button_background = get_pixel_resource (si->dpy, cmap, + "splash.Button.background", + "Dialog.Button.Background"); + sp->shadow_top = get_pixel_resource (si->dpy, cmap, + "splash.topShadowColor", + "Dialog.Foreground"); + sp->shadow_bottom = get_pixel_resource (si->dpy, cmap, + "splash.bottomShadowColor", + "Dialog.Background"); + + sp->logo_width = get_integer_resource (si->dpy, + "splash.logo.width", + "Dialog.Logo.Width"); + sp->logo_height = get_integer_resource (si->dpy, + "splash.logo.height", + "Dialog.Logo.Height"); + sp->internal_border = get_integer_resource (si->dpy, + "splash.internalBorderWidth", + "Dialog.InternalBorderWidth"); + sp->shadow_width = get_integer_resource (si->dpy, + "splash.shadowThickness", + "Dialog.ShadowThickness"); + + if (sp->logo_width == 0) sp->logo_width = 150; + if (sp->logo_height == 0) sp->logo_height = 150; + if (sp->internal_border == 0) sp->internal_border = 15; + if (sp->shadow_width == 0) sp->shadow_width = 4; + + { + int direction, ascent, descent; + XCharStruct overall; + + sp->width = 0; + sp->height = 0; + + /* Measure the heading_label. */ + XTextExtents (sp->heading_font, + sp->heading_label, strlen(sp->heading_label), + &direction, &ascent, &descent, &overall); + if (overall.width > sp->width) sp->width = overall.width; + sp->height += ascent + descent; + + /* Measure the body_label. */ + XTextExtents (sp->body_font, + sp->body_label, strlen(sp->body_label), + &direction, &ascent, &descent, &overall); + if (overall.width > sp->width) sp->width = overall.width; + sp->height += ascent + descent; + + /* Measure the body2_label. */ + XTextExtents (sp->body_font, + sp->body2_label, strlen(sp->body2_label), + &direction, &ascent, &descent, &overall); + if (overall.width > sp->width) sp->width = overall.width; + sp->height += ascent + descent; + + { + Dimension w2 = 0, w3 = 0, w4 = 0; + Dimension h2 = 0, h3 = 0, h4 = 0; + + /* Measure the Demo button. */ + XTextExtents (sp->button_font, + sp->demo_label, strlen(sp->demo_label), + &direction, &ascent, &descent, &overall); + w2 = overall.width; + h2 = ascent + descent; + +#ifdef PREFS_BUTTON + /* Measure the Prefs button. */ + XTextExtents (sp->button_font, + sp->prefs_label, strlen(sp->prefs_label), + &direction, &ascent, &descent, &overall); + w3 = overall.width; + h3 = ascent + descent; +#else /* !PREFS_BUTTON */ + w3 = 0; + h3 = 0; +#endif /* !PREFS_BUTTON */ + + /* Measure the Help button. */ + XTextExtents (sp->button_font, + sp->help_label, strlen(sp->help_label), + &direction, &ascent, &descent, &overall); + w4 = overall.width; + h4 = ascent + descent; + + w2 = MAX(w2, w3); w2 = MAX(w2, w4); + h2 = MAX(h2, h3); h2 = MAX(h2, h4); + + /* Add some horizontal padding inside the buttons. */ + w2 += ascent; + + w2 += ((ascent + descent) / 2) + (sp->shadow_width * 2); + h2 += ((ascent + descent) / 2) + (sp->shadow_width * 2); + + sp->button_width = w2; + sp->button_height = h2; + +#ifdef PREFS_BUTTON + w2 *= 3; +#else /* !PREFS_BUTTON */ + w2 *= 2; +#endif /* !PREFS_BUTTON */ + + w2 += ((ascent + descent) * 2); /* for space between buttons */ + + if (w2 > sp->width) sp->width = w2; + sp->height += h2; + } + + sp->width += (sp->internal_border * 2); + sp->height += (sp->internal_border * 3); + + if (sp->logo_height > sp->height) + sp->height = sp->logo_height; + else if (sp->height > sp->logo_height) + sp->logo_height = sp->height; + + sp->logo_width = sp->logo_height; + + sp->width += sp->logo_width; + } + + attrmask |= CWOverrideRedirect; attrs.override_redirect = True; + attrmask |= CWEventMask; + attrs.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask); + + { + int sx = 0, sy = 0, w, h; + int mouse_x = 0, mouse_y = 0; + + { + Window pointer_root, pointer_child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + if (XQueryPointer (si->dpy, + RootWindowOfScreen (ssi->screen), + &pointer_root, &pointer_child, + &root_x, &root_y, &win_x, &win_y, &mask)) + { + mouse_x = root_x; + mouse_y = root_y; + } + } + + x = ssi->x; + y = ssi->y; + w = ssi->width; + h = ssi->height; + if (si->prefs.debug_p) w /= 2; + x = sx + (((w + sp->width) / 2) - sp->width); + y = sy + (((h + sp->height) / 2) - sp->height); + if (x < sx) x = sx; + if (y < sy) y = sy; + } + + bw = get_integer_resource (si->dpy, + "splash.borderWidth", + "Dialog.BorderWidth"); + + si->splash_dialog = + XCreateWindow (si->dpy, + RootWindowOfScreen(ssi->screen), + x, y, sp->width, sp->height, bw, + DefaultDepthOfScreen (ssi->screen), InputOutput, + DefaultVisualOfScreen(ssi->screen), + attrmask, &attrs); + XSetWindowBackground (si->dpy, si->splash_dialog, sp->background); + + sp->logo_pixmap = xscreensaver_logo (ssi->screen, + /* same visual as si->splash_dialog */ + DefaultVisualOfScreen (ssi->screen), + si->splash_dialog, cmap, + sp->background, + &sp->logo_pixels, &sp->logo_npixels, + &sp->logo_clipmask, True); + + XMapRaised (si->dpy, si->splash_dialog); + XSync (si->dpy, False); + + si->sp_data = sp; + + sp->timer = XtAppAddTimeOut (si->app, si->prefs.splash_duration, + unsplash_timer, (XtPointer) si); + + draw_splash_window (si); + XSync (si->dpy, False); +} + + +static void +draw_splash_window (saver_info *si) +{ + splash_dialog_data *sp = si->sp_data; + XGCValues gcv; + GC gc1, gc2; + int hspacing, vspacing, height; + int x1, x2, x3, y1, y2; + int sw; + +#ifdef PREFS_BUTTON + int nbuttons = 3; +#else /* !PREFS_BUTTON */ + int nbuttons = 2; +#endif /* !PREFS_BUTTON */ + + height = (sp->heading_font->ascent + sp->heading_font->descent + + sp->body_font->ascent + sp->body_font->descent + + sp->body_font->ascent + sp->body_font->descent + + sp->button_font->ascent + sp->button_font->descent); + vspacing = ((sp->height + - (4 * sp->shadow_width) + - (2 * sp->internal_border) + - height) / 5); + if (vspacing < 0) vspacing = 0; + if (vspacing > (sp->heading_font->ascent * 2)) + vspacing = (sp->heading_font->ascent * 2); + + gcv.foreground = sp->foreground; + gc1 = XCreateGC (si->dpy, si->splash_dialog, GCForeground, &gcv); + gc2 = XCreateGC (si->dpy, si->splash_dialog, GCForeground, &gcv); + x1 = sp->logo_width; + x3 = sp->width - (sp->shadow_width * 2); + y1 = sp->internal_border; + + /* top heading + */ + XSetFont (si->dpy, gc1, sp->heading_font->fid); + sw = string_width (sp->heading_font, sp->heading_label); + x2 = (x1 + ((x3 - x1 - sw) / 2)); + y1 += sp->heading_font->ascent; + XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1, + sp->heading_label, strlen(sp->heading_label)); + y1 += sp->heading_font->descent; + + /* text below top heading + */ + XSetFont (si->dpy, gc1, sp->body_font->fid); + y1 += vspacing + sp->body_font->ascent; + sw = string_width (sp->body_font, sp->body_label); + x2 = (x1 + ((x3 - x1 - sw) / 2)); + XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1, + sp->body_label, strlen(sp->body_label)); + y1 += sp->body_font->descent; + + y1 += sp->body_font->ascent; + sw = string_width (sp->body_font, sp->body2_label); + x2 = (x1 + ((x3 - x1 - sw) / 2)); + XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1, + sp->body2_label, strlen(sp->body2_label)); + y1 += sp->body_font->descent; + + /* The buttons + */ + XSetForeground (si->dpy, gc1, sp->button_foreground); + XSetForeground (si->dpy, gc2, sp->button_background); + +/* y1 += (vspacing * 2);*/ + y1 = sp->height - sp->internal_border - sp->button_height; + + x1 += sp->internal_border; + y2 = (y1 + ((sp->button_height - + (sp->button_font->ascent + sp->button_font->descent)) + / 2) + + sp->button_font->ascent); + hspacing = ((sp->width - x1 - (sp->shadow_width * 2) - + sp->internal_border - (sp->button_width * nbuttons)) + / 2); + + x2 = x1 + ((sp->button_width - string_width(sp->button_font, sp->demo_label)) + / 2); + XFillRectangle (si->dpy, si->splash_dialog, gc2, x1, y1, + sp->button_width, sp->button_height); + XDrawString (si->dpy, si->splash_dialog, gc1, x2, y2, + sp->demo_label, strlen(sp->demo_label)); + sp->demo_button_x = x1; + sp->demo_button_y = y1; + +#ifdef PREFS_BUTTON + x1 += hspacing + sp->button_width; + x2 = x1 + ((sp->button_width - string_width(sp->button_font,sp->prefs_label)) + / 2); + XFillRectangle (si->dpy, si->splash_dialog, gc2, x1, y1, + sp->button_width, sp->button_height); + XDrawString (si->dpy, si->splash_dialog, gc1, x2, y2, + sp->prefs_label, strlen(sp->prefs_label)); + sp->prefs_button_x = x1; + sp->prefs_button_y = y1; +#endif /* PREFS_BUTTON */ + +#ifdef PREFS_BUTTON + x1 += hspacing + sp->button_width; +#else /* !PREFS_BUTTON */ + x1 = (sp->width - sp->button_width - + sp->internal_border - (sp->shadow_width * 2)); +#endif /* !PREFS_BUTTON */ + + x2 = x1 + ((sp->button_width - string_width(sp->button_font,sp->help_label)) + / 2); + XFillRectangle (si->dpy, si->splash_dialog, gc2, x1, y1, + sp->button_width, sp->button_height); + XDrawString (si->dpy, si->splash_dialog, gc1, x2, y2, + sp->help_label, strlen(sp->help_label)); + sp->help_button_x = x1; + sp->help_button_y = y1; + + + /* The logo + */ + x1 = sp->shadow_width * 6; + y1 = sp->shadow_width * 6; + x2 = sp->logo_width - (sp->shadow_width * 12); + y2 = sp->logo_height - (sp->shadow_width * 12); + + if (sp->logo_pixmap) + { + Window root; + int x, y; + unsigned int w, h, bw, d; + XGetGeometry (si->dpy, sp->logo_pixmap, &root, &x, &y, &w, &h, &bw, &d); + XSetForeground (si->dpy, gc1, sp->foreground); + XSetBackground (si->dpy, gc1, sp->background); + XSetClipMask (si->dpy, gc1, sp->logo_clipmask); + XSetClipOrigin (si->dpy, gc1, x1 + ((x2 - (int)w) /2), y1 + ((y2 - (int)h) / 2)); + if (d == 1) + XCopyPlane (si->dpy, sp->logo_pixmap, si->splash_dialog, gc1, + 0, 0, w, h, + x1 + ((x2 - (int)w) / 2), + y1 + ((y2 - (int)h) / 2), + 1); + else + XCopyArea (si->dpy, sp->logo_pixmap, si->splash_dialog, gc1, + 0, 0, w, h, + x1 + ((x2 - (int)w) / 2), + y1 + ((y2 - (int)h) / 2)); + } + + /* Solid border inside the logo box. */ +#if 0 + XSetForeground (si->dpy, gc1, sp->foreground); + XDrawRectangle (si->dpy, si->splash_dialog, gc1, x1, y1, x2-1, y2-1); +#endif + + /* The shadow around the logo + */ + draw_shaded_rectangle (si->dpy, si->splash_dialog, + sp->shadow_width * 4, + sp->shadow_width * 4, + sp->logo_width - (sp->shadow_width * 8), + sp->logo_height - (sp->shadow_width * 8), + sp->shadow_width, + sp->shadow_bottom, sp->shadow_top); + + /* The shadow around the whole window + */ + draw_shaded_rectangle (si->dpy, si->splash_dialog, + 0, 0, sp->width, sp->height, sp->shadow_width, + sp->shadow_top, sp->shadow_bottom); + + XFreeGC (si->dpy, gc1); + XFreeGC (si->dpy, gc2); + + update_splash_window (si); +} + + +static void +update_splash_window (saver_info *si) +{ + splash_dialog_data *sp = si->sp_data; + int pressed; + if (!sp) return; + pressed = sp->pressed; + + /* The shadows around the buttons + */ + draw_shaded_rectangle (si->dpy, si->splash_dialog, + sp->demo_button_x, sp->demo_button_y, + sp->button_width, sp->button_height, sp->shadow_width, + (pressed == 1 ? sp->shadow_bottom : sp->shadow_top), + (pressed == 1 ? sp->shadow_top : sp->shadow_bottom)); +#ifdef PREFS_BUTTON + draw_shaded_rectangle (si->dpy, si->splash_dialog, + sp->prefs_button_x, sp->prefs_button_y, + sp->button_width, sp->button_height, sp->shadow_width, + (pressed == 2 ? sp->shadow_bottom : sp->shadow_top), + (pressed == 2 ? sp->shadow_top : sp->shadow_bottom)); +#endif /* PREFS_BUTTON */ + draw_shaded_rectangle (si->dpy, si->splash_dialog, + sp->help_button_x, sp->help_button_y, + sp->button_width, sp->button_height, sp->shadow_width, + (pressed == 3 ? sp->shadow_bottom : sp->shadow_top), + (pressed == 3 ? sp->shadow_top : sp->shadow_bottom)); +} + +static void +destroy_splash_window (saver_info *si) +{ + splash_dialog_data *sp = si->sp_data; + saver_screen_info *ssi = sp->prompt_screen; + Colormap cmap = DefaultColormapOfScreen (ssi->screen); + Pixel black = BlackPixelOfScreen (ssi->screen); + Pixel white = WhitePixelOfScreen (ssi->screen); + + if (sp->timer) + XtRemoveTimeOut (sp->timer); + + if (si->splash_dialog) + { + XDestroyWindow (si->dpy, si->splash_dialog); + si->splash_dialog = 0; + } + + if (sp->heading_label) free (sp->heading_label); + if (sp->body_label) free (sp->body_label); + if (sp->body2_label) free (sp->body2_label); + if (sp->demo_label) free (sp->demo_label); +#ifdef PREFS_BUTTON + if (sp->prefs_label) free (sp->prefs_label); +#endif /* PREFS_BUTTON */ + if (sp->help_label) free (sp->help_label); + + if (sp->heading_font) XFreeFont (si->dpy, sp->heading_font); + if (sp->body_font) XFreeFont (si->dpy, sp->body_font); + if (sp->button_font) XFreeFont (si->dpy, sp->button_font); + + if (sp->foreground != black && sp->foreground != white) + XFreeColors (si->dpy, cmap, &sp->foreground, 1, 0L); + if (sp->background != black && sp->background != white) + XFreeColors (si->dpy, cmap, &sp->background, 1, 0L); + if (sp->button_foreground != black && sp->button_foreground != white) + XFreeColors (si->dpy, cmap, &sp->button_foreground, 1, 0L); + if (sp->button_background != black && sp->button_background != white) + XFreeColors (si->dpy, cmap, &sp->button_background, 1, 0L); + if (sp->shadow_top != black && sp->shadow_top != white) + XFreeColors (si->dpy, cmap, &sp->shadow_top, 1, 0L); + if (sp->shadow_bottom != black && sp->shadow_bottom != white) + XFreeColors (si->dpy, cmap, &sp->shadow_bottom, 1, 0L); + + if (sp->logo_pixmap) + XFreePixmap (si->dpy, sp->logo_pixmap); + if (sp->logo_clipmask) + XFreePixmap (si->dpy, sp->logo_clipmask); + if (sp->logo_pixels) + { + if (sp->logo_npixels) + XFreeColors (si->dpy, cmap, sp->logo_pixels, sp->logo_npixels, 0L); + free (sp->logo_pixels); + sp->logo_pixels = 0; + sp->logo_npixels = 0; + } + + memset (sp, 0, sizeof(*sp)); + free (sp); + si->sp_data = 0; +} + +void +handle_splash_event (saver_info *si, XEvent *event) +{ + splash_dialog_data *sp = si->sp_data; + saver_screen_info *ssi = sp->prompt_screen; + int which = 0; + if (!sp) return; + + switch (event->xany.type) + { + case Expose: + draw_splash_window (si); + break; + + case ButtonPress: case ButtonRelease: + + if (event->xbutton.x >= sp->demo_button_x && + event->xbutton.x < sp->demo_button_x + sp->button_width && + event->xbutton.y >= sp->demo_button_y && + event->xbutton.y < sp->demo_button_y + sp->button_height) + which = 1; + +#ifdef PREFS_BUTTON + else if (event->xbutton.x >= sp->prefs_button_x && + event->xbutton.x < sp->prefs_button_x + sp->button_width && + event->xbutton.y >= sp->prefs_button_y && + event->xbutton.y < sp->prefs_button_y + sp->button_height) + which = 2; +#endif /* PREFS_BUTTON */ + + else if (event->xbutton.x >= sp->help_button_x && + event->xbutton.x < sp->help_button_x + sp->button_width && + event->xbutton.y >= sp->help_button_y && + event->xbutton.y < sp->help_button_y + sp->button_height) + which = 3; + + if (event->xany.type == ButtonPress) + { + sp->pressed = which; + update_splash_window (si); + if (which == 0) + XBell (si->dpy, False); + } + else if (event->xany.type == ButtonRelease) + { + if (which && sp->pressed == which) + { + destroy_splash_window (si); + sp = si->sp_data; + switch (which) + { + case 1: do_demo (ssi); break; +#ifdef PREFS_BUTTON + case 2: do_prefs (ssi); break; +#endif /* PREFS_BUTTON */ + case 3: do_help (ssi); break; + default: abort(); + } + } + else if (which == 0 && sp->pressed == 0) + { + /* click and release on the window but not in a button: + treat that as "dismiss the splash dialog." */ + destroy_splash_window (si); + sp = si->sp_data; + } + if (sp) sp->pressed = 0; + update_splash_window (si); + } + break; + + default: + break; + } +} + +static void +unsplash_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + if (si && si->sp_data) + destroy_splash_window (si); +} + + +/* Button callbacks */ + +#ifdef VMS +# define pid_t int +# define fork vfork +#endif /* VMS */ + + +static void +do_demo (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + const char *cmd = p->demo_command; + + if (cmd && *cmd) + fork_and_exec (ssi, cmd); + else + fprintf (stderr, "%s: no demo-mode command has been specified.\n", + blurb()); +} + +#ifdef PREFS_BUTTON +static void +do_prefs (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + const char *cmd = p->prefs_command; + + if (command && *command) + fork_and_exec (ssi, cmd); + else + fprintf (stderr, "%s: no preferences command has been specified.\n", + blurb()); +} +#endif /* PREFS_BUTTON */ + +static void +do_help (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + char *help_command = 0; + + if (!p->load_url_command || !*p->load_url_command) + { + fprintf (stderr, "%s: no URL command has been specified.\n", blurb()); + return; + } + if (!p->help_url || !*p->help_url) + { + fprintf (stderr, "%s: no Help URL has been specified.\n", blurb()); + return; + } + + help_command = (char *) malloc (strlen (p->load_url_command) + + (strlen (p->help_url) * 4) + 10); + sprintf (help_command, p->load_url_command, + p->help_url, p->help_url, p->help_url, p->help_url); + + fork_and_exec (ssi, help_command); + free (help_command); +} diff --git a/driver/stderr.c b/driver/stderr.c new file mode 100644 index 00000000..68e75213 --- /dev/null +++ b/driver/stderr.c @@ -0,0 +1,548 @@ +/* stderr.c --- capturing stdout/stderr output onto the screensaver window. + * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* stderr hackery - Why Unix Sucks, reason number 32767. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_FCNTL +# include +#endif + +#include + +#include "xscreensaver.h" +#include "resources.h" +#include "visual.h" + +FILE *real_stderr = 0; +FILE *real_stdout = 0; + + +/* It's ok for these to be global, since they refer to the one and only + stderr stream, not to a particular screen or window or visual. + */ +static char stderr_buffer [4096]; +static char *stderr_tail = 0; +static time_t stderr_last_read = 0; + +static int stderr_stdout_read_fd = -1; + +static void make_stderr_overlay_window (saver_screen_info *); + + +/* Recreates the stderr window or GCs: do this when the xscreensaver window + on a screen has been re-created. + */ +void +reset_stderr (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + + if (si->prefs.debug_p) + fprintf ((real_stderr ? real_stderr : stderr), + "%s: resetting stderr\n", blurb()); + + ssi->stderr_text_x = 0; + ssi->stderr_text_y = 0; + + if (ssi->stderr_gc) + XFreeGC (si->dpy, ssi->stderr_gc); + ssi->stderr_gc = 0; + + if (ssi->stderr_overlay_window) + XDestroyWindow(si->dpy, ssi->stderr_overlay_window); + ssi->stderr_overlay_window = 0; + + if (ssi->stderr_cmap) + XFreeColormap(si->dpy, ssi->stderr_cmap); + ssi->stderr_cmap = 0; +} + +/* Erases any stderr text overlaying the screen (if possible) and resets + the stderr output cursor to the upper left. Do this when the xscreensaver + window is cleared. + */ +void +clear_stderr (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + ssi->stderr_text_x = 0; + ssi->stderr_text_y = 0; + if (ssi->stderr_overlay_window) + XClearWindow (si->dpy, ssi->stderr_overlay_window); +} + + +/* Draws the string on the screen's window. + */ +static void +print_stderr_1 (saver_screen_info *ssi, char *string) +{ + saver_info *si = ssi->global; + Display *dpy = si->dpy; + Screen *screen = ssi->screen; + Window window = (ssi->stderr_overlay_window ? + ssi->stderr_overlay_window : + ssi->screensaver_window); + int h_border = 20; + int v_border = 20; + char *head = string; + char *tail; + + if (! ssi->stderr_font) + { + char *font_name = get_string_resource (dpy, "font", "Font"); + if (!font_name) font_name = strdup ("fixed"); + ssi->stderr_font = XLoadQueryFont (dpy, font_name); + if (! ssi->stderr_font) ssi->stderr_font = XLoadQueryFont (dpy, "fixed"); + ssi->stderr_line_height = (ssi->stderr_font->ascent + + ssi->stderr_font->descent); + free (font_name); + } + + if (! ssi->stderr_gc) + { + XGCValues gcv; + Pixel fg, bg; + Colormap cmap = ssi->cmap; + + if (!ssi->stderr_overlay_window && + get_boolean_resource(dpy, "overlayStderr", "Boolean")) + { + make_stderr_overlay_window (ssi); + if (ssi->stderr_overlay_window) + window = ssi->stderr_overlay_window; + if (ssi->stderr_cmap) + cmap = ssi->stderr_cmap; + } + + fg = get_pixel_resource (dpy,cmap,"overlayTextForeground","Foreground"); + bg = get_pixel_resource (dpy,cmap,"overlayTextBackground","Background"); + gcv.font = ssi->stderr_font->fid; + gcv.foreground = fg; + gcv.background = bg; + ssi->stderr_gc = XCreateGC (dpy, window, + (GCFont | GCForeground | GCBackground), + &gcv); + } + + + if (ssi->stderr_cmap) + XInstallColormap(si->dpy, ssi->stderr_cmap); + + for (tail = string; *tail; tail++) + { + if (*tail == '\n' || *tail == '\r') + { + int maxy = HeightOfScreen (screen) - v_border - v_border; + if (tail != head) + XDrawImageString (dpy, window, ssi->stderr_gc, + ssi->stderr_text_x + h_border, + ssi->stderr_text_y + v_border + + ssi->stderr_font->ascent, + head, tail - head); + ssi->stderr_text_x = 0; + ssi->stderr_text_y += ssi->stderr_line_height; + head = tail + 1; + if (*tail == '\r' && *head == '\n') + head++, tail++; + + if (ssi->stderr_text_y > maxy - ssi->stderr_line_height) + { +#if 0 + ssi->stderr_text_y = 0; +#else + int offset = ssi->stderr_line_height * 5; + XWindowAttributes xgwa; + XGetWindowAttributes (dpy, window, &xgwa); + + XCopyArea (dpy, window, window, ssi->stderr_gc, + 0, v_border + offset, + xgwa.width, + (xgwa.height - v_border - v_border - offset), + 0, v_border); + XClearArea (dpy, window, + 0, xgwa.height - v_border - offset, + xgwa.width, offset, False); + ssi->stderr_text_y -= offset; +#endif + } + } + } + if (tail != head) + { + int direction, ascent, descent; + XCharStruct overall; + XDrawImageString (dpy, window, ssi->stderr_gc, + ssi->stderr_text_x + h_border, + ssi->stderr_text_y + v_border + + ssi->stderr_font->ascent, + head, tail - head); + XTextExtents (ssi->stderr_font, tail, tail - head, + &direction, &ascent, &descent, &overall); + ssi->stderr_text_x += overall.width; + } +} + +static void +make_stderr_overlay_window (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + unsigned long transparent_pixel = 0; + Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel); + if (visual) + { + int depth = visual_depth (ssi->screen, visual); + XSetWindowAttributes attrs; + XWindowAttributes xgwa; + unsigned long attrmask; + XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa); + + if (si->prefs.debug_p) + fprintf(real_stderr, + "%s: using overlay visual 0x%0x for stderr text layer.\n", + blurb(), (int) XVisualIDFromVisual (visual)); + + ssi->stderr_cmap = XCreateColormap(si->dpy, + RootWindowOfScreen(ssi->screen), + visual, AllocNone); + + attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel | + CWBackingStore | CWSaveUnder); + attrs.colormap = ssi->stderr_cmap; + attrs.background_pixel = transparent_pixel; + attrs.backing_pixel = transparent_pixel; + attrs.border_pixel = transparent_pixel; + attrs.backing_store = NotUseful; + attrs.save_under = False; + + ssi->stderr_overlay_window = + XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0, + xgwa.width, xgwa.height, + 0, depth, InputOutput, visual, attrmask, &attrs); + XMapRaised(si->dpy, ssi->stderr_overlay_window); + } +} + + +/* Draws the string on each screen's window as error text. + */ +static void +print_stderr (saver_info *si, char *string) +{ + saver_preferences *p = &si->prefs; + int i; + + /* In verbose mode, copy it to stderr as well. */ + if (p->verbose_p) + fprintf (real_stderr, "%s", string); + + for (i = 0; i < si->nscreens; i++) + print_stderr_1 (&si->screens[i], string); +} + + +/* Polls the stderr buffer every few seconds and if it finds any text, + writes it on all screens. + */ +static void +stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + char *s = stderr_buffer; + if (*s) + { + /* If too much data was printed, then something has gone haywire, + so truncate it. */ + char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n"; + int max = sizeof (stderr_buffer) - strlen (trailer) - 5; + if (strlen (s) > max) + strcpy (s + max, trailer); + /* Now show the user. */ + print_stderr (si, s); + } + + stderr_tail = stderr_buffer; + si->stderr_popup_timer = 0; +} + + +/* Called when data becomes available on the stderr pipe. Copies it into + stderr_buffer where stderr_popup_timer_fn() can find it later. + */ +static void +stderr_callback (XtPointer closure, int *fd, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + char *s; + int left; + int size; + int read_this_time = 0; + + if (!fd || *fd < 0 || *fd != stderr_stdout_read_fd) + abort(); + + if (stderr_tail == 0) + stderr_tail = stderr_buffer; + + left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer)); + + s = stderr_tail; + *s = 0; + + /* Read as much data from the fd as we can, up to our buffer size. */ + if (left > 0) + { + while ((size = read (*fd, (void *) s, left)) > 0) + { + left -= size; + s += size; + read_this_time += size; + } + *s = 0; + } + else + { + char buf2 [1024]; + /* The buffer is full; flush the rest of it. */ + while (read (*fd, (void *) buf2, sizeof (buf2)) > 0) + ; + } + + stderr_tail = s; + stderr_last_read = time ((time_t *) 0); + + /* Now we have read some data that we would like to put up in a dialog + box. But more data may still be coming in - so don't pop up the + dialog right now, but instead, start a timer that will pop it up + a second from now. Should more data come in in the meantime, we + will be called again, and will reset that timer again. So the + dialog will only pop up when a second has elapsed with no new data + being written to stderr. + + However, if the buffer is full (meaning lots of data has been written) + then we don't reset the timer. + */ + if (read_this_time > 0) + { + if (si->stderr_popup_timer) + XtRemoveTimeOut (si->stderr_popup_timer); + + si->stderr_popup_timer = + XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn, + (XtPointer) si); + } +} + +/* If stderr capturing is desired, this replaces `stdout' and `stderr' + with a pipe, so that any output written to them will show up on the + screen as well as on the original value of those streams. + */ +void +initialize_stderr (saver_info *si) +{ + static Boolean done = False; + int fds [2]; + int in, out; + int new_stdout, new_stderr; + int stdout_fd = 1; + int stderr_fd = 2; + int flags = 0; + Boolean stderr_dialog_p; + + if (done) return; + done = True; + + real_stderr = stderr; + real_stdout = stdout; + + stderr_dialog_p = get_boolean_resource (si->dpy, "captureStderr", "Boolean"); + + if (!stderr_dialog_p) + return; + + if (pipe (fds)) + { + perror ("error creating pipe:"); + return; + } + + in = fds [0]; + out = fds [1]; + +# ifdef HAVE_FCNTL + +# if defined(O_NONBLOCK) + flags = O_NONBLOCK; +# elif defined(O_NDELAY) + flags = O_NDELAY; +# else + ERROR!! neither O_NONBLOCK nor O_NDELAY are defined. +# endif + + /* Set both sides of the pipe to nonblocking - this is so that + our reads (in stderr_callback) will terminate, and so that + out writes (in the client programs) will silently fail when + the pipe is full, instead of hosing the program. */ + if (fcntl (in, F_SETFL, flags) != 0) + { + perror ("fcntl:"); + return; + } + if (fcntl (out, F_SETFL, flags) != 0) + { + perror ("fcntl:"); + return; + } + +# endif /* !HAVE_FCNTL */ + + if (stderr_dialog_p) + { + FILE *new_stderr_file; + FILE *new_stdout_file; + + new_stderr = dup (stderr_fd); + if (new_stderr < 0) + { + perror ("could not dup() a stderr:"); + return; + } + if (! (new_stderr_file = fdopen (new_stderr, "w"))) + { + perror ("could not fdopen() the new stderr:"); + return; + } + real_stderr = new_stderr_file; + + close (stderr_fd); + if (dup2 (out, stderr_fd) < 0) + { + perror ("could not dup() a new stderr:"); + return; + } + + + new_stdout = dup (stdout_fd); + if (new_stdout < 0) + { + perror ("could not dup() a stdout:"); + return; + } + if (! (new_stdout_file = fdopen (new_stdout, "w"))) + { + perror ("could not fdopen() the new stdout:"); + return; + } + real_stdout = new_stdout_file; + + close (stdout_fd); + if (dup2 (out, stdout_fd) < 0) + { + perror ("could not dup() a new stdout:"); + return; + } + } + + stderr_stdout_read_fd = in; + XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback, + (XtPointer) si); +} + + +/* If the "-log file" command-line option has been specified, + open the file for append, and redirect stdout/stderr there. + This is called very early, before initialize_stderr(). + */ +void +stderr_log_file (saver_info *si) +{ + int stdout_fd = 1; + int stderr_fd = 2; + const char *filename = get_string_resource (si->dpy, "logFile", "LogFile"); + int fd; + + if (!filename || !*filename) return; + + fd = open (filename, O_WRONLY | O_APPEND | O_CREAT, 0666); + + if (fd < 0) + { + char buf[255]; + FAIL: + sprintf (buf, "%.100s: %.100s", blurb(), filename); + perror (buf); + fflush (stderr); + fflush (stdout); + exit (1); + } + + fprintf (stderr, "%s: logging to file %s\n", blurb(), filename); + + if (dup2 (fd, stdout_fd) < 0) goto FAIL; + if (dup2 (fd, stderr_fd) < 0) goto FAIL; + + fprintf (stderr, "\n\n" + "##########################################################################\n" + "%s: logging to \"%s\" at %s\n" + "##########################################################################\n" + "\n", + blurb(), filename, timestring()); +} + + +/* If there is anything in the stderr buffer, flush it to the real stderr. + This does no X operations. Call this when exiting to make sure any + last words actually show up. + */ +void +shutdown_stderr (saver_info *si) +{ + fflush (stdout); + fflush (stderr); + + if (!real_stderr || stderr_stdout_read_fd < 0) + return; + + stderr_callback ((XtPointer) si, &stderr_stdout_read_fd, 0); + + if (stderr_tail && + stderr_buffer < stderr_tail) + { + *stderr_tail = 0; + fprintf (real_stderr, "%s", stderr_buffer); + stderr_tail = stderr_buffer; + } + + if (real_stdout) fflush (real_stdout); + if (real_stderr) fflush (real_stderr); + + if (stdout != real_stdout) + dup2 (fileno(real_stdout), fileno(stdout)); + if (stderr != real_stderr) + dup2 (fileno(real_stderr), fileno(stderr)); + + stderr_stdout_read_fd = -1; +} diff --git a/driver/subprocs.c b/driver/subprocs.c new file mode 100644 index 00000000..405a382b --- /dev/null +++ b/driver/subprocs.c @@ -0,0 +1,1344 @@ +/* subprocs.c --- choosing, spawning, and killing screenhacks. + * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include /* not used for much... */ + +#ifndef ESRCH +# include +#endif + +#include /* sys/resource.h needs this for timeval */ +#include /* for PATH_MAX */ + +#ifdef HAVE_SYS_WAIT_H +# include /* for waitpid() and associated macros */ +#endif + +#ifdef HAVE_SETRLIMIT +# include /* for setrlimit() and RLIMIT_AS */ +#endif + +#ifdef VMS +# include +# include /* for close */ +# include /* for getpid */ +# define pid_t int +# define fork vfork +#endif /* VMS */ + +#include /* for the signal names */ + +#if !defined(SIGCHLD) && defined(SIGCLD) +# define SIGCHLD SIGCLD +#endif + +#if 0 /* putenv() is declared in stdlib.h on modern linux systems. */ +#ifdef HAVE_PUTENV +extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */ +#endif +#endif + +extern int kill (pid_t, int); /* signal() is in sys/signal.h... */ + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" +#include "exec.h" +#include "yarandom.h" +#include "visual.h" /* for id_to_visual() */ + +extern saver_info *global_si_kludge; /* I hate C so much... */ + + +/* Used when printing error/debugging messages from signal handlers. + */ +static const char * +no_malloc_number_to_string (long num) +{ + static char string[128] = ""; + int num_digits; + Bool negative_p = False; + + num_digits = 0; + + if (num == 0) + return "0"; + + if (num < 0) + { + negative_p = True; + num = -num; + } + + while ((num > 0) && (num_digits < sizeof(string) - 1)) + { + int digit; + digit = (int) num % 10; + num_digits++; + string[sizeof(string) - 1 - num_digits] = digit + '0'; + num /= 10; + } + + if (negative_p) + { + num_digits++; + string[sizeof(string) - 1 - num_digits] = '-'; + } + + return string + sizeof(string) - 1 - num_digits; +} + +/* Like write(), but runs strlen() on the arg to get the length. */ +static int +write_string (int fd, const char *str) +{ + return write (fd, str, strlen (str)); +} + +static int +write_long (int fd, long n) +{ + const char *str = no_malloc_number_to_string (n); + return write_string (fd, str); +} + + +/* RLIMIT_AS (called RLIMIT_VMEM on some systems) controls the maximum size + of a process's address space, i.e., the maximal brk(2) and mmap(2) values. + Setting this lets you put a cap on how much memory a process can allocate. + + Except the "and mmap()" part kinda makes this useless, since many GL + implementations end up using mmap() to pull the whole frame buffer into + memory (or something along those lines) making it appear processes are + using hundreds of megabytes when in fact they're using very little, and + we end up capping their mallocs prematurely. YAY! + */ +#if defined(RLIMIT_VMEM) && !defined(RLIMIT_AS) +# define RLIMIT_AS RLIMIT_VMEM +#endif + +static void +limit_subproc_memory (int address_space_limit, Bool verbose_p) +{ + +/* This has caused way more problems than it has solved... + Let's just completely ignore the "memoryLimit" option now. + */ +#undef HAVE_SETRLIMIT + +#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_AS) + struct rlimit r; + + if (address_space_limit < 10 * 1024) /* let's not be crazy */ + return; + + if (getrlimit (RLIMIT_AS, &r) != 0) + { + char buf [512]; + sprintf (buf, "%s: getrlimit(RLIMIT_AS) failed", blurb()); + perror (buf); + return; + } + + r.rlim_cur = address_space_limit; + + if (setrlimit (RLIMIT_AS, &r) != 0) + { + char buf [512]; + sprintf (buf, "%s: setrlimit(RLIMIT_AS, {%lu, %lu}) failed", + blurb(), r.rlim_cur, r.rlim_max); + perror (buf); + return; + } + + if (verbose_p) + { + int i = address_space_limit; + char buf[100]; + if (i >= (1<<30) && i == ((i >> 30) << 30)) + sprintf(buf, "%dG", i >> 30); + else if (i >= (1<<20) && i == ((i >> 20) << 20)) + sprintf(buf, "%dM", i >> 20); + else if (i >= (1<<10) && i == ((i >> 10) << 10)) + sprintf(buf, "%dK", i >> 10); + else + sprintf(buf, "%d bytes", i); + + fprintf (stderr, "%s: limited pid %lu address space to %s.\n", + blurb(), (unsigned long) getpid (), buf); + } + +#endif /* HAVE_SETRLIMIT && RLIMIT_AS */ +} + + +/* Management of child processes, and de-zombification. + */ + +enum job_status { + job_running, /* the process is still alive */ + job_stopped, /* we have sent it a STOP signal */ + job_killed, /* we have sent it a TERM signal */ + job_dead /* we have wait()ed for it, and it's dead -- this state only + occurs so that we can avoid calling free() from a signal + handler. Shortly after going into this state, the list + element will be removed. */ +}; + +struct screenhack_job { + char *name; + pid_t pid; + int screen; + enum job_status status; + struct screenhack_job *next; +}; + +static struct screenhack_job *jobs = 0; + +/* for debugging -- nothing calls this, but it's useful to invoke from gdb. + */ +void show_job_list (void); + +void +show_job_list (void) +{ + struct screenhack_job *job; + fprintf(stderr, "%s: job list:\n", blurb()); + for (job = jobs; job; job = job->next) + fprintf (stderr, " %5ld: %2d: (%s) %s\n", + (long) job->pid, + job->screen, + (job->status == job_running ? "running" : + job->status == job_stopped ? "stopped" : + job->status == job_killed ? " killed" : + job->status == job_dead ? " dead" : " ???"), + job->name); + fprintf (stderr, "\n"); +} + + +static void clean_job_list (void); + +static struct screenhack_job * +make_job (pid_t pid, int screen, const char *cmd) +{ + struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job)); + + static char name [1024]; + const char *in = cmd; + char *out = name; + int got_eq = 0; + int first = 1; + + clean_job_list(); + + AGAIN: + while (isspace(*in)) in++; /* skip whitespace */ + while (!isspace(*in) && *in != ':') { + if (*in == '=') got_eq = 1; + *out++ = *in++; /* snarf first token */ + } + + if (got_eq) /* if the first token was FOO=bar */ + { /* then get the next token instead. */ + got_eq = 0; + out = name; + first = 0; + goto AGAIN; + } + + while (isspace(*in)) in++; /* skip whitespace */ + *out = 0; + + job->name = strdup(name); + job->pid = pid; + job->screen = screen; + job->status = job_running; + job->next = jobs; + jobs = job; + + return jobs; +} + + +static void +free_job (struct screenhack_job *job) +{ + if (!job) + return; + else if (job == jobs) + jobs = jobs->next; + else + { + struct screenhack_job *job2, *prev; + for (prev = 0, job2 = jobs; + job2; + prev = job2, job2 = job2->next) + if (job2 == job) + { + prev->next = job->next; + break; + } + } + free(job->name); + free(job); +} + + +/* Cleans out dead jobs from the jobs list -- this must only be called + from the main thread, not from a signal handler. + */ +static void +clean_job_list (void) +{ + struct screenhack_job *job, *prev, *next; + for (prev = 0, job = jobs, next = (job ? job->next : 0); + job; + prev = job, job = next, next = (job ? job->next : 0)) + { + if (job->status == job_dead) + { + if (prev) + prev->next = next; + free_job (job); + job = prev; + } + } +} + + +static struct screenhack_job * +find_job (pid_t pid) +{ + struct screenhack_job *job; + for (job = jobs; job; job = job->next) + if (job->pid == pid) + return job; + return 0; +} + +static void await_dying_children (saver_info *si); +#ifndef VMS +static void describe_dead_child (saver_info *, pid_t, int wait_status); +#endif + + +/* Semaphore to temporarily turn the SIGCHLD handler into a no-op. + Don't alter this directly -- use block_sigchld() / unblock_sigchld(). + */ +static int block_sigchld_handler = 0; + + +#ifdef HAVE_SIGACTION + sigset_t +#else /* !HAVE_SIGACTION */ + int +#endif /* !HAVE_SIGACTION */ +block_sigchld (void) +{ +#ifdef HAVE_SIGACTION + struct sigaction sa; + sigset_t child_set; + + memset (&sa, 0, sizeof (sa)); + sa.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &sa, NULL); + + sigemptyset (&child_set); + sigaddset (&child_set, SIGCHLD); + sigprocmask (SIG_BLOCK, &child_set, 0); + +#else /* !HAVE_SIGACTION */ + signal (SIGPIPE, SIG_IGN); +#endif /* !HAVE_SIGACTION */ + + block_sigchld_handler++; + +#ifdef HAVE_SIGACTION + return child_set; +#else /* !HAVE_SIGACTION */ + return 0; +#endif /* !HAVE_SIGACTION */ +} + +void +unblock_sigchld (void) +{ +#ifdef HAVE_SIGACTION + struct sigaction sa; + sigset_t child_set; + + memset(&sa, 0, sizeof (sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGPIPE, &sa, NULL); + + sigemptyset(&child_set); + sigaddset(&child_set, SIGCHLD); + sigprocmask(SIG_UNBLOCK, &child_set, 0); + +#else /* !HAVE_SIGACTION */ + signal(SIGPIPE, SIG_DFL); +#endif /* !HAVE_SIGACTION */ + + block_sigchld_handler--; +} + +static int +kill_job (saver_info *si, pid_t pid, int signal) +{ + saver_preferences *p = &si->prefs; + struct screenhack_job *job; + int status = -1; + + clean_job_list(); + + if (block_sigchld_handler) + /* This function should not be called from the signal handler. */ + abort(); + + block_sigchld(); /* we control the horizontal... */ + + job = find_job (pid); + if (!job || + !job->pid || + job->status == job_killed) + { + if (p->verbose_p) + fprintf (stderr, "%s: no child %ld to signal!\n", + blurb(), (long) pid); + goto DONE; + } + + switch (signal) { + case SIGTERM: job->status = job_killed; break; +#ifdef SIGSTOP + /* #### there must be a way to do this on VMS... */ + case SIGSTOP: job->status = job_stopped; break; + case SIGCONT: job->status = job_running; break; +#endif /* SIGSTOP */ + default: abort(); + } + + if (p->verbose_p) + fprintf (stderr, "%s: %d: %s pid %lu (%s)\n", + blurb(), job->screen, + (job->status == job_killed ? "killing" : + job->status == job_stopped ? "suspending" : "resuming"), + (unsigned long) job->pid, + job->name); + + status = kill (job->pid, signal); + + if (p->verbose_p && status < 0) + { + if (errno == ESRCH) + fprintf (stderr, + "%s: %d: child process %lu (%s) was already dead.\n", + blurb(), job->screen, (unsigned long) job->pid, job->name); + else + { + char buf [1024]; + sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)", + blurb(), job->screen, (unsigned long) job->pid, job->name); + perror (buf); + } + } + + await_dying_children (si); + + DONE: + unblock_sigchld(); + if (block_sigchld_handler < 0) + abort(); + + clean_job_list(); + return status; +} + + +#ifdef SIGCHLD +static RETSIGTYPE +sigchld_handler (int sig) +{ + saver_info *si = global_si_kludge; /* I hate C so much... */ + + if (si->prefs.debug_p) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(), + (block_sigchld_handler ? " (blocked)" : "")); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": got SIGCHLD"); + + if (block_sigchld_handler) + write_string (STDERR_FILENO, " (blocked)\n"); + else + write_string (STDERR_FILENO, "\n"); + } + + if (block_sigchld_handler < 0) + abort(); + else if (block_sigchld_handler == 0) + { + block_sigchld(); + await_dying_children (si); + unblock_sigchld(); + } + + init_sigchld(); +} +#endif /* SIGCHLD */ + + +#ifndef VMS + +static void +await_dying_children (saver_info *si) +{ + while (1) + { + int wait_status = 0; + pid_t kid; + + errno = 0; + kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED); + + if (si->prefs.debug_p) + { + if (kid < 0 && errno) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(), + (long) kid, errno); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": waitpid(-1) ==> "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_long (STDERR_FILENO, (long) errno); + write_string (STDERR_FILENO, ")\n"); + } + else + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(), + (long) kid); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": waitpid(-1) ==> "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, "\n"); + } + } + + /* 0 means no more children to reap. + -1 means error -- except "interrupted system call" isn't a "real" + error, so if we get that, we should just try again. */ + if (kid == 0 || + (kid < 0 && errno != EINTR)) + break; + + describe_dead_child (si, kid, wait_status); + } +} + + +static void +describe_dead_child (saver_info *si, pid_t kid, int wait_status) +{ + int i; + saver_preferences *p = &si->prefs; + struct screenhack_job *job = find_job (kid); + const char *name = job ? job->name : ""; + int screen_no = job ? job->screen : 0; + + if (WIFEXITED (wait_status)) + { + int exit_status = WEXITSTATUS (wait_status); + + /* Treat exit code as a signed 8-bit quantity. */ + if (exit_status & 0x80) exit_status |= ~0xFF; + + /* One might assume that exiting with non-0 means something went wrong. + But that loser xswarm exits with the code that it was killed with, so + it *always* exits abnormally. Treat abnormal exits as "normal" (don't + mention them) if we've just killed the subprocess. But mention them + if they happen on their own. + */ + if (!job || + (exit_status != 0 && + (p->verbose_p || job->status != job_killed))) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, + "%s: %d: child pid %lu (%s) exited abnormally (code %d).\n", + blurb(), screen_no, (unsigned long) kid, name, exit_status); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") exited abnormally (code "); + write_long (STDERR_FILENO, (long) exit_status); + write_string (STDERR_FILENO, ").\n"); + } + else if (p->verbose_p) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: %d: child pid %lu (%s) exited normally.\n", + blurb(), screen_no, (unsigned long) kid, name); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") exited normally.\n"); + } + + if (job) + job->status = job_dead; + } + else if (WIFSIGNALED (wait_status)) + { + if (p->verbose_p || + !job || + job->status != job_killed || + WTERMSIG (wait_status) != SIGTERM) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: %d: child pid %lu (%s) terminated with %s.\n", + blurb(), screen_no, (unsigned long) kid, name, + signal_name (WTERMSIG(wait_status))); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") terminated with signal "); + write_long (STDERR_FILENO, WTERMSIG(wait_status)); + write_string (STDERR_FILENO, ".\n"); + } + + if (job) + job->status = job_dead; + } + else if (WIFSTOPPED (wait_status)) + { + if (p->verbose_p) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n", + blurb(), (unsigned long) kid, name, + signal_name (WSTOPSIG (wait_status))); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") stopped with signal "); + write_long (STDERR_FILENO, WSTOPSIG(wait_status)); + write_string (STDERR_FILENO, ".\n"); + } + + if (job) + job->status = job_stopped; + } + else + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!", + blurb(), (unsigned long) kid, name); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") died in a mysterious way!"); + if (job) + job->status = job_dead; + } + + /* Clear out the pid so that screenhack_running_p() knows it's dead. + */ + if (!job || job->status == job_dead) + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (kid == ssi->pid) + ssi->pid = 0; + } +} + +#else /* VMS */ +static void await_dying_children (saver_info *si) { return; } +#endif /* VMS */ + + +void +init_sigchld (void) +{ +#ifdef SIGCHLD + +# ifdef HAVE_SIGACTION /* Thanks to Tom Kelly */ + + static Bool sigchld_initialized_p = 0; + if (!sigchld_initialized_p) + { + struct sigaction action, old; + + action.sa_handler = sigchld_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + + if (sigaction(SIGCHLD, &action, &old) < 0) + { + char buf [255]; + sprintf (buf, "%s: couldn't catch SIGCHLD", blurb()); + perror (buf); + } + sigchld_initialized_p = True; + } + +# else /* !HAVE_SIGACTION */ + + if (((long) signal (SIGCHLD, sigchld_handler)) == -1L) + { + char buf [255]; + sprintf (buf, "%s: couldn't catch SIGCHLD", blurb()); + perror (buf); + } +# endif /* !HAVE_SIGACTION */ +#endif /* SIGCHLD */ +} + + + + + +static Bool +select_visual_of_hack (saver_screen_info *ssi, screenhack *hack) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + Bool selected; + + if (hack->visual && *hack->visual) + selected = select_visual(ssi, hack->visual); + else + selected = select_visual(ssi, 0); + + if (!selected && (p->verbose_p || si->demoing_p)) + fprintf (stderr, + (si->demoing_p + ? "%s: warning, no \"%s\" visual for \"%s\".\n" + : "%s: no \"%s\" visual; skipping \"%s\".\n"), + blurb(), + (hack->visual && *hack->visual ? hack->visual : "???"), + hack->command); + + return selected; +} + + +static void +print_path_error (const char *program) +{ + char buf [512]; + char *cmd = strdup (program); + char *token = strchr (cmd, ' '); + + if (token) *token = 0; + sprintf (buf, "%s: could not execute \"%.100s\"", blurb(), cmd); + free (cmd); + perror (buf); + + if (errno == ENOENT && + (token = getenv("PATH"))) + { +# ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# define PATH_MAX 2048 +# endif +# endif + char path[PATH_MAX]; + fprintf (stderr, "\n"); + *path = 0; +# if defined(HAVE_GETCWD) + if (! getcwd (path, sizeof(path))) + *path = 0; +# elif defined(HAVE_GETWD) + getwd (path); +# endif + if (*path) + fprintf (stderr, " Current directory is: %s\n", path); + fprintf (stderr, " PATH is:\n"); + token = strtok (strdup(token), ":"); + while (token) + { + fprintf (stderr, " %s\n", token); + token = strtok(0, ":"); + } + fprintf (stderr, "\n"); + } +} + + +/* Executes the command in another process. + Command may be any single command acceptable to /bin/sh. + It may include wildcards, but no semicolons. + If successful, the pid of the other process is returned. + Otherwise, -1 is returned and an error may have been + printed to stderr. + */ +pid_t +fork_and_exec (saver_screen_info *ssi, const char *command) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + pid_t forked; + + switch ((int) (forked = fork ())) + { + case -1: + { + char buf [255]; + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + break; + } + + case 0: + close (ConnectionNumber (si->dpy)); /* close display fd */ + limit_subproc_memory (p->inferior_memory_limit, p->verbose_p); + hack_subproc_environment (ssi->screen, ssi->screensaver_window); + + if (p->verbose_p) + fprintf (stderr, "%s: %d: spawning \"%s\" in pid %lu.\n", + blurb(), ssi->number, command, + (unsigned long) getpid ()); + + exec_command (p->shell, command, p->nice_inferior); + + /* If that returned, we were unable to exec the subprocess. + Print an error message, if desired. + */ + if (! p->ignore_uninstalled_p) + print_path_error (command); + + exit (1); /* exits child fork */ + break; + + default: /* parent */ + (void) make_job (forked, ssi->number, command); + break; + } + + return forked; +} + + +void +spawn_screenhack (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + XFlush (si->dpy); + + if (!monitor_powered_on_p (si)) + { + if (si->prefs.verbose_p) + fprintf (stderr, + "%s: %d: X says monitor has powered down; " + "not launching a hack.\n", blurb(), ssi->number); + return; + } + + if (p->screenhacks_count) + { + screenhack *hack; + pid_t forked; + char buf [255]; + int new_hack = -1; + int retry_count = 0; + Bool force = False; + + AGAIN: + + if (p->screenhacks_count < 1) + { + /* No hacks at all */ + new_hack = -1; + } + else if (p->screenhacks_count == 1) + { + /* Exactly one hack in the list */ + new_hack = 0; + } + else if (si->selection_mode == -1) + { + /* Select the next hack, wrapping. */ + new_hack = (ssi->current_hack + 1) % p->screenhacks_count; + } + else if (si->selection_mode == -2) + { + /* Select the previous hack, wrapping. */ + if (ssi->current_hack < 0) + new_hack = p->screenhacks_count - 1; + else + new_hack = ((ssi->current_hack + p->screenhacks_count - 1) + % p->screenhacks_count); + } + else if (si->selection_mode > 0) + { + /* Select a specific hack, by number (via the ACTIVATE command.) */ + new_hack = ((si->selection_mode - 1) % p->screenhacks_count); + force = True; + } + else if (p->mode == ONE_HACK && + p->selected_hack >= 0) + { + /* Select a specific hack, by number (via "One Saver" mode.) */ + new_hack = p->selected_hack; + force = True; + } + else if (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) + { + new_hack = -1; + } + else if (p->mode == RANDOM_HACKS_SAME && + ssi->number != 0) + { + /* Use the same hack that's running on screen 0. + (Assumes this function was called on screen 0 first.) + */ + new_hack = si->screens[0].current_hack; + } + else /* (p->mode == RANDOM_HACKS) */ + { + /* Select a random hack (but not the one we just ran.) */ + while ((new_hack = random () % p->screenhacks_count) + == ssi->current_hack) + ; + } + + if (new_hack < 0) /* don't run a hack */ + { + ssi->current_hack = -1; + if (si->selection_mode < 0) + si->selection_mode = 0; + return; + } + + ssi->current_hack = new_hack; + hack = p->screenhacks[ssi->current_hack]; + + /* If the hack is disabled, or there is no visual for this hack, + then try again (move forward, or backward, or re-randomize.) + Unless this hack was specified explicitly, in which case, + use it regardless. + */ + if (force) + select_visual_of_hack (ssi, hack); + + if (!force && + (!hack->enabled_p || + !on_path_p (hack->command) || + !select_visual_of_hack (ssi, hack))) + { + if (++retry_count > (p->screenhacks_count*4)) + { + /* Uh, oops. Odds are, there are no suitable visuals, + and we're looping. Give up. (This is totally lame, + what we should do is make a list of suitable hacks at + the beginning, then only loop over them.) + */ + if (p->verbose_p) + fprintf(stderr, + "%s: %d: no programs enabled, or no suitable visuals.\n", + blurb(), ssi->number); + return; + } + else + goto AGAIN; + } + + /* Turn off "next" and "prev" modes now, but "demo" mode is only + turned off by explicit action. + */ + if (si->selection_mode < 0) + si->selection_mode = 0; + + forked = fork_and_exec (ssi, hack->command); + switch ((int) forked) + { + case -1: /* fork failed */ + case 0: /* child fork (can't happen) */ + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + restore_real_vroot (si); + saver_exit (si, 1, "couldn't fork"); + break; + + default: + ssi->pid = forked; + break; + } + } + + store_saver_status (si); /* store current hack number */ +} + + +void +kill_screenhack (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + if (ssi->pid) + kill_job (si, ssi->pid, SIGTERM); + ssi->pid = 0; +} + + +void +suspend_screenhack (saver_screen_info *ssi, Bool suspend_p) +{ +#ifdef SIGSTOP /* older VMS doesn't have it... */ + saver_info *si = ssi->global; + if (ssi->pid) + kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT)); +#endif /* SIGSTOP */ +} + + +/* Called when we're exiting abnormally, to kill off the subproc. */ +void +emergency_kill_subproc (saver_info *si) +{ + int i; +#ifdef SIGCHLD + signal (SIGCHLD, SIG_IGN); +#endif /* SIGCHLD */ + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->pid) + { + kill_job (si, ssi->pid, SIGTERM); + ssi->pid = 0; + } + } +} + +Bool +screenhack_running_p (saver_info *si) +{ + Bool any_running_p = False; + int i; + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->pid) any_running_p = True; + } + return any_running_p; +} + + +/* Environment variables. */ + + +/* Modifies $PATH in the current environment, so that if DEFAULT_PATH_PREFIX + is defined, the xscreensaver daemon will search that directory for hacks. + */ +void +hack_environment (saver_info *si) +{ +#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX) + static const char *def_path = DEFAULT_PATH_PREFIX; + if (def_path && *def_path) + { + const char *opath = getenv("PATH"); + char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20); + strcpy (npath, "PATH="); + strcat (npath, def_path); + strcat (npath, ":"); + strcat (npath, opath); + + if (putenv (npath)) + abort (); + + /* don't free (npath) -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) + do not. So we must leak it (and/or the previous setting). Yay. + */ + } +#endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */ +} + + +void +hack_subproc_environment (Screen *screen, Window saver_window) +{ + /* Store $DISPLAY into the environment, so that the $DISPLAY variable that + the spawned processes inherit is correct. First, it must be on the same + host and display as the value of -display passed in on our command line + (which is not necessarily the same as what our $DISPLAY variable is.) + Second, the screen number in the $DISPLAY passed to the subprocess should + be the screen on which this particular hack is running -- not the display + specification which the driver itself is using, since the driver ignores + its screen number and manages all existing screens. + + Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this will allow + us to (eventually) run multiple hacks in Xinerama mode, where each hack + has the same $DISPLAY but a different piece of glass. + */ + Display *dpy = DisplayOfScreen (screen); + const char *odpy = DisplayString (dpy); + char *ndpy = (char *) malloc (strlen(odpy) + 20); + char *nssw = (char *) malloc (40); + char *s, *c; + + strcpy (ndpy, "DISPLAY="); + s = ndpy + strlen(ndpy); + strcpy (s, odpy); + + /* We have to find the last colon since it is the boundary between + hostname & screen - IPv6 numeric format addresses may have many + colons before that point, and DECnet addresses always have two colons */ + c = strrchr(s,':'); /* skip to last colon */ + if (c != NULL) s = c+1; + while (isdigit(*s)) s++; /* skip over dpy number */ + while (*s == '.') s++; /* skip over dot */ + if (s[-1] != '.') *s++ = '.'; /* put on a dot */ + sprintf(s, "%d", screen_number (screen)); /* put on screen number */ + + sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_window); + + /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems + any more, right? It's not Posix, but everyone seems to have it. */ +#ifdef HAVE_PUTENV + if (putenv (ndpy)) + abort (); + if (putenv (nssw)) + abort (); + + /* don't free ndpy/nssw -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) + do not. So we must leak it (and/or the previous setting). Yay. + */ +#endif /* HAVE_PUTENV */ +} + + +/* GL crap */ + +Visual * +get_best_gl_visual (saver_info *si, Screen *screen) +{ + pid_t forked; + int fds [2]; + int in, out; + char buf[1024]; + + char *av[10]; + int ac = 0; + + av[ac++] = "xscreensaver-gl-helper"; + av[ac] = 0; + + if (pipe (fds)) + { + perror ("error creating pipe:"); + return 0; + } + + in = fds [0]; + out = fds [1]; + + block_sigchld(); /* This blocks it in the parent and child, to avoid + racing. It is never unblocked in the child before + the child exits, but that doesn't matter. + */ + + switch ((int) (forked = fork ())) + { + case -1: + { + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + saver_exit (si, 1, 0); + } + case 0: + { + int stdout_fd = 1; + + close (in); /* don't need this one */ + close (ConnectionNumber (si->dpy)); /* close display fd */ + + if (dup2 (out, stdout_fd) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stdout:"); + return 0; + } + hack_subproc_environment (screen, 0); /* set $DISPLAY */ + + execvp (av[0], av); /* shouldn't return. */ + + if (errno != ENOENT /* || si->prefs.verbose_p */ ) + { + /* Ignore "no such file or directory" errors. + Issue all other exec errors, though. */ + sprintf (buf, "%s: running %s", blurb(), av[0]); + perror (buf); + } + exit (1); /* exits fork */ + break; + } + default: + { + int result = 0; + int wait_status = 0; + + FILE *f = fdopen (in, "r"); + unsigned long v = 0; + char c; + + close (out); /* don't need this one */ + + *buf = 0; + if (! fgets (buf, sizeof(buf)-1, f)) + *buf = 0; + fclose (f); + + /* Wait for the child to die. */ + waitpid (-1, &wait_status, 0); + + unblock_sigchld(); /* child is dead and waited, unblock now. */ + + if (1 == sscanf (buf, "0x%lx %c", &v, &c)) + result = (int) v; + + if (result == 0) + { + if (si->prefs.verbose_p) + { + int L = strlen(buf); + fprintf (stderr, "%s: %s did not report a GL visual!\n", + blurb(), av[0]); + + if (L && buf[L-1] == '\n') + buf[--L] = 0; + if (*buf) + fprintf (stderr, "%s: %s said: \"%s\"\n", + blurb(), av[0], buf); + } + return 0; + } + else + { + Visual *v = id_to_visual (screen, result); + if (si->prefs.verbose_p) + fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n", + blurb(), screen_number (screen), + av[0], result, + (v == DefaultVisualOfScreen (screen) + ? " (default)" : "")); + return v; + } + } + } + + abort(); +} + + + +/* Restarting the xscreensaver process from scratch. */ + +static char **saved_argv; + +void +save_argv (int argc, char **argv) +{ + saved_argv = (char **) calloc (argc+2, sizeof (char *)); + saved_argv [argc] = 0; + while (argc--) + { + int i = strlen (argv [argc]) + 1; + saved_argv [argc] = (char *) malloc (i); + memcpy (saved_argv [argc], argv [argc], i); + } +} + + +/* Re-execs the process with the arguments in saved_argv. Does not return. + */ +void +restart_process (saver_info *si) +{ + fflush (stdout); + fflush (stderr); + shutdown_stderr (si); + if (si->prefs.verbose_p) + { + int i; + fprintf (stderr, "%s: re-executing", blurb()); + for (i = 0; saved_argv[i]; i++) + fprintf (stderr, " %s", saved_argv[i]); + fprintf (stderr, "\n"); + } + describe_uids (si, stderr); + fprintf (stderr, "\n"); + + fflush (stdout); + fflush (stderr); + execvp (saved_argv [0], saved_argv); /* shouldn't return */ + { + char buf [512]; + sprintf (buf, "%s: could not restart process", blurb()); + perror(buf); + fflush(stderr); + abort(); + } +} diff --git a/driver/test-apm.c b/driver/test-apm.c new file mode 100644 index 00000000..6b87c7e7 --- /dev/null +++ b/driver/test-apm.c @@ -0,0 +1,101 @@ +/* test-apm.c --- playing with the APM library. + * xscreensaver, Copyright (c) 1999 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include + +#include +#include + +#include + +#define countof(x) (sizeof((x))/sizeof(*(x))) + + +char *progname = 0; +char *progclass = "XScreenSaver"; + +static const char * +blurb (void) +{ + static char buf[255]; + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + +static void +apm_cb (XtPointer closure, int *fd, XtInputId *id) +{ + apm_event_t events[100]; + int n, i; + while ((n = apm_get_events (*fd, 0, events, countof(events))) + > 0) + for (i = 0; i < n; i++) + { + fprintf (stderr, "%s: APM event 0x%x: %s.\n", blurb(), + events[i], apm_event_name (events[i])); +#if 0 + switch (events[i]) + { + case APM_SYS_STANDBY: + case APM_USER_STANDBY: + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + case APM_CRITICAL_SUSPEND: + break; + } +#endif + } +} + +int +main (int argc, char **argv) +{ + XtAppContext app; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + int fd; + XtInputId id; + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + + fd = apm_open (); + if (fd <= 0) + { + fprintf (stderr, "%s: couldn't initialize APM.\n", blurb()); + exit (1); + } + + id = XtAppAddInput(app, fd, + (XtPointer) (XtInputReadMask | XtInputWriteMask), + apm_cb, 0); + XtAppMainLoop (app); + exit (0); +} diff --git a/driver/test-fade.c b/driver/test-fade.c new file mode 100644 index 00000000..9db773d0 --- /dev/null +++ b/driver/test-fade.c @@ -0,0 +1,123 @@ +/* test-fade.c --- playing with colormap and/or gamma fading. + * xscreensaver, Copyright (c) 2001, 2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include + +#include +#include "xscreensaver.h" +#include "fade.h" + +#ifdef HAVE_SGI_VC_EXTENSION +# include +#endif +#ifdef HAVE_XF86VMODE_GAMMA +# include +#endif + +XrmDatabase db = 0; +char *progname = 0; +char *progclass = "XScreenSaver"; + +#define SGI_VC_NAME "SGI-VIDEO-CONTROL" +#define XF86_VIDMODE_NAME "XFree86-VidModeExtension" + +int +main (int argc, char **argv) +{ + int seconds = 3; + int ticks = 20; + int delay = 1; + + int op, event, error, major, minor; + + XtAppContext app; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + Colormap *current_maps; + int i; + + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + db = XtDatabase (dpy); + + current_maps = (Colormap *) calloc(sizeof(Colormap), ScreenCount(dpy)); + for (i = 0; i < ScreenCount(dpy); i++) + current_maps[i] = DefaultColormap (dpy, i); + + if (!XQueryExtension (dpy, SGI_VC_NAME, &op, &event, &error)) + fprintf(stderr, "%s: no " SGI_VC_NAME " extension\n", progname); + else + { +# ifdef HAVE_SGI_VC_EXTENSION + if (!XSGIvcQueryVersion (dpy, &major, &minor)) + fprintf(stderr, "%s: unable to get " SGI_VC_NAME " version\n", + progname); + else + fprintf(stderr, "%s: " SGI_VC_NAME " version %d.%d\n", + progname, major, minor); +# else /* !HAVE_SGI_VC_EXTENSION */ + fprintf(stderr, "%s: no support for display's " SGI_VC_NAME + " extension\n", progname); +# endif /* !HAVE_SGI_VC_EXTENSION */ + } + + + if (!XQueryExtension (dpy, XF86_VIDMODE_NAME, &op, &event, &error)) + fprintf(stderr, "%s: no " XF86_VIDMODE_NAME " extension\n", progname); + else + { +# ifdef HAVE_XF86VMODE_GAMMA + if (!XF86VidModeQueryVersion (dpy, &major, &minor)) + fprintf(stderr, "%s: unable to get " XF86_VIDMODE_NAME " version\n", + progname); + else + fprintf(stderr, "%s: " XF86_VIDMODE_NAME " version %d.%d\n", + progname, major, minor); +# else /* !HAVE_XF86VMODE_GAMMA */ + fprintf(stderr, "%s: no support for display's " XF86_VIDMODE_NAME + " extension\n", progname); +# endif /* !HAVE_XF86VMODE_GAMMA */ + } + + fprintf (stderr, "%s: fading %d screen%s\n", + progname, ScreenCount(dpy), ScreenCount(dpy) == 1 ? "" : "s"); + + while (1) + { + XSync (dpy, False); + + fprintf(stderr, "%s: out...", progname); + fflush(stderr); + fade_screens (dpy, current_maps, 0, 0, seconds, ticks, True, False); + fprintf(stderr, "done.\n"); + fflush(stderr); + + if (delay) sleep (delay); + + fprintf(stderr,"%s: in...", progname); + fflush(stderr); + fade_screens (dpy, current_maps, 0, 0, seconds, ticks, False, False); + fprintf(stderr, "done.\n"); + fflush(stderr); + + if (delay) sleep (delay); + } +} diff --git a/driver/test-grab.c b/driver/test-grab.c new file mode 100644 index 00000000..03018eb5 --- /dev/null +++ b/driver/test-grab.c @@ -0,0 +1,89 @@ +/* test-uid.c --- playing with grabs. + * xscreensaver, Copyright (c) 1999, 2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include + +#include +#include + +char *progname = 0; +char *progclass = "XScreenSaver"; + +#define ALL_POINTER_EVENTS \ + (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \ + LeaveWindowMask | PointerMotionMask | PointerMotionHintMask | \ + Button1MotionMask | Button2MotionMask | Button3MotionMask | \ + Button4MotionMask | Button5MotionMask | ButtonMotionMask) + +int +main (int argc, char **argv) +{ + XtAppContext app; + int kstatus, mstatus; + Cursor cursor = 0; + int delay = 60 * 15; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + Window w = RootWindow (dpy, DefaultScreen(dpy)); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + + kstatus = XGrabKeyboard (dpy, w, True, + GrabModeSync, GrabModeAsync, + CurrentTime); + fprintf (stderr, "%s: grabbing keyboard on 0x%lx... %s.\n", + progname, (unsigned long) w, + (kstatus == GrabSuccess ? "GrabSuccess" : + kstatus == AlreadyGrabbed ? "AlreadyGrabbed" : + kstatus == GrabInvalidTime ? "GrabInvalidTime" : + kstatus == GrabNotViewable ? "GrabNotViewable" : + kstatus == GrabFrozen ? "GrabFrozen" : + "???")); + + mstatus = XGrabPointer (dpy, w, True, ALL_POINTER_EVENTS, + GrabModeAsync, GrabModeAsync, None, + cursor, CurrentTime); + fprintf (stderr, "%s: grabbing mouse on 0x%lx... %s.\n", + progname, (unsigned long) w, + (mstatus == GrabSuccess ? "GrabSuccess" : + mstatus == AlreadyGrabbed ? "AlreadyGrabbed" : + mstatus == GrabInvalidTime ? "GrabInvalidTime" : + mstatus == GrabNotViewable ? "GrabNotViewable" : + mstatus == GrabFrozen ? "GrabFrozen" : + "???")); + + XSync(dpy, False); + + if (kstatus == GrabSuccess || mstatus == GrabSuccess) + { + fprintf (stderr, "%s: sleeping for %d:%02d:%02d...\n", + progname, + delay / (60 * 60), + (delay % (60 * 60)) / 60, + delay % 60); + fflush(stderr); + sleep (delay); + XSync(dpy, False); + } + + exit (0); +} diff --git a/driver/test-mlstring.c b/driver/test-mlstring.c new file mode 100644 index 00000000..e269a004 --- /dev/null +++ b/driver/test-mlstring.c @@ -0,0 +1,312 @@ +/* + * (c) 2007, Quest Software, Inc. All rights reserved. + * + * This file is part of XScreenSaver, + * Copyright (c) 1993-2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#include +#include +#include + +#include "mlstring.c" /* hokey, but whatever */ + +#define WRAP_WIDTH_PX 100 + +#undef Bool +#undef True +#undef False +typedef int Bool; +#define True 1 +#define False 0 + +#define SKIPPED -1 +#define SUCCESS 0 +#define FAILURE 1 + +#define FAIL(msg, ...) \ + do { \ + ++failcount; \ + fprintf(stderr, "[FAIL] "); \ + fprintf(stderr, msg, __VA_ARGS__); \ + putc('\n', stderr); \ + return FAILURE; \ + } while (0) + +#define SUCCEED(testname) \ + do { \ + fprintf(stderr, "[SUCCESS] %s\n", (testname)); \ + } while (0) + +#define SKIP(testname) \ + do { \ + fprintf(stderr, "[SKIPPED] %s\n", (testname)); \ + } while (0) + +extern mlstring* mlstring_allocate(const char *msg); +extern void mlstring_wrap(mlstring *mstr, XFontStruct *font, Dimension width); + +static int failcount = 0; + +static char *mlstring_to_cstr(const mlstring *mlstr) { + char *cstr; + size_t cstrlen = 0, alloclen = 1024; + const struct mlstr_line *line; + + cstr = malloc(alloclen); + if (!cstr) + return NULL; + cstr[0] = '\0'; + + for (line = mlstr->lines; line; line = line->next_line) { + /* Extend the buffer if necessary. */ + if (cstrlen + strlen(line->line) + 1 > alloclen) { + cstr = realloc(cstr, alloclen *= 2); + if (!cstr) + return NULL; + } + + /* If this is not the first line */ + if (line != mlstr->lines) { + /* Append a newline character */ + cstr[cstrlen] = '\n'; + ++cstrlen; + cstr[cstrlen] = '\0'; + } + + strcat(cstr, line->line); + cstrlen += strlen(line->line); + } + return cstr; +} + +/* Pass -1 for expect_min or expect_exact to not check that value. + * expect_empty_p means an empty line is expected at some point in the string. + * Also ensures that the string was not too wide after wrapping. */ +static int mlstring_expect_lines(const mlstring *mlstr, int expect_min, int expect_exact, Bool expect_empty_p) +{ + int count; + Bool got_empty_line = False; + const struct mlstr_line *line = mlstr->lines; + + for (count = 0; line; line = line->next_line) { + if (line->line[0] == '\0') { + if (!expect_empty_p) + FAIL("Not expecting empty lines, but got one on line %d of [%s]", count + 1, mlstring_to_cstr(mlstr)); + got_empty_line = True; + } + ++count; + } + + if (expect_empty_p && !got_empty_line) + FAIL("Expecting an empty line, but none found in [%s]", mlstring_to_cstr(mlstr)); + + if (expect_exact != -1 && expect_exact != count) + FAIL("Expected %d lines, got %d", expect_exact, count); + + if (expect_min != -1 && count < expect_min) + FAIL("Expected at least %d lines, got %d", expect_min, count); + + return SUCCESS; +} + +static int mlstring_expect(const char *msg, int expect_lines, const mlstring *mlstr, Bool expect_empty_p) +{ + char *str, *str_top; + const struct mlstr_line *cur; + int linecount = 0; + + /* Duplicate msg so we can chop it up */ + str_top = strdup(msg); + if (!str_top) + return SKIPPED; + + /* Replace all newlines with NUL */ + str = str_top; + while ((str = strchr(str, '\n'))) + *str++ = '\0'; + + /* str is now used to point to the expected string */ + str = str_top; + + for (cur = mlstr->lines; cur; cur = cur->next_line) + { + ++linecount; + if (strcmp(cur->line, str)) + FAIL("lines didn't match; expected [%s], got [%s]", str, cur->line); + + str += strlen(str) + 1; /* Point to the next expected string */ + } + + free(str_top); + + return mlstring_expect_lines(mlstr, -1, expect_lines, expect_empty_p); +} + +/* Ensures that the width has been set properly after wrapping */ +static int check_width(const char *msg, const mlstring *mlstr) { + if (mlstr->overall_width == 0) + FAIL("Overall width was zero for string [%s]", msg); + + if (mlstr->overall_width > WRAP_WIDTH_PX) + FAIL("Overall width was %hu but the maximum wrap width was %d", mlstr->overall_width, WRAP_WIDTH_PX); + + return SUCCESS; +} + +/* FAIL() actually returns the wrong return codes in main, but it + * prints a message which is what we want. */ + +#define TRY_NEW(str, numl, expect_empty) \ + do { \ + mlstr = mlstring_allocate((str)); \ + if (!mlstr) \ + FAIL("%s", #str); \ + if (SUCCESS == mlstring_expect((str), (numl), mlstr, (expect_empty))) \ + SUCCEED(#str); \ + free(mlstr); \ + } while (0) + +/* Expects an XFontStruct* font, and tries to wrap to 100px */ +#define TRY_WRAP(str, minl, expect_empty) \ + do { \ + mltest = mlstring_allocate((str)); \ + if (!mltest) \ + SKIP(#str); \ + else { \ + mlstring_wrap(mltest, font, WRAP_WIDTH_PX); \ + check_width((str), mltest); \ + if (SUCCESS == mlstring_expect_lines(mltest, (minl), -1, (expect_empty))) \ + SUCCEED(#str); \ + free(mltest); \ + mltest = NULL; \ + } \ + } while (0) + + +/* Ideally this function would use stub functions rather than real Xlib. + * Then it would be possible to test for exact line counts, which would be + * more reliable. + * It also doesn't handle Xlib errors. + * + * Don't print anything based on the return value of this function, it only + * returns a value so that I can use the FAIL() macro without warning. + * + * Anyone who understands this function wins a cookie ;) + */ +static int test_wrapping(void) +{ + Display *dpy = NULL; + XFontStruct *font = NULL; + mlstring *mltest = NULL; + int ok = 0; + int chars_per_line, chars_first_word, i; + + const char *test_short = "a"; + const char *test_hardwrap = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const char *test_withnewlines = "a\nb"; + char *test_softwrap = NULL; + + dpy = XOpenDisplay(NULL); + if (!dpy) + goto end; + + font = XLoadQueryFont(dpy, "fixed"); + if (!font) + goto end; + + TRY_WRAP(test_short, 1, False); + TRY_WRAP(test_hardwrap, 2, False); + TRY_WRAP(test_withnewlines, 2, False); + + /* See if wrapping splits on word boundaries like it should */ + chars_per_line = WRAP_WIDTH_PX / font->max_bounds.width; + if (chars_per_line < 3) + goto end; + + /* Allocate for 2 lines + \0 */ + test_softwrap = malloc(chars_per_line * 2 + 1); + if (!test_softwrap) + goto end; + + /* 2 = strlen(' a'); that is, the minimum space required to start a new word + * on the same line. */ + chars_first_word = chars_per_line - 2; + + for (i = 0; i < chars_first_word; ++i) { + test_softwrap[i] = 'a'; /* first word */ + test_softwrap[i + chars_per_line] = 'b'; /* second word */ + } + /* space between first & second words */ + test_softwrap[chars_first_word] = ' '; + /* first char of second word (last char of first line) */ + test_softwrap[chars_first_word + 1] = 'b'; + /* after second word */ + test_softwrap[chars_per_line * 2] = '\0'; + + mltest = mlstring_allocate(test_softwrap); + mlstring_wrap(mltest, font, WRAP_WIDTH_PX); + + /* reusing 'i' for a moment here to make freeing mltest easier */ + i = strlen(mltest->lines->line); + free(mltest); + + if (i != chars_first_word) + FAIL("Soft wrap failed, expected the first line to be %d chars, but it was %d.", chars_first_word, i); + SUCCEED("Soft wrap"); + + ok = 1; + +end: + if (test_softwrap) + free(test_softwrap); + + if (font) + XFreeFont(dpy, font); + + if (dpy) + XCloseDisplay(dpy); + + if (!ok) + SKIP("wrapping"); + + return ok ? SUCCESS : SKIPPED; /* Unused, actually */ +} + + +int main(int argc, char *argv[]) +{ + const char *oneline = "1Foo"; + const char *twolines = "2Foo\nBar"; + const char *threelines = "3Foo\nBar\nWhippet"; + const char *trailnewline = "4Foo\n"; + const char *trailnewlines = "5Foo\n\n"; + const char *embeddednewlines = "6Foo\n\nBar"; + mlstring *mlstr; + + TRY_NEW(oneline, 1, False); + TRY_NEW(twolines, 2, False); + TRY_NEW(threelines, 3, False); + TRY_NEW(trailnewline, 2, True); + TRY_NEW(trailnewlines, 3, True); + TRY_NEW(embeddednewlines, 3, True); + + (void) test_wrapping(); + + fprintf(stdout, "%d test failures.\n", failcount); + + return !!failcount; +} + +/* vim:ts=8:sw=2:noet + */ diff --git a/driver/test-passwd.c b/driver/test-passwd.c new file mode 100644 index 00000000..42e2e280 --- /dev/null +++ b/driver/test-passwd.c @@ -0,0 +1,291 @@ +/* xscreensaver, Copyright (c) 1998-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* This is a kludgy test harness for debugging the password dialog box. + It's somewhat easier to debug it here than in the xscreensaver executable + itself. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "xscreensaver.h" +#include "resources.h" +#include "version.h" +#include "visual.h" +#include "auth.h" + +char *progname = 0; +char *progclass = 0; +XrmDatabase db = 0; +saver_info *global_si_kludge; + +FILE *real_stderr, *real_stdout; + +void monitor_power_on (saver_info *si) {} +Bool monitor_powered_on_p (saver_info *si) { return True; } +void initialize_screensaver_window (saver_info *si) {} +void raise_window (saver_info *si, Bool i, Bool b, Bool d) {} +Bool blank_screen (saver_info *si) {return False;} +void unblank_screen (saver_info *si) {} +Bool select_visual (saver_screen_info *ssi, const char *v) { return False; } +Bool window_exists_p (Display *dpy, Window window) {return True;} +void start_notice_events_timer (saver_info *si, Window w, Bool b) {} +Bool handle_clientmessage (saver_info *si, XEvent *e, Bool u) { return False; } +int BadWindow_ehandler (Display *dpy, XErrorEvent *error) { exit(1); } +const char *signal_name(int signal) { return "???"; } +Bool restore_real_vroot (saver_info *si) { return False; } +void store_saver_status (saver_info *si) {} +void saver_exit (saver_info *si, int status, const char *core) { exit(status);} +int move_mouse_grab (saver_info *si, Window to, Cursor c, int ts) { return 0; } +int mouse_screen (saver_info *si) { return 0; } +void check_for_leaks (const char *where) { } +void shutdown_stderr (saver_info *si) { } + +const char *blurb(void) { return progname; } +Atom XA_SCREENSAVER, XA_DEMO, XA_PREFS; + +void +idle_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + XEvent fake_event; + fake_event.type = 0; /* XAnyEvent type, ignored. */ + fake_event.xany.display = si->dpy; + fake_event.xany.window = 0; + XPutBackEvent (si->dpy, &fake_event); +} + + +static int +text_auth_conv ( + int num_msg, + const struct auth_message *auth_msgs, + struct auth_response **resp, + saver_info *si) +{ + char *input; + char buf[255]; + struct auth_response *responses; + int i; + + responses = calloc(num_msg, sizeof(struct auth_response)); + if (!responses) + return -1; + + /* The unlock state won't actually be used until this function returns and + * the auth module processes the response, but set it anyway for consistency + */ + si->unlock_state = ul_read; + + for (i = 0; i < num_msg; ++i) + { + printf ("\n%s: %s", progname, auth_msgs[i].msg); + if ( auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO + || auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO) + { + input = fgets (buf, sizeof(buf)-1, stdin); + if (!input || !*input) + exit (0); + if (input[strlen(input)-1] == '\n') + input[strlen(input)-1] = 0; + + responses[i].response = strdup(input); + } + } + + *resp = responses; + + si->unlock_state = ul_finished; + + return 0; +} + + +#ifdef __GNUC__ + __extension__ /* shut up about "string length is greater than the length + ISO C89 compilers are required to support" when including + the .ad file... */ +#endif + +static char *fallback[] = { +#include "XScreenSaver_ad.h" + 0 +}; + +int +main (int argc, char **argv) +{ + enum { PASS, SPLASH, TTY } which; + Widget toplevel_shell = 0; + saver_screen_info ssip; + saver_info sip; + saver_info *si = &sip; + saver_preferences *p = &si->prefs; + struct passwd *pw; + + memset(&sip, 0, sizeof(sip)); + memset(&ssip, 0, sizeof(ssip)); + + si->nscreens = 1; + si->screens = si->default_screen = &ssip; + ssip.global = si; + + global_si_kludge = si; + real_stderr = stderr; + real_stdout = stdout; + + si->version = (char *) malloc (5); + memcpy (si->version, screensaver_id + 17, 4); + progname = argv[0]; + { + char *s = strrchr(progname, '/'); + if (*s) strcpy (progname, s+1); + } + + if (argc != 2) goto USAGE; + else if (!strcmp (argv[1], "pass")) which = PASS; + else if (!strcmp (argv[1], "splash")) which = SPLASH; + else if (!strcmp (argv[1], "tty")) which = TTY; + else + { + USAGE: + fprintf (stderr, "usage: %s [ pass | splash | tty ]\n", progname); + exit (1); + } + +#ifdef NO_LOCKING + if (which == PASS || which == TTY) + { + fprintf (stderr, "%s: compiled with NO_LOCKING\n", progname); + exit (1); + } +#endif + +#ifndef NO_LOCKING + /* before hack_uid() for proper permissions */ + lock_priv_init (argc, argv, True); + + hack_uid (si); + + if (! lock_init (argc, argv, True)) + { + si->locking_disabled_p = True; + si->nolock_reason = "error getting password"; + } +#endif + + progclass = "XScreenSaver"; + + if (which != TTY) + { + toplevel_shell = XtAppInitialize (&si->app, progclass, 0, 0, + &argc, argv, fallback, + 0, 0); + + si->dpy = XtDisplay (toplevel_shell); + p->db = XtDatabase (si->dpy); + si->default_screen->toplevel_shell = toplevel_shell; + si->default_screen->screen = XtScreen(toplevel_shell); + si->default_screen->default_visual = + si->default_screen->current_visual = + DefaultVisualOfScreen(si->default_screen->screen); + si->default_screen->screensaver_window = + RootWindowOfScreen(si->default_screen->screen); + si->default_screen->current_depth = + visual_depth(si->default_screen->screen, + si->default_screen->current_visual); + + ssip.width = WidthOfScreen(ssip.screen); + ssip.height = HeightOfScreen(ssip.screen); + + db = p->db; + XtGetApplicationNameAndClass (si->dpy, &progname, &progclass); + + load_init_file (si->dpy, &si->prefs); + } + + p->verbose_p = True; + + pw = getpwuid (getuid ()); + si->user = strdup (pw->pw_name); + +/* si->nscreens = 0; + si->screens = si->default_screen = 0; */ + + while (1) + { +#ifndef NO_LOCKING + if (which == PASS) + { + si->unlock_cb = gui_auth_conv; + si->auth_finished_cb = auth_finished_cb; + + xss_authenticate(si, True); + + if (si->unlock_state == ul_success) + fprintf (stderr, "%s: authentication succeeded\n", progname); + else + fprintf (stderr, "%s: authentication FAILED!\n", progname); + + XSync(si->dpy, False); + fprintf (stderr, "\n######################################\n\n"); + sleep (3); + } + else +#endif + if (which == SPLASH) + { + XEvent event; + make_splash_dialog (si); + XtAppAddTimeOut (si->app, p->splash_duration + 1000, + idle_timer, (XtPointer) si); + while (si->splash_dialog) + { + XtAppNextEvent (si->app, &event); + if (event.xany.window == si->splash_dialog) + handle_splash_event (si, &event); + XtDispatchEvent (&event); + } + XSync (si->dpy, False); + sleep (1); + } +#ifndef NO_LOCKING + else if (which == TTY) + { + si->unlock_cb = text_auth_conv; + + printf ("%s: Authenticating user %s\n", progname, si->user); + xss_authenticate(si, True); + + if (si->unlock_state == ul_success) + printf ("%s: Ok!\n", progname); + else + printf ("%s: Wrong!\n", progname); + } +#endif + else + abort(); + } + + free(si->user); +} diff --git a/driver/test-randr.c b/driver/test-randr.c new file mode 100644 index 00000000..74ead37f --- /dev/null +++ b/driver/test-randr.c @@ -0,0 +1,339 @@ +/* test-randr.c --- playing with the Resize And Rotate extension. + * xscreensaver, Copyright (c) 2004-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include + +char *progname = 0; +char *progclass = "XScreenSaver"; + +static const char * +blurb (void) +{ + static char buf[255]; + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + + +static Bool error_handler_hit_p = False; + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + error_handler_hit_p = True; + return 0; +} + + +int +main (int argc, char **argv) +{ + int event_number = -1, error_number = -1; + int major = -1, minor = -1; + int nscreens = 0; + int i; + + XtAppContext app; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + + nscreens = ScreenCount(dpy); + + if (!XRRQueryExtension(dpy, &event_number, &error_number)) + { + fprintf(stderr, "%s: XRRQueryExtension(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, "%s: server does not support the RANDR extension.\n", + blurb()); + major = -1; + } + else + { + fprintf(stderr, "%s: XRRQueryExtension(dpy, ...) ==> %d, %d\n", + blurb(), event_number, error_number); + + if (!XRRQueryVersion(dpy, &major, &minor)) + { + fprintf(stderr, "%s: XRRQueryVersion(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, "%s: server didn't report RANDR version numbers?\n", + blurb()); + } + else + fprintf(stderr, "%s: XRRQueryVersion(dpy, ...) ==> %d, %d\n", blurb(), + major, minor); + } + + for (i = 0; i < nscreens; i++) + { + XRRScreenConfiguration *rrc; + XErrorHandler old_handler; + + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + rrc = (major >= 0 ? XRRGetScreenInfo (dpy, RootWindow (dpy, i)) : 0); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + if (error_handler_hit_p) + { + fprintf(stderr, "%s: XRRGetScreenInfo(dpy, %d) ==> X error:\n", + blurb(), i); + /* do it again without the error handler to print the error */ + rrc = XRRGetScreenInfo (dpy, RootWindow (dpy, i)); + } + else if (rrc) + { + SizeID current_size = -1; + Rotation current_rotation = ~0; + + fprintf (stderr, "\n%s: Screen %d\n", blurb(), i); + + current_size = + XRRConfigCurrentConfiguration (rrc, ¤t_rotation); + + /* Times */ +# if 0 /* #### This is wrong -- I don't understand what these two + timestamp numbers represent, or how they correlate + to the wall clock or to each other. */ + { + Time server_time, config_time; + server_time = XRRConfigTimes (rrc, &config_time); + if (config_time == 0 || server_time == 0) + fprintf (stderr, "%s: config has never been changed\n", + blurb()); + else + fprintf (stderr, "%s: config changed %lu seconds ago\n", + blurb(), (unsigned long) (server_time - config_time)); + } +# endif + + /* Rotations */ + { + Rotation available, current; + available = XRRConfigRotations (rrc, ¤t); + + fprintf (stderr, "%s: Available Rotations:\t", blurb()); + if (available & RR_Rotate_0) fprintf (stderr, " 0"); + if (available & RR_Rotate_90) fprintf (stderr, " 90"); + if (available & RR_Rotate_180) fprintf (stderr, " 180"); + if (available & RR_Rotate_270) fprintf (stderr, " 270"); + if (! (available & (RR_Rotate_0 | RR_Rotate_90 | + RR_Rotate_180 | RR_Rotate_270))) + fprintf (stderr, " none"); + fprintf (stderr, "\n"); + + if (current_rotation != current) + fprintf (stderr, + "%s: WARNING: rotation inconsistency: 0x%X vs 0x%X\n", + blurb(), current_rotation, current); + + fprintf (stderr, "%s: Current Rotation:\t", blurb()); + if (current & RR_Rotate_0) fprintf (stderr, " 0"); + if (current & RR_Rotate_90) fprintf (stderr, " 90"); + if (current & RR_Rotate_180) fprintf (stderr, " 180"); + if (current & RR_Rotate_270) fprintf (stderr, " 270"); + if (! (current & (RR_Rotate_0 | RR_Rotate_90 | + RR_Rotate_180 | RR_Rotate_270))) + fprintf (stderr, " none"); + fprintf (stderr, "\n"); + + fprintf (stderr, "%s: Available Reflections:\t", blurb()); + if (available & RR_Reflect_X) fprintf (stderr, " X"); + if (available & RR_Reflect_Y) fprintf (stderr, " Y"); + if (! (available & (RR_Reflect_X | RR_Reflect_Y))) + fprintf (stderr, " none"); + fprintf (stderr, "\n"); + + fprintf (stderr, "%s: Current Reflections:\t", blurb()); + if (current & RR_Reflect_X) fprintf (stderr, " X"); + if (current & RR_Reflect_Y) fprintf (stderr, " Y"); + if (! (current & (RR_Reflect_X | RR_Reflect_Y))) + fprintf (stderr, " none"); + fprintf (stderr, "\n"); + } + + /* Sizes */ + { + int nsizes, j; + XRRScreenSize *rrsizes; + + rrsizes = XRRConfigSizes (rrc, &nsizes); + if (nsizes <= 0) + fprintf (stderr, "%s: sizes:\t none\n", blurb()); + else + for (j = 0; j < nsizes; j++) + { + short *rates; + int nrates, k; + fprintf (stderr, + "%s: %c size %d: %d x %d\t rates:", + blurb(), + (j == current_size ? '+' : ' '), + j, + rrsizes[j].width, rrsizes[j].height); + + rates = XRRConfigRates (rrc, j, &nrates); + if (nrates == 0) + fprintf (stderr, " none?"); + else + for (k = 0; k < nrates; k++) + fprintf (stderr, " %d", rates[k]); + fprintf (stderr, "\n"); + /* don't free 'rates' */ + } + /* don't free 'rrsizes' */ + } + + XRRFreeScreenConfigInfo (rrc); + } + else if (major >= 0) + { + fprintf(stderr, "%s: XRRGetScreenInfo(dpy, %d) ==> NULL\n", + blurb(), i); + } + + +# ifdef HAVE_RANDR_12 + if (major > 1 || (major == 1 && minor >= 2)) + { + int j; + XRRScreenResources *res = + XRRGetScreenResources (dpy, RootWindow (dpy, i)); + fprintf (stderr, "\n"); + for (j = 0; j < res->noutput; j++) + { + int k; + XRROutputInfo *rroi = + XRRGetOutputInfo (dpy, res, res->outputs[j]); + fprintf (stderr, "%s: Output %d: %s: %s (%d)\n", blurb(), j, + rroi->name, + (rroi->connection == RR_Disconnected ? "disconnected" : + rroi->connection == RR_UnknownConnection ? "unknown" : + "connected"), + (int) rroi->crtc); + for (k = 0; k < rroi->ncrtc; k++) + { + XRRCrtcInfo *crtci = XRRGetCrtcInfo (dpy, res, + rroi->crtcs[k]); + fprintf(stderr, "%s: %c CRTC %d (%d): %dx%d+%d+%d\n", + blurb(), + (rroi->crtc == rroi->crtcs[k] ? '+' : ' '), + k, (int) rroi->crtcs[k], + crtci->width, crtci->height, crtci->x, crtci->y); + XRRFreeCrtcInfo (crtci); + } + XRRFreeOutputInfo (rroi); + fprintf (stderr, "\n"); + } + XRRFreeScreenResources (res); + } +# endif /* HAVE_RANDR_12 */ + } + + if (major > 0) + { + Window w[20]; + XWindowAttributes xgwa[20]; + + for (i = 0; i < nscreens; i++) + { + XRRSelectInput (dpy, RootWindow (dpy, i), RRScreenChangeNotifyMask); + w[i] = RootWindow (dpy, i); + XGetWindowAttributes (dpy, w[i], &xgwa[i]); + } + + XSync (dpy, False); + + fprintf (stderr, "\n%s: awaiting events...\n\n" + "\t(If you resize the screen or add/remove monitors, this should\n" + "\tnotice that and print stuff. Otherwise, hit ^C.)\n\n", + progname); + while (1) + { + XEvent event; + XNextEvent (dpy, &event); + + if (event.type == event_number + RRScreenChangeNotify) + { + XRRScreenChangeNotifyEvent *xrr_event = + (XRRScreenChangeNotifyEvent *) &event; + int screen = XRRRootToScreen (dpy, xrr_event->window); + + fprintf (stderr, "%s: screen %d: RRScreenChangeNotify event\n", + progname, screen); + + fprintf (stderr, "%s: screen %d: old size: \t%d x %d\n", + progname, screen, + DisplayWidth (dpy, screen), + DisplayHeight (dpy, screen)); + fprintf (stderr, "%s: screen %d: old root 0x%lx:\t%d x %d\n", + progname, screen, (unsigned long) w[screen], + xgwa[screen].width, xgwa[screen].height); + + XRRUpdateConfiguration (&event); + XSync (dpy, False); + + fprintf (stderr, "%s: screen %d: new size: \t%d x %d\n", + progname, screen, + DisplayWidth (dpy, screen), + DisplayHeight (dpy, screen)); + + w[screen] = RootWindow (dpy, screen); + XGetWindowAttributes (dpy, w[screen], &xgwa[screen]); + fprintf (stderr, "%s: screen %d: new root 0x%lx:\t%d x %d\n", + progname, screen, (unsigned long) w[screen], + xgwa[screen].width, xgwa[screen].height); + fprintf (stderr, "\n"); + } + else + { + fprintf (stderr, "%s: event %d\n", progname, event.type); + } + } + } + + XSync (dpy, False); + exit (0); +} diff --git a/driver/test-screens.c b/driver/test-screens.c new file mode 100644 index 00000000..2fb3e35d --- /dev/null +++ b/driver/test-screens.c @@ -0,0 +1,208 @@ +/* test-screens.c --- some test cases for the "monitor sanity" checks. + * xscreensaver, Copyright (c) 2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" +#include "visual.h" + +#undef WidthOfScreen +#undef HeightOfScreen +#define WidthOfScreen(s) 10240 +#define HeightOfScreen(s) 10240 + +#undef screen_number +#define screen_number(s) ((int) s) + +#include "screens.c" /* to get at static void check_monitor_sanity() */ + +char *progname = 0; +char *progclass = "XScreenSaver"; + +const char *blurb(void) { return progname; } + +Bool safe_XF86VidModeGetViewPort(Display *d, int i, int *x, int *y) { abort(); } +void initialize_screen_root_widget(saver_screen_info *ssi) { abort(); } +Visual *get_best_gl_visual (saver_info *si, Screen *sc) { abort(); } + + +static const char * +failstr (monitor_sanity san) +{ + switch (san) { + case S_SANE: return "OK"; + case S_ENCLOSED: return "ENC"; + case S_DUPLICATE: return "DUP"; + case S_OVERLAP: return "OVR"; + case S_OFFSCREEN: return "OFF"; + case S_DISABLED: return "DIS"; + default: abort(); break; + } +} + + +static void +test (int testnum, const char *screens, const char *desired) +{ + monitor *monitors[100]; + char result[2048]; + char *out = result; + int i, nscreens = 0; + char *token = strtok (strdup(screens), ","); + while (token) + { + monitor *m = calloc (1, sizeof (monitor)); + char c; + m->id = (testnum * 1000) + nscreens; + if (5 == sscanf (token, "%dx%d+%d+%d@%d%c", + &m->width, &m->height, &m->x, &m->y, + (int *) &m->screen, &c)) + ; + else if (4 != sscanf (token, "%dx%d+%d+%d%c", + &m->width, &m->height, &m->x, &m->y, &c)) + { + fprintf (stderr, "%s: unparsable geometry: %s\n", blurb(), token); + exit (1); + } + monitors[nscreens] = m; + nscreens++; + token = strtok (0, ","); + } + monitors[nscreens] = 0; + + check_monitor_sanity (monitors); + + *out = 0; + for (i = 0; i < nscreens; i++) + { + monitor *m = monitors[i]; + if (out != result) *out++ = ','; + if (m->sanity == S_SANE) + { + sprintf (out, "%dx%d+%d+%d", m->width, m->height, m->x, m->y); + if (m->screen) + sprintf (out + strlen(out), "@%d", (int) m->screen); + } + else + strcpy (out, failstr (m->sanity)); + out += strlen(out); + } + *out = 0; + + if (!strcmp (result, desired)) + fprintf (stderr, "%s: test %2d OK\n", blurb(), testnum); + else + fprintf (stderr, "%s: test %2d FAILED:\n" + "%s: given: %s\n" + "%s: wanted: %s\n" + "%s: got: %s\n", + blurb(), testnum, + blurb(), screens, + blurb(), desired, + blurb(), result); + +# if 0 + { + saver_info SI; + SI.monitor_layout = monitors; + describe_monitor_layout (&SI); + } +# endif + +} + +static void +run_tests(void) +{ + int i = 1; +# define A(a) test (i++, a, a); +# define B(a,b) test (i++, a, b) + + A(""); + A("1024x768+0+0"); + A("1024x768+0+0,1024x768+1024+0"); + A("1024x768+0+0,1024x768+0+768"); + A("1024x768+0+0,1024x768+0+768,1024x768+1024+0"); + A("800x600+0+0,800x600+0+0@1,800x600+10+0@2"); + + B("1024x768+999999+0", + "OFF"); + B("1024x768+-999999+-999999", + "OFF"); + B("1024x768+0+0,1024x768+0+0", + "1024x768+0+0,DUP"); + B("1024x768+0+0,1024x768+0+0,1024x768+0+0", + "1024x768+0+0,DUP,DUP"); + B("1024x768+0+0,1024x768+1024+0,1024x768+0+0", + "1024x768+0+0,1024x768+1024+0,DUP"); + B("1280x1024+0+0,1024x768+0+64,800x600+0+0,640x480+0+0,720x400+0+0", + "1280x1024+0+0,ENC,ENC,ENC,ENC"); + B("1024x768+0+64,1280x1024+0+0,800x600+0+0,640x480+0+0,800x600+0+0,720x400+0+0", + "ENC,1280x1024+0+0,ENC,ENC,ENC,ENC"); + B("1024x768+0+64,1280x1024+0+0,800x600+0+0,640x480+0+0,1280x1024+0+0,720x400+0+0", + "ENC,1280x1024+0+0,ENC,ENC,DUP,ENC"); + B("720x400+0+0,640x480+0+0,800x600+0+0,1024x768+0+64,1280x1024+0+0", + "ENC,ENC,ENC,ENC,1280x1024+0+0"); + B("1280x1024+0+0,800x600+1280+0,800x600+1300+0", + "1280x1024+0+0,800x600+1280+0,OVR"); + B("1280x1024+0+0,800x600+1280+0,800x600+1300+0,1280x1024+0+0,800x600+1280+0", + "1280x1024+0+0,800x600+1280+0,OVR,DUP,DUP"); + + /* +-------------+----+ +------+---+ 1: 1440x900, widescreen display + | : | | 3+4 : | 2: 1280x1024, conventional display + | 1+2 : 1 | +......+ | 3: 1024x768, laptop + | : | | 3 | 4: 800x600, external projector + +.............+----+ +----------+ + | 2 | + | | + +-------------+ + */ + B("1440x900+0+0,1280x1024+0+0,1024x768+1440+0,800x600+1440+0", + "1440x900+0+0,OVR,1024x768+1440+0,ENC"); + B("800x600+0+0,800x600+0+0,800x600+800+0", + "800x600+0+0,DUP,800x600+800+0"); + B("1600x1200+0+0,1360x768+0+0", + "1600x1200+0+0,ENC"); + B("1600x1200+0+0,1360x768+0+0,1600x1200+0+0@1,1360x768+0+0@1", + "1600x1200+0+0,ENC,1600x1200+0+0@1,ENC"); +} + + +int +main (int argc, char **argv) +{ + char *s; + progname = argv[0]; + s = strrchr(progname, '/'); + if (s) progname = s+1; + if (argc != 1) + { + fprintf (stderr, "usage: %s\n", argv[0]); + exit (1); + } + + run_tests(); + + exit (0); +} diff --git a/driver/test-uid.c b/driver/test-uid.c new file mode 100644 index 00000000..6a1f9cc1 --- /dev/null +++ b/driver/test-uid.c @@ -0,0 +1,209 @@ +/* test-uid.c --- playing with setuid. + * xscreensaver, Copyright (c) 1998, 2005 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +static void +print(void) +{ + int uid = getuid(); + int gid = getgid(); + int euid = geteuid(); + int egid = getegid(); + struct passwd *p = 0; + struct group *g = 0; + gid_t groups[1024]; + int n, size; + + p = getpwuid (uid); + g = getgrgid (gid); + fprintf(stderr, "real user/group: %ld/%ld (%s/%s)\n", (long) uid, (long) gid, + (p && p->pw_name ? p->pw_name : "???"), + (g && g->gr_name ? g->gr_name : "???")); + + p = getpwuid (euid); + g = getgrgid (egid); + fprintf(stderr, "eff. user/group: %ld/%ld (%s/%s)\n", (long)euid, (long)egid, + (p && p->pw_name ? p->pw_name : "???"), + (g && g->gr_name ? g->gr_name : "???")); + + size = sizeof(groups) / sizeof(gid_t); + n = getgroups(size - 1, groups); + if (n < 0) + perror("getgroups failed"); + else + { + int i; + fprintf (stderr, "eff. group list: ["); + for (i = 0; i < n; i++) + { + g = getgrgid (groups[i]); + fprintf(stderr, "%s%s=%ld", (i == 0 ? "" : ", "), + (g->gr_name ? g->gr_name : "???"), + (long) groups[i]); + } + fprintf (stderr, "]\n"); + } +} + +int +main (int argc, char **argv) +{ + int i; + struct passwd *p = 0; + struct group *g = 0; + + if (argc <= 1) + { + fprintf(stderr, + "usage: %s [ user/group ... ]\n" + "\tEach argument may be a user name, or user/group.\n" + "\tThis program will attempt to setuid/setgid to each\n" + "\tin turn, and report the results. The user and group\n" + "\tnames may be strings, or numeric.\n", + argv[0]); + exit(1); + } + + print(); + for (i = 1; i < argc; i++) + { + char *user = argv[i]; + char *group = strchr(user, '/'); + if (!group) + group = strchr(user, '.'); + if (group) + *group++ = 0; + + if (group && *group) + { + long gid = 0; + int was_numeric = 0; + + g = 0; + if (*group == '-' || (*group >= '0' && *group <= '9')) + if (1 == sscanf(group, "%ld", &gid)) + { + g = getgrgid (gid); + was_numeric = 1; + } + + if (!g) + g = getgrnam(group); + + if (g) + { + gid = g->gr_gid; + group = g->gr_name; + } + else + { + if (was_numeric) + { + fprintf(stderr, "no group numbered %s.\n", group); + group = ""; + } + else + { + fprintf(stderr, "no group named %s.\n", group); + goto NOGROUP; + } + } + + fprintf(stderr, "setgroups(1, [%ld]) \"%s\"", gid, group); + { + gid_t g2 = gid; + if (setgroups(1, &g2) == 0) + fprintf(stderr, " succeeded.\n"); + else + perror(" failed"); + } + + fprintf(stderr, "setgid(%ld) \"%s\"", gid, group); + if (setgid(gid) == 0) + fprintf(stderr, " succeeded.\n"); + else + perror(" failed"); + + NOGROUP: ; + } + + if (user && *user) + { + long uid = 0; + int was_numeric = 0; + + p = 0; + if (*user == '-' || (*user >= '0' && *user <= '9')) + if (1 == sscanf(user, "%ld", &uid)) + { + p = getpwuid (uid); + was_numeric = 1; + } + + if (!p) + p = getpwnam(user); + + if (p) + { + uid = p->pw_uid; + user = p->pw_name; + } + else + { + if (was_numeric) + { + fprintf(stderr, "no user numbered \"%s\".\n", user); + user = ""; + } + else + { + fprintf(stderr, "no user named %s.\n", user); + goto NOUSER; + } + } + + fprintf(stderr, "setuid(%ld) \"%s\"", uid, user); + if (setuid(uid) == 0) + fprintf(stderr, " succeeded.\n"); + else + perror(" failed"); + NOUSER: ; + } + print(); + } + + fprintf(stderr, + "running \"whoami\" and \"groups\" in a sub-process reports:\n"); + fflush(stdout); + fflush(stderr); + system ("/bin/sh -c 'echo \"`whoami` / `groups`\"'"); + + fflush(stdout); + fflush(stderr); + exit(0); +} diff --git a/driver/test-vp.c b/driver/test-vp.c new file mode 100644 index 00000000..bf1a0b18 --- /dev/null +++ b/driver/test-vp.c @@ -0,0 +1,213 @@ +/* test-xinerama.c --- playing with XF86VidModeGetViewPort + * xscreensaver, Copyright (c) 2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +char *progname = 0; +char *progclass = "XScreenSaver"; + +static const char * +blurb (void) +{ + static char buf[255]; + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + + +static Bool error_handler_hit_p = False; + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + error_handler_hit_p = True; + return 0; +} + + +static int +screen_count (Display *dpy) +{ + int n = ScreenCount(dpy); + int xn = 0; + int event_number, error_number; + + if (!XineramaQueryExtension (dpy, &event_number, &error_number)) + { + fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> False\n", + blurb()); + goto DONE; + } + else + fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> %d, %d\n", + blurb(), event_number, error_number); + + if (!XineramaIsActive(dpy)) + { + fprintf(stderr, "%s: XineramaIsActive(dpy) ==> False\n", + blurb()); + goto DONE; + } + else + { + int major, minor; + XineramaScreenInfo *xsi; + fprintf(stderr, "%s: XineramaIsActive(dpy) ==> True\n", + blurb()); + if (!XineramaQueryVersion(dpy, &major, &minor)) + { + fprintf(stderr, + "%s: XineramaQueryVersion(dpy, ...) ==> False\n", + blurb()); + goto DONE; + } + else + fprintf(stderr, + "%s: XineramaQueryVersion(dpy, ...) ==> %d, %d\n", + blurb(), major, minor); + + xsi = XineramaQueryScreens (dpy, &xn); + if (xsi) XFree (xsi); + } + + DONE: + fprintf (stderr, "\n"); + fprintf (stderr, "%s: X client screens: %d\n", blurb(), n); + fprintf (stderr, "%s: Xinerama screens: %d\n", blurb(), xn); + fprintf (stderr, "\n"); + + if (xn > n) return xn; + else return n; +} + + +int +main (int argc, char **argv) +{ + int event_number, error_number; + int major, minor; + int nscreens = 0; + int i; + + XtAppContext app; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + + if (!XF86VidModeQueryExtension(dpy, &event_number, &error_number)) + { + fprintf(stderr, "%s: XF86VidModeQueryExtension(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, + "%s: server does not support the XF86VidMode extension.\n", + blurb()); + exit(1); + } + else + fprintf(stderr, "%s: XF86VidModeQueryExtension(dpy, ...) ==> %d, %d\n", + blurb(), event_number, error_number); + + if (!XF86VidModeQueryVersion(dpy, &major, &minor)) + { + fprintf(stderr, "%s: XF86VidModeQueryVersion(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, + "%s: server didn't report XF86VidMode version numbers?\n", + blurb()); + } + else + fprintf(stderr, "%s: XF86VidModeQueryVersion(dpy, ...) ==> %d, %d\n", + blurb(), major, minor); + + nscreens = screen_count (dpy); + + for (i = 0; i < nscreens; i++) + { + int result = 0; + int x = 0, y = 0, dot = 0; + XF86VidModeModeLine ml = { 0, }; + XErrorHandler old_handler; + + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + result = XF86VidModeGetViewPort (dpy, i, &x, &y); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + if (error_handler_hit_p) + { + fprintf(stderr, + "%s: XF86VidModeGetViewPort(dpy, %d, ...) ==> X error\n", + blurb(), i); + continue; + } + + if (! result) + fprintf(stderr, "%s: XF86VidModeGetViewPort(dpy, %d, ...) ==> %d\n", + blurb(), i, result); + + result = XF86VidModeGetModeLine (dpy, i, &dot, &ml); + if (! result) + fprintf(stderr, "%s: XF86VidModeGetModeLine(dpy, %d, ...) ==> %d\n", + blurb(), i, result); + + fprintf (stderr, "%s: screen %d: %dx%d; viewport: %dx%d+%d+%d\n", + blurb(), i, + DisplayWidth (dpy, i), DisplayHeight (dpy, i), + ml.hdisplay, ml.vdisplay, x, y + ); + + fprintf (stderr, + "%s: hsync start %d; end %d; total %d; skew %d;\n", + blurb(), + ml.hsyncstart, ml.hsyncend, ml.htotal, ml.hskew); + fprintf (stderr, + "%s: vsync start %d; end %d; total %d; flags 0x%04x;\n", + blurb(), + ml.vsyncstart, ml.vsyncend, ml.vtotal, ml.flags); + fprintf (stderr, "\n"); + } + XSync (dpy, False); + exit (0); +} diff --git a/driver/test-xdpms.c b/driver/test-xdpms.c new file mode 100644 index 00000000..a401c38c --- /dev/null +++ b/driver/test-xdpms.c @@ -0,0 +1,160 @@ +/* test-xdpms.c --- playing with the XDPMS extension. + * xscreensaver, Copyright (c) 1998 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +extern Bool DPMSQueryExtension (Display *dpy, int *event_ret, int *error_ret); +extern Bool DPMSCapable (Display *dpy); +extern Status DPMSForceLevel (Display *dpy, CARD16 level); +extern Status DPMSInfo (Display *dpy, CARD16 *power_level, BOOL *state); + +extern Status DPMSGetVersion (Display *dpy, int *major_ret, int *minor_ret); +extern Status DPMSSetTimeouts (Display *dpy, + CARD16 standby, CARD16 suspend, CARD16 off); +extern Bool DPMSGetTimeouts (Display *dpy, + CARD16 *standby, CARD16 *suspend, CARD16 *off); +extern Status DPMSEnable (Display *dpy); +extern Status DPMSDisable (Display *dpy); + + +char *progname = 0; +char *progclass = "XScreenSaver"; + +static const char * +blurb (void) +{ + static char buf[255]; + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + + +int +main (int argc, char **argv) +{ + int delay = 10; + + int event_number, error_number; + int major, minor; + CARD16 standby, suspend, off; + CARD16 state; + BOOL onoff; + + XtAppContext app; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + + if (!DPMSQueryExtension(dpy, &event_number, &error_number)) + { + fprintf(stderr, "%s: DPMSQueryExtension(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, "%s: server does not support the XDPMS extension.\n", + blurb()); + exit(1); + } + else + fprintf(stderr, "%s: DPMSQueryExtension(dpy, ...) ==> %d, %d\n", blurb(), + event_number, error_number); + + if (!DPMSCapable(dpy)) + { + fprintf(stderr, "%s: DPMSCapable(dpy) ==> False\n", blurb()); + fprintf(stderr, "%s: server says hardware doesn't support DPMS.\n", + blurb()); + exit(1); + } + else + fprintf(stderr, "%s: DPMSCapable(dpy) ==> True\n", blurb()); + + if (!DPMSGetVersion(dpy, &major, &minor)) + { + fprintf(stderr, "%s: DPMSGetVersion(dpy, ...) ==> False\n", blurb()); + fprintf(stderr, "%s: server didn't report XDPMS version numbers?\n", + blurb()); + } + else + fprintf(stderr, "%s: DPMSGetVersion(dpy, ...) ==> %d, %d\n", blurb(), + major, minor); + + if (!DPMSGetTimeouts(dpy, &standby, &suspend, &off)) + { + fprintf(stderr, "%s: DPMSGetTimeouts(dpy, ...) ==> False\n", blurb()); + fprintf(stderr, "%s: server didn't report DPMS timeouts?\n", blurb()); + } + else + fprintf(stderr, + "%s: DPMSGetTimeouts(dpy, ...)\n" + "\t ==> standby = %d, suspend = %d, off = %d\n", + blurb(), standby, suspend, off); + + while (1) + { + if (!DPMSInfo(dpy, &state, &onoff)) + { + fprintf(stderr, "%s: DPMSInfo(dpy, ...) ==> False\n", blurb()); + fprintf(stderr, "%s: couldn't read DPMS state?\n", blurb()); + onoff = 0; + state = -1; + } + else + { + fprintf(stderr, "%s: DPMSInfo(dpy, ...) ==> %s, %s\n", blurb(), + (state == DPMSModeOn ? "DPMSModeOn" : + state == DPMSModeStandby ? "DPMSModeStandby" : + state == DPMSModeSuspend ? "DPMSModeSuspend" : + state == DPMSModeOff ? "DPMSModeOff" : "???"), + (onoff == 1 ? "On" : onoff == 0 ? "Off" : "???")); + } + + if (state == DPMSModeStandby || + state == DPMSModeSuspend || + state == DPMSModeOff) + { + Status st; + fprintf(stderr, "%s: monitor is off; turning it on.\n", blurb()); + st = DPMSForceLevel (dpy, DPMSModeOn); + fprintf (stderr, "%s: DPMSForceLevel (dpy, DPMSModeOn) ==> %s\n", + blurb(), (st ? "Ok" : "Error")); + } + + sleep (delay); + } +} diff --git a/driver/test-xinerama.c b/driver/test-xinerama.c new file mode 100644 index 00000000..8bafbb07 --- /dev/null +++ b/driver/test-xinerama.c @@ -0,0 +1,112 @@ +/* test-xinerama.c --- playing with the Xinerama extension. + * xscreensaver, Copyright (c) 2003 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include + +char *progname = 0; +char *progclass = "XScreenSaver"; + +static const char * +blurb (void) +{ + static char buf[255]; + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + + +int +main (int argc, char **argv) +{ + int event_number, error_number; + int major, minor; + int nscreens = 0; + XineramaScreenInfo *xsi; + int i; + + XtAppContext app; + Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, + &argc, argv, 0, 0, 0); + Display *dpy = XtDisplay (toplevel_shell); + XtGetApplicationNameAndClass (dpy, &progname, &progclass); + + if (!XineramaQueryExtension(dpy, &event_number, &error_number)) + { + fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, "%s: server does not support the Xinerama extension.\n", + blurb()); + exit(1); + } + else + fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> %d, %d\n", + blurb(), event_number, error_number); + + if (!XineramaIsActive(dpy)) + { + fprintf(stderr, "%s: XineramaIsActive(dpy) ==> False\n", blurb()); + fprintf(stderr, "%s: server says Xinerama is turned off.\n", blurb()); + exit(1); + } + else + fprintf(stderr, "%s: XineramaIsActive(dpy) ==> True\n", blurb()); + + if (!XineramaQueryVersion(dpy, &major, &minor)) + { + fprintf(stderr, "%s: XineramaQueryVersion(dpy, ...) ==> False\n", + blurb()); + fprintf(stderr, "%s: server didn't report Xinerama version numbers?\n", + blurb()); + } + else + fprintf(stderr, "%s: XineramaQueryVersion(dpy, ...) ==> %d, %d\n", blurb(), + major, minor); + + xsi = XineramaQueryScreens (dpy, &nscreens); + fprintf(stderr, "%s: %d Xinerama screens\n", blurb(), nscreens); + + for (i = 0; i < nscreens; i++) + fprintf (stderr, "%s: screen %d: %dx%d+%d+%d\n", + blurb(), + xsi[i].screen_number, + xsi[i].width, xsi[i].height, + xsi[i].x_org, xsi[i].y_org); + XFree (xsi); + XSync (dpy, False); + exit (0); +} diff --git a/driver/timers.c b/driver/timers.c new file mode 100644 index 00000000..97a26c02 --- /dev/null +++ b/driver/timers.c @@ -0,0 +1,1540 @@ +/* timers.c --- detecting when the user is idle, and other timer-related tasks. + * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_XMU +# ifndef VMS +# include +# else /* VMS */ +# include +# endif /* VMS */ +# else /* !HAVE_XMU */ +# include "xmu.h" +#endif /* !HAVE_XMU */ + +#ifdef HAVE_XIDLE_EXTENSION +#include +#endif /* HAVE_XIDLE_EXTENSION */ + +#ifdef HAVE_MIT_SAVER_EXTENSION +#include +#endif /* HAVE_MIT_SAVER_EXTENSION */ + +#ifdef HAVE_SGI_SAVER_EXTENSION +#include +#endif /* HAVE_SGI_SAVER_EXTENSION */ + +#ifdef HAVE_RANDR +#include +#endif /* HAVE_RANDR */ + +#include "xscreensaver.h" + +#undef ABS +#define ABS(x)((x)<0?-(x):(x)) + +#undef MAX +#define MAX(x,y)((x)>(y)?(x):(y)) + + +#ifdef HAVE_PROC_INTERRUPTS +static Bool proc_interrupts_activity_p (saver_info *si); +#endif /* HAVE_PROC_INTERRUPTS */ + +static void check_for_clock_skew (saver_info *si); + + +void +idle_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + + /* What an amazingly shitty design. Not only does Xt execute timeout + events from XtAppNextEvent() instead of from XtDispatchEvent(), but + there is no way to tell Xt to block until there is an X event OR a + timeout happens. Once your timeout proc is called, XtAppNextEvent() + still won't return until a "real" X event comes in. + + So this function pushes a stupid, gratuitous, unnecessary event back + on the event queue to force XtAppNextEvent to return Right Fucking Now. + When the code in sleep_until_idle() sees an event of type XAnyEvent, + which the server never generates, it knows that a timeout has occurred. + */ + XEvent fake_event; + fake_event.type = 0; /* XAnyEvent type, ignored. */ + fake_event.xany.display = si->dpy; + fake_event.xany.window = 0; + XPutBackEvent (si->dpy, &fake_event); + + /* If we are the timer that just went off, clear the pointer to the id. */ + if (id) + { + if (si->timer_id && *id != si->timer_id) + abort(); /* oops, scheduled timer twice?? */ + si->timer_id = 0; + } +} + + +void +schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p) +{ + if (si->timer_id) + { + if (verbose_p) + fprintf (stderr, "%s: idle_timer already running\n", blurb()); + return; + } + + /* Wake up periodically to ask the server if we are idle. */ + si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer, + (XtPointer) si); + + if (verbose_p) + fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n", + blurb(), when, si->timer_id); +} + + +static void +notice_events (saver_info *si, Window window, Bool top_p) +{ + saver_preferences *p = &si->prefs; + XWindowAttributes attrs; + unsigned long events; + Window root, parent, *kids; + unsigned int nkids; + int screen_no; + + if (XtWindowToWidget (si->dpy, window)) + /* If it's one of ours, don't mess up its event mask. */ + return; + + if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids)) + return; + if (window == root) + top_p = False; + + /* Figure out which screen this window is on, for the diagnostics. */ + for (screen_no = 0; screen_no < si->nscreens; screen_no++) + if (root == RootWindowOfScreen (si->screens[screen_no].screen)) + break; + + XGetWindowAttributes (si->dpy, window, &attrs); + events = ((attrs.all_event_masks | attrs.do_not_propagate_mask) + & KeyPressMask); + + /* Select for SubstructureNotify on all windows. + Select for KeyPress on all windows that already have it selected. + + Note that we can't select for ButtonPress, because of X braindamage: + only one client at a time may select for ButtonPress on a given + window, though any number can select for KeyPress. Someone explain + *that* to me. + + So, if the user spends a while clicking the mouse without ever moving + the mouse or touching the keyboard, we won't know that they've been + active, and the screensaver will come on. That sucks, but I don't + know how to get around it. + + Since X presents mouse wheels as clicks, this applies to those, too: + scrolling through a document using only the mouse wheel doesn't + count as activity... Fortunately, /proc/interrupts helps, on + systems that have it. Oh, if it's a PS/2 mouse, not serial or USB. + This sucks! + */ + XSelectInput (si->dpy, window, SubstructureNotifyMask | events); + + if (top_p && p->debug_p && (events & KeyPressMask)) + { + /* Only mention one window per tree (hack hack). */ + fprintf (stderr, "%s: %d: selected KeyPress on 0x%lX\n", + blurb(), screen_no, (unsigned long) window); + top_p = False; + } + + if (kids) + { + while (nkids) + notice_events (si, kids [--nkids], top_p); + XFree ((char *) kids); + } +} + + +int +BadWindow_ehandler (Display *dpy, XErrorEvent *error) +{ + /* When we notice a window being created, we spawn a timer that waits + 30 seconds or so, and then selects events on that window. This error + handler is used so that we can cope with the fact that the window + may have been destroyed <30 seconds after it was created. + */ + if (error->error_code == BadWindow || + error->error_code == BadMatch || + error->error_code == BadDrawable) + return 0; + else + return saver_ehandler (dpy, error); +} + + +struct notice_events_timer_arg { + saver_info *si; + Window w; +}; + +static void +notice_events_timer (XtPointer closure, XtIntervalId *id) +{ + struct notice_events_timer_arg *arg = + (struct notice_events_timer_arg *) closure; + + XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler); + + saver_info *si = arg->si; + Window window = arg->w; + + free(arg); + notice_events (si, window, True); + XSync (si->dpy, False); + XSetErrorHandler (old_handler); +} + +void +start_notice_events_timer (saver_info *si, Window w, Bool verbose_p) +{ + saver_preferences *p = &si->prefs; + struct notice_events_timer_arg *arg = + (struct notice_events_timer_arg *) malloc(sizeof(*arg)); + arg->si = si; + arg->w = w; + XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer, + (XtPointer) arg); + + if (verbose_p) + fprintf (stderr, "%s: starting notice_events_timer for 0x%X (%lu)\n", + blurb(), (unsigned int) w, p->notice_events_timeout); +} + + +/* When the screensaver is active, this timer will periodically change + the running program. + */ +void +cycle_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; + Time how_long = p->cycle; + + if (si->selection_mode > 0 && + screenhack_running_p (si)) + /* If we're in "SELECT n" mode, the cycle timer going off will just + restart this same hack again. There's not much point in doing this + every 5 or 10 minutes, but on the other hand, leaving one hack running + for days is probably not a great idea, since they tend to leak and/or + crash. So, restart the thing once an hour. */ + how_long = 1000 * 60 * 60; + + if (si->dbox_up_p) + { + if (p->verbose_p) + fprintf (stderr, "%s: dialog box up; delaying hack change.\n", + blurb()); + how_long = 30000; /* 30 secs */ + } + else + { + int i; + maybe_reload_init_file (si); + for (i = 0; i < si->nscreens; i++) + kill_screenhack (&si->screens[i]); + + raise_window (si, True, True, False); + + if (!si->throttled_p) + for (i = 0; i < si->nscreens; i++) + spawn_screenhack (&si->screens[i]); + else + { + if (p->verbose_p) + fprintf (stderr, "%s: not launching new hack (throttled.)\n", + blurb()); + } + } + + if (how_long > 0) + { + si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer, + (XtPointer) si); + + if (p->debug_p) + fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n", + blurb(), how_long, si->cycle_id); + } + else + { + if (p->debug_p) + fprintf (stderr, "%s: not starting cycle_timer: how_long == %ld\n", + blurb(), (unsigned long) how_long); + } +} + + +void +activate_lock_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; + + if (p->verbose_p) + fprintf (stderr, "%s: timed out; activating lock.\n", blurb()); + set_locked_p (si, True); +} + + +/* Call this when user activity (or "simulated" activity) has been noticed. + */ +void +reset_timers (saver_info *si) +{ + saver_preferences *p = &si->prefs; + if (si->using_mit_saver_extension || si->using_sgi_saver_extension) + return; + + if (si->timer_id) + { + if (p->debug_p) + fprintf (stderr, "%s: killing idle_timer (%ld, %ld)\n", + blurb(), p->timeout, si->timer_id); + XtRemoveTimeOut (si->timer_id); + si->timer_id = 0; + } + + schedule_wakeup_event (si, p->timeout, p->debug_p); /* sets si->timer_id */ + + if (si->cycle_id) abort (); /* no cycle timer when inactive */ + + si->last_activity_time = time ((time_t *) 0); + + /* This will (hopefully, supposedly) tell the server to re-set its + DPMS timer. Without this, the -deactivate clientmessage would + prevent xscreensaver from blanking, but would not prevent the + monitor from powering down. */ +#if 0 + /* #### With some servers, this causes the screen to flicker every + time a key is pressed! Ok, I surrender. I give up on ever + having DPMS work properly. + */ + XForceScreenSaver (si->dpy, ScreenSaverReset); + + /* And if the monitor is already powered off, turn it on. + You'd think the above would do that, but apparently not? */ + monitor_power_on (si); +#endif + +} + + +/* Returns true if the mouse has moved since the last time we checked. + Small motions (of less than "hysteresis" pixels/second) are ignored. + */ +static Bool +pointer_moved_p (saver_screen_info *ssi, Bool mods_p) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + + Window root, child; + int root_x, root_y, x, y; + unsigned int mask; + time_t now = time ((time_t *) 0); + unsigned int distance, dps; + unsigned long seconds = 0; + Bool moved_p = False; + + /* don't check xinerama pseudo-screens. */ + if (!ssi->real_screen_p) return False; + + if (!XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child, + &root_x, &root_y, &x, &y, &mask)) + { + /* If XQueryPointer() returns false, the mouse is not on this screen. + */ + x = root_x = -1; + y = root_y = -1; + root = child = 0; + mask = 0; + } + + distance = MAX (ABS (ssi->poll_mouse_last_root_x - root_x), + ABS (ssi->poll_mouse_last_root_y - root_y)); + seconds = (now - ssi->poll_mouse_last_time); + + + /* When the screen is blanked, we get MotionNotify events, but when not + blanked, we poll only every 5 seconds, and that's not enough resolution + to do hysteresis based on a 1 second interval. So, assume that any + motion we've seen during the 5 seconds when our eyes were closed happened + in the last 1 second instead. + */ + if (seconds > 1) seconds = 1; + + dps = (seconds <= 0 ? distance : (distance / seconds)); + + /* Motion only counts if the rate is more than N pixels per second. + */ + if (dps >= p->pointer_hysteresis && + distance > 0) + moved_p = True; + + /* If the mouse is not on this screen but used to be, that's motion. + If the mouse was not on this screen, but is now, that's motion. + */ + { + Bool on_screen_p = (root_x != -1 && root_y != -1); + Bool was_on_screen_p = (ssi->poll_mouse_last_root_x != -1 && + ssi->poll_mouse_last_root_y != -1); + + if (on_screen_p != was_on_screen_p) + moved_p = True; + } + + if (p->debug_p && (distance != 0 || moved_p)) + { + fprintf (stderr, "%s: %d: pointer %s", blurb(), ssi->number, + (moved_p ? "moved: " : "ignored:")); + if (ssi->poll_mouse_last_root_x == -1) + fprintf (stderr, "off screen"); + else + fprintf (stderr, "%d,%d", + ssi->poll_mouse_last_root_x, + ssi->poll_mouse_last_root_y); + fprintf (stderr, " -> "); + if (root_x == -1) + fprintf (stderr, "off screen"); + else + fprintf (stderr, "%d,%d", root_x, root_y); + if (ssi->poll_mouse_last_root_x != -1 && root_x != -1) + fprintf (stderr, " (%d,%d; %d/%lu=%d)", + ABS(ssi->poll_mouse_last_root_x - root_x), + ABS(ssi->poll_mouse_last_root_y - root_y), + distance, seconds, dps); + + fprintf (stderr, ".\n"); + } + + if (!moved_p && + mods_p && + mask != ssi->poll_mouse_last_mask) + { + moved_p = True; + + if (p->debug_p) + fprintf (stderr, "%s: %d: modifiers changed: 0x%04x -> 0x%04x.\n", + blurb(), ssi->number, ssi->poll_mouse_last_mask, mask); + } + + si->last_activity_screen = ssi; + ssi->poll_mouse_last_child = child; + ssi->poll_mouse_last_mask = mask; + + if (moved_p || seconds > 0) + { + ssi->poll_mouse_last_time = now; + ssi->poll_mouse_last_root_x = root_x; + ssi->poll_mouse_last_root_y = root_y; + } + + return moved_p; +} + + +/* When we aren't using a server extension, this timer is used to periodically + wake up and poll the mouse position, which is possibly more reliable than + selecting motion events on every window. + */ +static void +check_pointer_timer (XtPointer closure, XtIntervalId *id) +{ + int i; + saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; + Bool active_p = False; + + if (!si->using_proc_interrupts && + (si->using_xidle_extension || + si->using_mit_saver_extension || + si->using_sgi_saver_extension)) + /* If an extension is in use, we should not be polling the mouse. + Unless we're also checking /proc/interrupts, in which case, we should. + */ + abort (); + + if (id && *id == si->check_pointer_timer_id) /* this is us - it's expired */ + si->check_pointer_timer_id = 0; + + if (si->check_pointer_timer_id) /* only queue one at a time */ + XtRemoveTimeOut (si->check_pointer_timer_id); + + si->check_pointer_timer_id = /* now re-queue */ + XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer, + (XtPointer) si); + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (pointer_moved_p (ssi, True)) + active_p = True; + } + +#ifdef HAVE_PROC_INTERRUPTS + if (!active_p && + si->using_proc_interrupts && + proc_interrupts_activity_p (si)) + { + active_p = True; + } +#endif /* HAVE_PROC_INTERRUPTS */ + + if (active_p) + reset_timers (si); + + check_for_clock_skew (si); +} + + +/* An unfortunate situation is this: the saver is not active, because the + user has been typing. The machine is a laptop. The user closes the lid + and suspends it. The CPU halts. Some hours later, the user opens the + lid. At this point, Xt's timers will fire, and xscreensaver will blank + the screen. + + So far so good -- well, not really, but it's the best that we can do, + since the OS doesn't send us a signal *before* shutdown -- but if the + user had delayed locking (lockTimeout > 0) then we should start off + in the locked state, rather than only locking N minutes from when the + lid was opened. Also, eschewing fading is probably a good idea, to + clamp down as soon as possible. + + We only do this when we'd be polling the mouse position anyway. + This amounts to an assumption that machines with APM support also + have /proc/interrupts. + */ +static void +check_for_clock_skew (saver_info *si) +{ + saver_preferences *p = &si->prefs; + time_t now = time ((time_t *) 0); + long shift = now - si->last_wall_clock_time; + + if (p->debug_p) + { + int i = (si->last_wall_clock_time == 0 ? 0 : shift); + fprintf (stderr, + "%s: checking wall clock for hibernation (%d:%02d:%02d).\n", + blurb(), + (i / (60 * 60)), ((i / 60) % 60), (i % 60)); + } + + if (si->last_wall_clock_time != 0 && + shift > (p->timeout / 1000)) + { + if (p->verbose_p) + fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld!\n", + blurb(), + (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60)); + + si->emergency_lock_p = True; + idle_timer ((XtPointer) si, 0); + } + + si->last_wall_clock_time = now; +} + + + +static void +dispatch_event (saver_info *si, XEvent *event) +{ + /* If this is for the splash dialog, pass it along. + Note that the password dialog is handled with its own event loop, + so events for that window will never come through here. + */ + if (si->splash_dialog && event->xany.window == si->splash_dialog) + handle_splash_event (si, event); + + XtDispatchEvent (event); +} + + +static void +swallow_unlock_typeahead_events (saver_info *si, XEvent *e) +{ + XEvent event; + char buf [100]; + int i = 0; + + memset (buf, 0, sizeof(buf)); + + event = *e; + + do + { + if (event.xany.type == KeyPress) + { + char s[2]; + int size = XLookupString ((XKeyEvent *) &event, s, 1, 0, 0); + if (size != 1) continue; + switch (*s) + { + case '\010': case '\177': /* Backspace */ + if (i > 0) i--; + break; + case '\025': case '\030': /* Erase line */ + case '\012': case '\015': /* Enter */ + case '\033': /* ESC */ + i = 0; + break; + case '\040': /* Space */ + if (i == 0) + break; /* ignore space at beginning of line */ + /* else, fall through */ + default: + buf [i++] = *s; + break; + } + } + + } while (i < sizeof(buf)-1 && + XCheckMaskEvent (si->dpy, KeyPressMask, &event)); + + buf[i] = 0; + + if (si->unlock_typeahead) + { + memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead)); + free (si->unlock_typeahead); + } + + if (i > 0) + si->unlock_typeahead = strdup (buf); + else + si->unlock_typeahead = 0; + + memset (buf, 0, sizeof(buf)); +} + + +/* methods of detecting idleness: + + explicitly informed by SGI SCREEN_SAVER server event; + explicitly informed by MIT-SCREEN-SAVER server event; + poll server idle time with XIDLE extension; + select events on all windows, and note absence of recent events; + note that /proc/interrupts has not changed in a while; + activated by clientmessage. + + methods of detecting non-idleness: + + read events on the xscreensaver window; + explicitly informed by SGI SCREEN_SAVER server event; + explicitly informed by MIT-SCREEN-SAVER server event; + select events on all windows, and note events on any of them; + note that /proc/interrupts has changed; + deactivated by clientmessage. + + I trust that explains why this function is a big hairy mess. + */ +void +sleep_until_idle (saver_info *si, Bool until_idle_p) +{ + saver_preferences *p = &si->prefs; + + /* We have to go through this union bullshit because gcc-4.4.0 has + stricter struct-aliasing rules. Without this, the optimizer + can fuck things up. + */ + union { + XEvent x_event; +# ifdef HAVE_RANDR + XRRScreenChangeNotifyEvent xrr_event; +# endif /* HAVE_RANDR */ +# ifdef HAVE_MIT_SAVER_EXTENSION + XScreenSaverNotifyEvent sevent; +# endif /* HAVE_MIT_SAVER_EXTENSION */ + } event; + + /* We need to select events on all windows if we're not using any extensions. + Otherwise, we don't need to. */ + Bool scanning_all_windows = !(si->using_xidle_extension || + si->using_mit_saver_extension || + si->using_sgi_saver_extension); + + /* We need to periodically wake up and check for idleness if we're not using + any extensions, or if we're using the XIDLE extension. The other two + extensions explicitly deliver events when we go idle/non-idle, so we + don't need to poll. */ + Bool polling_for_idleness = !(si->using_mit_saver_extension || + si->using_sgi_saver_extension); + + /* Whether we need to periodically wake up and check to see if the mouse has + moved. We only need to do this when not using any extensions. The reason + this isn't the same as `polling_for_idleness' is that the "idleness" poll + can happen (for example) 5 minutes from now, whereas the mouse-position + poll should happen with low periodicity. We don't need to poll the mouse + position with the XIDLE extension, but we do need to periodically wake up + and query the server with that extension. For our purposes, polling + /proc/interrupts is just like polling the mouse position. It has to + happen on the same kind of schedule. */ + Bool polling_mouse_position = (si->using_proc_interrupts || + !(si->using_xidle_extension || + si->using_mit_saver_extension || + si->using_sgi_saver_extension)); + + if (until_idle_p) + { + if (polling_for_idleness) + /* This causes a no-op event to be delivered to us in a while, so that + we come back around through the event loop again. */ + schedule_wakeup_event (si, p->timeout, p->debug_p); + + if (polling_mouse_position) + /* Check to see if the mouse has moved, and set up a repeating timer + to do so periodically (typically, every 5 seconds.) */ + check_pointer_timer ((XtPointer) si, 0); + } + + while (1) + { + XtAppNextEvent (si->app, &event.x_event); + + switch (event.x_event.xany.type) { + case 0: /* our synthetic "timeout" event has been signalled */ + if (until_idle_p) + { + Time idle; + + /* We may be idle; check one last time to see if the mouse has + moved, just in case the idle-timer went off within the 5 second + window between mouse polling. If the mouse has moved, then + check_pointer_timer() will reset last_activity_time. + */ + if (polling_mouse_position) + check_pointer_timer ((XtPointer) si, 0); + +#ifdef HAVE_XIDLE_EXTENSION + if (si->using_xidle_extension) + { + /* The XIDLE extension uses the synthetic event to prod us into + re-asking the server how long the user has been idle. */ + if (! XGetIdleTime (si->dpy, &idle)) + { + fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb()); + saver_exit (si, 1, 0); + } + } + else +#endif /* HAVE_XIDLE_EXTENSION */ +#ifdef HAVE_MIT_SAVER_EXTENSION + if (si->using_mit_saver_extension) + { + /* We don't need to do anything in this case - the synthetic + event isn't necessary, as we get sent specific events + to wake us up. In fact, this event generally shouldn't + be being delivered when the MIT extension is in use. */ + idle = 0; + } + else +#endif /* HAVE_MIT_SAVER_EXTENSION */ +#ifdef HAVE_SGI_SAVER_EXTENSION + if (si->using_sgi_saver_extension) + { + /* We don't need to do anything in this case - the synthetic + event isn't necessary, as we get sent specific events + to wake us up. In fact, this event generally shouldn't + be being delivered when the SGI extension is in use. */ + idle = 0; + } + else +#endif /* HAVE_SGI_SAVER_EXTENSION */ + { + /* Otherwise, no server extension is in use. The synthetic + event was to tell us to wake up and see if the user is now + idle. Compute the amount of idle time by comparing the + `last_activity_time' to the wall clock. The l_a_t was set + by calling `reset_timers()', which is called only in only + two situations: when polling the mouse position has revealed + the the mouse has moved (user activity) or when we have read + an event (again, user activity.) + */ + idle = 1000 * (si->last_activity_time - time ((time_t *) 0)); + } + + if (idle >= p->timeout) + { + /* Look, we've been idle long enough. We're done. */ + goto DONE; + } + else if (si->emergency_lock_p) + { + /* Oops, the wall clock has jumped far into the future, so + we need to lock down in a hurry! */ + goto DONE; + } + else + { + /* The event went off, but it turns out that the user has not + yet been idle for long enough. So re-signal the event. + Be economical: if we should blank after 5 minutes, and the + user has been idle for 2 minutes, then set this timer to + go off in 3 minutes. + */ + if (polling_for_idleness) + schedule_wakeup_event (si, p->timeout - idle, p->debug_p); + } + } + break; + + case ClientMessage: + if (handle_clientmessage (si, &event.x_event, until_idle_p)) + goto DONE; + break; + + case CreateNotify: + /* A window has been created on the screen somewhere. If we're + supposed to scan all windows for events, prepare this window. */ + if (scanning_all_windows) + { + Window w = event.x_event.xcreatewindow.window; + start_notice_events_timer (si, w, p->debug_p); + } + break; + + case KeyPress: + case ButtonPress: + /* Ignore release events so that hitting ESC at the password dialog + doesn't result in the password dialog coming right back again when + the fucking release key is seen! */ + /* case KeyRelease:*/ + /* case ButtonRelease:*/ + case MotionNotify: + + if (p->debug_p) + { + Window root=0, window=0; + int x=-1, y=-1; + const char *type = 0; + if (event.x_event.xany.type == MotionNotify) + { + /*type = "MotionNotify";*/ + root = event.x_event.xmotion.root; + window = event.x_event.xmotion.window; + x = event.x_event.xmotion.x_root; + y = event.x_event.xmotion.y_root; + } + else if (event.x_event.xany.type == KeyPress) + { + type = "KeyPress"; + root = event.x_event.xkey.root; + window = event.x_event.xkey.window; + x = y = -1; + } + else if (event.x_event.xany.type == ButtonPress) + { + type = "ButtonPress"; + root = event.x_event.xkey.root; + window = event.x_event.xkey.window; + x = event.x_event.xmotion.x_root; + y = event.x_event.xmotion.y_root; + } + + if (type) + { + int i; + for (i = 0; i < si->nscreens; i++) + if (root == RootWindowOfScreen (si->screens[i].screen)) + break; + fprintf (stderr,"%s: %d: %s on 0x%lx", + blurb(), i, type, (unsigned long) window); + + /* Be careful never to do this unless in -debug mode, as + this could expose characters from the unlock password. */ + if (p->debug_p && event.x_event.xany.type == KeyPress) + { + KeySym keysym; + char c = 0; + XLookupString (&event.x_event.xkey, &c, 1, &keysym, 0); + fprintf (stderr, " (%s%s)", + (event.x_event.xkey.send_event ? "synthetic " : ""), + XKeysymToString (keysym)); + } + + if (x == -1) + fprintf (stderr, "\n"); + else + fprintf (stderr, " at %d,%d.\n", x, y); + } + } + + /* If any widgets want to handle this event, let them. */ + dispatch_event (si, &event.x_event); + + + /* If we got a MotionNotify event, figure out what screen it + was on and poll the mouse there: if the mouse hasn't moved + far enough to count as "real" motion, then ignore this + event. + */ + if (event.x_event.xany.type == MotionNotify) + { + int i; + for (i = 0; i < si->nscreens; i++) + if (event.x_event.xmotion.root == + RootWindowOfScreen (si->screens[i].screen)) + break; + if (i < si->nscreens) + { + if (!pointer_moved_p (&si->screens[i], False)) + continue; + } + } + + + /* We got a user event. + If we're waiting for the user to become active, this is it. + If we're waiting until the user becomes idle, reset the timers + (since now we have longer to wait.) + */ + if (!until_idle_p) + { + if (si->demoing_p && + (event.x_event.xany.type == MotionNotify || + event.x_event.xany.type == KeyRelease)) + /* When we're demoing a single hack, mouse motion doesn't + cause deactivation. Only clicks and keypresses do. */ + ; + else + /* If we're not demoing, then any activity causes deactivation. + */ + goto DONE; + } + else + reset_timers (si); + + break; + + default: + +#ifdef HAVE_MIT_SAVER_EXTENSION + if (event.x_event.type == si->mit_saver_ext_event_number) + { + /* This event's number is that of the MIT-SCREEN-SAVER server + extension. This extension has one event number, and the event + itself contains sub-codes that say what kind of event it was + (an "idle" or "not-idle" event.) + */ + if (event.sevent.state == ScreenSaverOn) + { + int i = 0; + if (p->verbose_p) + fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n", + blurb()); + + /* Get the "real" server window(s) out of the way as soon + as possible. */ + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->server_mit_saver_window && + window_exists_p (si->dpy, + ssi->server_mit_saver_window)) + XUnmapWindow (si->dpy, ssi->server_mit_saver_window); + } + + if (event.sevent.kind != ScreenSaverExternal) + { + fprintf (stderr, + "%s: ScreenSaverOn event wasn't of type External!\n", + blurb()); + } + + if (until_idle_p) + goto DONE; + } + else if (event.sevent.state == ScreenSaverOff) + { + if (p->verbose_p) + fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n", + blurb()); + if (!until_idle_p) + goto DONE; + } + else + fprintf (stderr, + "%s: unknown MIT-SCREEN-SAVER event %d received!\n", + blurb(), event.sevent.state); + } + else + +#endif /* HAVE_MIT_SAVER_EXTENSION */ + + +#ifdef HAVE_SGI_SAVER_EXTENSION + if (event.x_event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart)) + { + /* The SGI SCREEN_SAVER server extension has two event numbers, + and this event matches the "idle" event. */ + if (p->verbose_p) + fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n", + blurb()); + + if (until_idle_p) + goto DONE; + } + else if (event.x_event.type == (si->sgi_saver_ext_event_number + + ScreenSaverEnd)) + { + /* The SGI SCREEN_SAVER server extension has two event numbers, + and this event matches the "idle" event. */ + if (p->verbose_p) + fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n", + blurb()); + if (!until_idle_p) + goto DONE; + } + else +#endif /* HAVE_SGI_SAVER_EXTENSION */ + +#ifdef HAVE_RANDR + if (event.x_event.type == (si->randr_event_number + RRScreenChangeNotify)) + { + /* The Resize and Rotate extension sends an event when the + size, rotation, or refresh rate of any screen has changed. + */ + if (p->verbose_p) + { + /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */ + int screen = XRRRootToScreen (si->dpy, event.xrr_event.window); + fprintf (stderr, "%s: %d: screen change event received\n", + blurb(), screen); + } + +# ifdef RRScreenChangeNotifyMask + /* Inform Xlib that it's ok to update its data structures. */ + XRRUpdateConfiguration (&event.x_event); /* Xrandr.h 1.9, 2002/09/29 */ +# endif /* RRScreenChangeNotifyMask */ + + /* Resize the existing xscreensaver windows and cached ssi data. */ + if (update_screen_layout (si)) + { + if (p->verbose_p) + { + fprintf (stderr, "%s: new layout:\n", blurb()); + describe_monitor_layout (si); + } + resize_screensaver_window (si); + } + } + else +#endif /* HAVE_RANDR */ + + /* Just some random event. Let the Widgets handle it, if desired. */ + dispatch_event (si, &event.x_event); + } + } + DONE: + + + /* If there's a user event on the queue, swallow it. + If we're using a server extension, and the user becomes active, we + get the extension event before the user event -- so the keypress or + motion or whatever is still on the queue. This makes "unfade" not + work, because it sees that event, and bugs out. (This problem + doesn't exhibit itself without an extension, because in that case, + there's only one event generated by user activity, not two.) + */ + if (!until_idle_p && si->locked_p) + swallow_unlock_typeahead_events (si, &event.x_event); + else + while (XCheckMaskEvent (si->dpy, + (KeyPressMask|ButtonPressMask|PointerMotionMask), + &event.x_event)) + ; + + + if (si->check_pointer_timer_id) + { + XtRemoveTimeOut (si->check_pointer_timer_id); + si->check_pointer_timer_id = 0; + } + if (si->timer_id) + { + XtRemoveTimeOut (si->timer_id); + si->timer_id = 0; + } + + if (until_idle_p && si->cycle_id) /* no cycle timer when inactive */ + abort (); +} + + + +/* Some crap for dealing with /proc/interrupts. + + On Linux systems, it's possible to see the hardware interrupt count + associated with the keyboard. We can therefore use that as another method + of detecting idleness. + + Why is it a good idea to do this? Because it lets us detect keyboard + activity that is not associated with X events. For example, if the user + has switched to another virtual console, it's good for xscreensaver to not + be running graphics hacks on the (non-visible) X display. The common + complaint that checking /proc/interrupts addresses is that the user is + playing Quake on a non-X console, and the GL hacks are perceptibly slowing + the game... + + This is tricky for a number of reasons. + + * First, we must be sure to only do this when running on an X server that + is on the local machine (because otherwise, we'd be reacting to the + wrong keyboard.) The way we do this is by noting that the $DISPLAY is + pointing to display 0 on the local machine. It *could* be that display + 1 is also on the local machine (e.g., two X servers, each on a different + virtual-terminal) but it's also possible that screen 1 is an X terminal, + using this machine as the host. So we can't take that chance. + + * Second, one can only access these interrupt numbers in a completely + and utterly brain-damaged way. You would think that one would use an + ioctl for this. But no. The ONLY way to get this information is to + open the pseudo-file /proc/interrupts AS A FILE, and read the numbers + out of it TEXTUALLY. Because this is Unix, and all the world's a file, + and the only real data type is the short-line sequence of ASCII bytes. + + Now it's all well and good that the /proc/interrupts pseudo-file + exists; that's a clever idea, and a useful API for things that are + already textually oriented, like shell scripts, and users doing + interactive debugging sessions. But to make a *C PROGRAM* open a file + and parse the textual representation of integers out of it is just + insane. + + * Third, you can't just hold the file open, and fseek() back to the + beginning to get updated data! If you do that, the data never changes. + And I don't want to call open() every five seconds, because I don't want + to risk going to disk for any inodes. It turns out that if you dup() + it early, then each copy gets fresh data, so we can get around that in + this way (but for how many releases, one might wonder?) + + * Fourth, the format of the output of the /proc/interrupts file is + undocumented, and has changed several times already! In Linux 2.0.33, + even on a multiprocessor machine, it looks like this: + + 0: 309453991 timer + 1: 4771729 keyboard + + but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this: + + CPU0 CPU1 + 0: 1671450 1672618 IO-APIC-edge timer + 1: 13037 13495 IO-APIC-edge keyboard + + and in Linux 2.6, it's gotten even goofier: now there are two lines + labelled "i8042". One of them is the keyboard, and one of them is + the PS/2 mouse -- and of course, you can't tell them apart, except + by wiggling the mouse and noting which one changes: + + CPU0 CPU1 + 1: 32051 30864 IO-APIC-edge i8042 + 12: 476577 479913 IO-APIC-edge i8042 + + Joy! So how are we expected to parse that? Well, this code doesn't + parse it: it saves the first line with the string "keyboard" (or + "i8042") in it, and does a string-comparison to note when it has + changed. If there are two "i8042" lines, we assume the first is + the keyboard and the second is the mouse (doesn't matter which is + which, really, as long as we don't compare them against each other.) + + Thanks to Nat Friedman for figuring out most of this crap. + + Note that if you have a serial or USB mouse, or a USB keyboard, it won't + detect it. That's because there's no way to tell the difference between a + serial mouse and a general serial port, and all USB devices look the same + from here. It would be somewhat unfortunate to have the screensaver turn + off when the modem on COM1 burped, or when a USB disk was accessed. + */ + + +#ifdef HAVE_PROC_INTERRUPTS + +#define PROC_INTERRUPTS "/proc/interrupts" + +Bool +query_proc_interrupts_available (saver_info *si, const char **why) +{ + /* We can use /proc/interrupts if $DISPLAY points to :0, and if the + "/proc/interrupts" file exists and is readable. + */ + FILE *f; + if (why) *why = 0; + + if (!display_is_on_console_p (si)) + { + if (why) *why = "not on primary console"; + return False; + } + + f = fopen (PROC_INTERRUPTS, "r"); + if (!f) + { + if (why) *why = "does not exist"; + return False; + } + + fclose (f); + return True; +} + + +static Bool +proc_interrupts_activity_p (saver_info *si) +{ + static FILE *f0 = 0; + FILE *f1 = 0; + int fd; + static char last_kbd_line[255] = { 0, }; + static char last_ptr_line[255] = { 0, }; + char new_line[sizeof(last_kbd_line)]; + Bool checked_kbd = False, kbd_changed = False; + Bool checked_ptr = False, ptr_changed = False; + int i8042_count = 0; + + if (!f0) + { + /* First time -- open the file. */ + f0 = fopen (PROC_INTERRUPTS, "r"); + if (!f0) + { + char buf[255]; + sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS); + perror (buf); + goto FAIL; + } + } + + if (f0 == (FILE *) -1) /* means we got an error initializing. */ + return False; + + fd = dup (fileno (f0)); + if (fd < 0) + { + char buf[255]; + sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS); + perror (buf); + goto FAIL; + } + + f1 = fdopen (fd, "r"); + if (!f1) + { + char buf[255]; + sprintf(buf, "%s: could not fdopen() the %s fd", blurb(), + PROC_INTERRUPTS); + perror (buf); + goto FAIL; + } + + /* Actually, I'm unclear on why this fseek() is necessary, given the timing + of the dup() above, but it is. */ + if (fseek (f1, 0, SEEK_SET) != 0) + { + char buf[255]; + sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS); + perror (buf); + goto FAIL; + } + + /* Now read through the pseudo-file until we find the "keyboard", + "PS/2 mouse", or "i8042" lines. */ + + while (fgets (new_line, sizeof(new_line)-1, f1)) + { + Bool i8042_p = !!strstr (new_line, "i8042"); + if (i8042_p) i8042_count++; + + if (strchr (new_line, ',')) + { + /* Ignore any line that has a comma on it: this is because + a setup like this: + + 12: 930935 XT-PIC usb-uhci, PS/2 Mouse + + is really bad news. It *looks* like we can note mouse + activity from that line, but really, that interrupt gets + fired any time any USB device has activity! So we have + to ignore any shared IRQs. + */ + } + else if (!checked_kbd && + (strstr (new_line, "keyboard") || + (i8042_p && i8042_count == 1))) + { + /* Assume the keyboard interrupt is the line that says "keyboard", + or the *first* line that says "i8042". + */ + kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line)); + strcpy (last_kbd_line, new_line); + checked_kbd = True; + } + else if (!checked_ptr && + (strstr (new_line, "PS/2 Mouse") || + (i8042_p && i8042_count == 2))) + { + /* Assume the mouse interrupt is the line that says "PS/2 mouse", + or the *second* line that says "i8042". + */ + ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line)); + strcpy (last_ptr_line, new_line); + checked_ptr = True; + } + + if (checked_kbd && checked_ptr) + break; + } + + if (checked_kbd || checked_ptr) + { + fclose (f1); + + if (si->prefs.debug_p && (kbd_changed || ptr_changed)) + fprintf (stderr, "%s: /proc/interrupts activity: %s\n", + blurb(), + ((kbd_changed && ptr_changed) ? "mouse and kbd" : + kbd_changed ? "kbd" : + ptr_changed ? "mouse" : "ERR")); + + return (kbd_changed || ptr_changed); + } + + + /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse" + line in the file at all. */ + fprintf (stderr, "%s: no keyboard or mouse data in %s?\n", + blurb(), PROC_INTERRUPTS); + + FAIL: + if (f1) + fclose (f1); + + if (f0 && f0 != (FILE *) -1) + fclose (f0); + + f0 = (FILE *) -1; + return False; +} + +#endif /* HAVE_PROC_INTERRUPTS */ + + +/* This timer goes off every few minutes, whether the user is idle or not, + to try and clean up anything that has gone wrong. + + It calls disable_builtin_screensaver() so that if xset has been used, + or some other program (like xlock) has messed with the XSetScreenSaver() + settings, they will be set back to sensible values (if a server extension + is in use, messing with xlock can cause xscreensaver to never get a wakeup + event, and could cause monitor power-saving to occur, and all manner of + heinousness.) + + If the screen is currently blanked, it raises the window, in case some + other window has been mapped on top of it. + + If the screen is currently blanked, and there is no hack running, it + clears the window, in case there is an error message printed on it (we + don't want the error message to burn in.) + */ + +static void +watchdog_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; + + disable_builtin_screensaver (si, False); + + /* If the DPMS settings on the server have changed, change them back to + what ~/.xscreensaver says they should be. */ + sync_server_dpms_settings (si->dpy, + (p->dpms_enabled_p && + p->mode != DONT_BLANK), + p->dpms_standby / 1000, + p->dpms_suspend / 1000, + p->dpms_off / 1000, + False); + + if (si->screen_blanked_p) + { + Bool running_p = screenhack_running_p (si); + + if (si->dbox_up_p) + { + if (si->prefs.debug_p) + fprintf (stderr, "%s: dialog box is up: not raising screen.\n", + blurb()); + } + else + { + if (si->prefs.debug_p) + fprintf (stderr, "%s: watchdog timer raising %sscreen.\n", + blurb(), (running_p ? "" : "and clearing ")); + + raise_window (si, True, True, running_p); + } + + if (screenhack_running_p (si) && + !monitor_powered_on_p (si)) + { + int i; + if (si->prefs.verbose_p) + fprintf (stderr, + "%s: X says monitor has powered down; " + "killing running hacks.\n", blurb()); + for (i = 0; i < si->nscreens; i++) + kill_screenhack (&si->screens[i]); + } + + /* Re-schedule this timer. The watchdog timer defaults to a bit less + than the hack cycle period, but is never longer than one hour. + */ + si->watchdog_id = 0; + reset_watchdog_timer (si, True); + } +} + + +void +reset_watchdog_timer (saver_info *si, Bool on_p) +{ + saver_preferences *p = &si->prefs; + + if (si->watchdog_id) + { + XtRemoveTimeOut (si->watchdog_id); + si->watchdog_id = 0; + } + + if (on_p && p->watchdog_timeout) + { + si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout, + watchdog_timer, (XtPointer) si); + + if (p->debug_p) + fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n", + blurb(), p->watchdog_timeout, si->watchdog_id); + } +} + + +/* It's possible that a race condition could have led to the saver + window being unexpectedly still mapped. This can happen like so: + + - screen is blanked + - hack is launched + - that hack tries to grab a screen image (it does this by + first unmapping the saver window, then remapping it.) + - hack unmaps window + - hack waits + - user becomes active + - hack re-maps window (*) + - driver kills subprocess + - driver unmaps window (**) + + The race is that (*) might have been sent to the server before + the client process was killed, but, due to scheduling randomness, + might not have been received by the server until after (**). + In other words, (*) and (**) might happen out of order, meaning + the driver will unmap the window, and then after that, the + recently-dead client will re-map it. This leaves the user + locked out (it looks like a desktop, but it's not!) + + To avoid this: after un-blanking the screen, we launch a timer + that wakes up once a second for ten seconds, and makes damned + sure that the window is still unmapped. + */ + +void +de_race_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; + int secs = 1; + + if (id == 0) /* if id is 0, this is the initialization call. */ + { + si->de_race_ticks = 10; + if (p->verbose_p) + fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n", + blurb(), si->de_race_ticks); + } + else + { + int i; + XSync (si->dpy, False); + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + Window w = ssi->screensaver_window; + XWindowAttributes xgwa; + XGetWindowAttributes (si->dpy, w, &xgwa); + if (xgwa.map_state != IsUnmapped) + { + if (p->verbose_p) + fprintf (stderr, + "%s: %d: client race! emergency unmap 0x%lx.\n", + blurb(), i, (unsigned long) w); + XUnmapWindow (si->dpy, w); + } + else if (p->debug_p) + fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n", + blurb(), i, (unsigned long) w); + } + XSync (si->dpy, False); + + si->de_race_ticks--; + } + + if (id && *id == si->de_race_id) + si->de_race_id = 0; + + if (si->de_race_id) abort(); + + if (si->de_race_ticks <= 0) + { + si->de_race_id = 0; + if (p->verbose_p) + fprintf (stderr, "%s: de-race completed.\n", blurb()); + } + else + { + si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000, + de_race_timer, closure); + } +} diff --git a/driver/types.h b/driver/types.h new file mode 100644 index 00000000..63ac39ec --- /dev/null +++ b/driver/types.h @@ -0,0 +1,407 @@ +/* xscreensaver, Copyright (c) 1993-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifndef __XSCREENSAVER_TYPES_H__ +#define __XSCREENSAVER_TYPES_H__ + +typedef struct saver_info saver_info; + +typedef enum { + ul_read, /* reading input or ready to do so */ + ul_success, /* auth success, unlock */ + ul_fail, /* auth fail */ + ul_cancel, /* user cancelled auth (pw_cancel or pw_null) */ + ul_time, /* timed out */ + ul_finished /* user pressed enter */ +} unlock_state; + +typedef struct screenhack screenhack; +struct screenhack { + Bool enabled_p; + char *visual; + char *name; + char *command; +}; + +typedef enum { + RANDOM_HACKS, ONE_HACK, BLANK_ONLY, DONT_BLANK, RANDOM_HACKS_SAME +} saver_mode; + +typedef enum { + TEXT_DATE, TEXT_LITERAL, TEXT_FILE, TEXT_PROGRAM, TEXT_URL +} text_mode; + +struct auth_message; +struct auth_response; + +typedef int (*auth_conv_cb_t) ( + int num_msg, + const struct auth_message *msg, + struct auth_response **resp, + saver_info *si); + +typedef struct saver_preferences saver_preferences; +typedef struct saver_screen_info saver_screen_info; +typedef struct passwd_dialog_data passwd_dialog_data; +typedef struct splash_dialog_data splash_dialog_data; +typedef struct _monitor monitor; + + +/* This structure holds all the user-specified parameters, read from the + command line, the resource database, or entered through a dialog box. + */ +struct saver_preferences { + + XrmDatabase db; /* The resource database into which the + init file is merged, and out of which the + preferences are parsed. */ + + time_t init_file_date; /* The date (from stat()) of the .xscreensaver + file the last time this process read or + wrote it. */ + + Bool verbose_p; /* whether to print out lots of status info */ + Bool timestamp_p; /* whether to mark messages with a timestamp */ + Bool capture_stderr_p; /* whether to redirect stdout/stderr */ + Bool ignore_uninstalled_p; /* whether to avoid displaying or complaining + about hacks that are not on $PATH */ + Bool debug_p; /* pay no mind to the man behind the curtain */ + Bool xsync_p; /* whether XSynchronize has been called */ + + Bool lock_p; /* whether to lock as well as save */ + + Bool fade_p; /* whether to fade to black, if possible */ + Bool unfade_p; /* whether to fade from black, if possible */ + Time fade_seconds; /* how long that should take */ + int fade_ticks; /* how many ticks should be used */ + Bool splash_p; /* whether to do a splash screen at startup */ + + Bool install_cmap_p; /* whether we should use our own colormap + when using the screen's default visual. */ + +# ifdef QUAD_MODE + Bool quad_p; /* whether to run four savers per monitor */ +# endif + + screenhack **screenhacks; /* the programs to run */ + int screenhacks_count; + + saver_mode mode; /* hack-selection mode */ + int selected_hack; /* in one_hack mode, this is the one */ + + int nice_inferior; /* nice value for subprocs */ + int inferior_memory_limit; /* setrlimit(LIMIT_AS) value for subprocs */ + + Time initial_delay; /* how long to sleep after launch */ + Time splash_duration; /* how long the splash screen stays up */ + Time timeout; /* how much idle time before activation */ + Time lock_timeout; /* how long after activation locking starts */ + Time cycle; /* how long each hack should run */ + Time passwd_timeout; /* how much time before pw dialog goes down */ + Time pointer_timeout; /* how often to check mouse position */ + Time notice_events_timeout; /* how long after window creation to select */ + Time watchdog_timeout; /* how often to re-raise and re-blank screen */ + int pointer_hysteresis; /* mouse motions less than N/sec are ignored */ + + Bool dpms_enabled_p; /* Whether to power down the monitor */ + Time dpms_standby; /* how long until monitor goes black */ + Time dpms_suspend; /* how long until monitor power-saves */ + Time dpms_off; /* how long until monitor powers down */ + + Bool grab_desktop_p; /* These are not used by "xscreensaver" */ + Bool grab_video_p; /* itself: they are used by the external */ + Bool random_image_p; /* "xscreensaver-getimage" program, and set */ + char *image_directory; /* by the "xscreensaver-demo" configurator. */ + + text_mode tmode; /* How we generate text to display. */ + char *text_literal; /* used when tmode is TEXT_LITERAL. */ + char *text_file; /* used when tmode is TEXT_FILE. */ + char *text_program; /* used when tmode is TEXT_PROGRAM. */ + char *text_url; /* used when tmode is TEXT_URL. */ + + Bool use_xidle_extension; /* which extension to use, if possible */ + Bool use_mit_saver_extension; + Bool use_sgi_saver_extension; + Bool use_proc_interrupts; + + Bool getviewport_full_of_lies_p; /* XFree86 bug #421 */ + + char *shell; /* where to find /bin/sh */ + + char *demo_command; /* How to enter demo mode. */ + char *prefs_command; /* How to edit preferences. */ + char *help_url; /* Where the help document resides. */ + char *load_url_command; /* How one loads URLs. */ + char *new_login_command; /* Command for the "New Login" button. */ +}; + +/* This structure holds all the data that applies to the program as a whole, + or to the non-screen-specific parts of the display connection. + + The saver_preferences structure (prefs.h) holds all the user-specified + parameters, read from the command line, the resource database, or entered + through a dialog box. + */ +struct saver_info { + char *version; + saver_preferences prefs; + + int nscreens; + int ssi_count; + saver_screen_info *screens; + saver_screen_info *default_screen; /* ...on which dialogs will appear. */ + monitor **monitor_layout; /* private to screens.c */ + Visual **best_gl_visuals; /* visuals for GL hacks on screen N */ + + /* ======================================================================= + global connection info + ======================================================================= */ + + XtAppContext app; + Display *dpy; + + /* ======================================================================= + server extension info + ======================================================================= */ + + Bool using_xidle_extension; /* which extension is being used. */ + Bool using_mit_saver_extension; /* Note that `p->use_*' is the *request*, */ + Bool using_sgi_saver_extension; /* and `si->using_*' is the *reality*. */ + Bool using_proc_interrupts; + +# ifdef HAVE_MIT_SAVER_EXTENSION + int mit_saver_ext_event_number; + int mit_saver_ext_error_number; +# endif +# ifdef HAVE_SGI_SAVER_EXTENSION + int sgi_saver_ext_event_number; + int sgi_saver_ext_error_number; +# endif +# ifdef HAVE_RANDR + int randr_event_number; + int randr_error_number; +# endif + + + /* ======================================================================= + blanking + ======================================================================= */ + + Bool screen_blanked_p; /* Whether the saver is currently active. */ + Window mouse_grab_window; /* Window holding our mouse grab */ + Window keyboard_grab_window; /* Window holding our keyboard grab */ + int mouse_grab_screen; /* The screen number the mouse grab is on */ + int keyboard_grab_screen; /* The screen number the keyboard grab is on */ + Bool fading_possible_p; /* Whether fading to/from black is possible. */ + Bool throttled_p; /* Whether we should temporarily just blank + the screen, not run hacks. (Deprecated: + users should use "xset dpms force off" + instead.) */ + time_t blank_time; /* The time at which the screen was blanked + (if currently blanked) or unblanked (if + not blanked.) */ + + + /* ======================================================================= + locking and runtime privileges + ======================================================================= */ + + Bool locked_p; /* Whether the screen is currently locked. */ + Bool dbox_up_p; /* Whether the demo-mode or passwd dialogs + are currently visible */ + + Bool locking_disabled_p; /* Sometimes locking is impossible. */ + char *nolock_reason; /* This is why. */ + + char *orig_uid; /* What uid/gid we had at startup, before + discarding privileges. */ + char *uid_message; /* Any diagnostics from our attempt to + discard privileges (printed only in + -verbose mode.) */ + Bool dangerous_uid_p; /* Set to true if we're running as a user id + which is known to not be a normal, non- + privileged user. */ + + Window passwd_dialog; /* The password dialog, if it's up. */ + passwd_dialog_data *pw_data; /* Other info necessary to draw it. */ + + int unlock_failures; /* Counts failed login attempts while the + screen is locked. */ + + char *unlock_typeahead; /* If the screen is locked, and the user types + a character, we assume that it is the first + character of the password. It's stored here + for the password dialog to use to populate + itself. */ + + char *user; /* The user whose session is locked. */ + char *cached_passwd; /* Cached password, used to avoid multiple + prompts for password-only auth mechanisms.*/ + unlock_state unlock_state; + + auth_conv_cb_t unlock_cb; /* The function used to prompt for creds. */ + void (*auth_finished_cb) (saver_info *si); + /* Called when authentication has finished, + regardless of success or failure. + May be NULL. */ + + + /* ======================================================================= + demoing + ======================================================================= */ + + Bool demoing_p; /* Whether we are demoing a single hack + (without UI.) */ + + Window splash_dialog; /* The splash dialog, if its up. */ + splash_dialog_data *sp_data; /* Other info necessary to draw it. */ + + + /* ======================================================================= + timers + ======================================================================= */ + + XtIntervalId lock_id; /* Timer to implement `prefs.lock_timeout' */ + XtIntervalId cycle_id; /* Timer to implement `prefs.cycle' */ + XtIntervalId timer_id; /* Timer to implement `prefs.timeout' */ + XtIntervalId watchdog_id; /* Timer to implement `prefs.watchdog */ + XtIntervalId check_pointer_timer_id; /* `prefs.pointer_timeout' */ + + XtIntervalId de_race_id; /* Timer to make sure screen un-blanks */ + int de_race_ticks; + + time_t last_activity_time; /* Used only when no server exts. */ + time_t last_wall_clock_time; /* Used to detect laptop suspend. */ + saver_screen_info *last_activity_screen; + + Bool emergency_lock_p; /* Set when the wall clock has jumped + (presumably due to laptop suspend) and we + need to lock down right away instead of + waiting for the lock timer to go off. */ + + + /* ======================================================================= + remote control + ======================================================================= */ + + int selection_mode; /* Set to -1 if the NEXT ClientMessage has just + been received; set to -2 if PREV has just + been received; set to N if SELECT or DEMO N + has been received. (This is kind of nasty.) + */ + + /* ======================================================================= + subprocs + ======================================================================= */ + + XtIntervalId stderr_popup_timer; + +}; + +/* This structure holds all the data that applies to the screen-specific parts + of the display connection; if the display has multiple screens, there will + be one of these for each screen. + */ +struct saver_screen_info { + saver_info *global; + + int number; /* The internal ordinal of this screen, + counting Xinerama rectangles as separate + screens. */ + int real_screen_number; /* The number of the underlying X screen on + which this rectangle lies. */ + Screen *screen; /* The X screen in question. */ + + int x, y, width, height; /* The size and position of this rectangle + on its underlying X screen. */ + + Bool real_screen_p; /* This will be true of exactly one ssi per + X screen. */ + + Widget toplevel_shell; + + /* ======================================================================= + blanking + ======================================================================= */ + + Window screensaver_window; /* The window that will impersonate the root, + when the screensaver activates. Note that + the window stored here may change, as we + destroy and recreate it on different + visuals. */ + Colormap cmap; /* The colormap that goes with the window. */ + Bool install_cmap_p; /* Whether this screen should have its own + colormap installed, for whichever of several + reasons. This is definitive (even a false + value here overrides prefs->install_cmap_p.) + */ + Visual *current_visual; /* The visual of the window. */ + int current_depth; /* How deep the visual (and the window) are. */ + + Visual *default_visual; /* visual to use when none other specified */ + + Window real_vroot; /* The original virtual-root window. */ + Window real_vroot_value; /* What was in the __SWM_VROOT property. */ + + Cursor cursor; /* A blank cursor that goes with the + real root window. */ + unsigned long black_pixel; /* Black, allocated from `cmap'. */ + + int blank_vp_x, blank_vp_y; /* Where the virtual-scrolling viewport was + when the screen went blank. We need to + prevent the X server from letting the mouse + bump the edges to scroll while the screen + is locked, so we reset to this when it has + moved, and the lock dialog is up... */ + +# ifdef HAVE_MIT_SAVER_EXTENSION + Window server_mit_saver_window; +# endif + + + /* ======================================================================= + demoing + ======================================================================= */ + + Colormap demo_cmap; /* The colormap that goes with the dialogs: + this might be the same as `cmap' so care + must be taken not to free it while it's + still in use. */ + + /* ======================================================================= + timers + ======================================================================= */ + + int poll_mouse_last_root_x; /* Used only when no server exts. */ + int poll_mouse_last_root_y; + Window poll_mouse_last_child; + unsigned int poll_mouse_last_mask; + time_t poll_mouse_last_time; + + + /* ======================================================================= + subprocs + ======================================================================= */ + + int current_hack; /* Index into `prefs.screenhacks' */ + pid_t pid; + + int stderr_text_x; + int stderr_text_y; + int stderr_line_height; + XFontStruct *stderr_font; + GC stderr_gc; + Window stderr_overlay_window; /* Used if the server has overlay planes */ + Colormap stderr_cmap; +}; + + +#endif /* __XSCREENSAVER_TYPES_H__ */ diff --git a/driver/vms-getpwnam.c b/driver/vms-getpwnam.c new file mode 100644 index 00000000..ec0650c9 --- /dev/null +++ b/driver/vms-getpwnam.c @@ -0,0 +1,129 @@ +/* + * getpwnam(name) - retrieves a UAF entry + * + * Author: Patrick L. Mahan + * Location: TGV, Inc + * Date: 15-Nov-1991 + * + * Purpose: Provides emulation for the UNIX getpwname routine. + * + * Modification History + * + * Date | Who | Version | Reason + * ------------+-----------+---------------+--------------------------- + * 15-Nov-1991 | PLM | 1.0 | First Write + */ + +#define PASSWDROUTINES + +#include +#include +#include +#include +#include +#include +#include "vms-pwd.h" + +struct uic { + unsigned short uid; + unsigned short gid; +}; + +#define TEST(ptr, str) { if (ptr == NULL) { \ + fprintf(stderr, "getpwnam: memory allocation failure for \"%s\"\n", \ + str); \ + return ((struct passwd *)(NULL)); \ + } } + +struct passwd *getpwnam(name) +char *name; +{ + int istatus; + int UserNameLen; + int UserOwnerLen; + int UserDeviceLen; + int UserDirLen; + static char UserName[13]; + static char UserOwner[32]; + static char UserDevice[32]; + static char UserDir[64]; + char *cptr, *sptr; + unsigned long int UserPwd[2]; + unsigned short int UserSalt; + unsigned long int UserEncrypt; + struct uic UicValue; + struct passwd *entry; + + struct dsc$descriptor_s VMSNAME = + {strlen(name), DSC$K_DTYPE_T, DSC$K_CLASS_S, name}; + + struct itmlist3 { + unsigned short int length; + unsigned short int item; + unsigned long int addr; + unsigned long int retaddr; + } ItemList[] = { + {12, UAI$_USERNAME, (unsigned long)&UserName, (unsigned long)&UserNameLen}, + {8, UAI$_PWD, (unsigned long)&UserPwd, 0}, + {4, UAI$_UIC, (unsigned long)&UicValue, 0}, + {32, UAI$_OWNER, (unsigned long)&UserOwner, (unsigned long)&UserOwnerLen}, + {32, UAI$_DEFDEV, (unsigned long)&UserDevice, (unsigned long)&UserDeviceLen}, + {64, UAI$_DEFDIR, (unsigned long)&UserDir, (unsigned long)&UserDirLen}, + {2, UAI$_SALT, (unsigned long)&UserSalt, 0}, + {4, UAI$_ENCRYPT, (unsigned long)&UserEncrypt, 0}, + {0, 0, 0, 0} + }; + + UserNameLen = 0; + istatus = sys$getuai (0, 0, &VMSNAME, &ItemList, 0, 0, 0); + + if (!(istatus & 1)) { + fprintf (stderr, "getpwnam: unable to retrieve passwd entry for %s\n", + name); + fprintf (stderr, "getpwnam: vms error number is 0x%x\n", istatus); + return ((struct passwd *)NULL); + } + + entry = (struct passwd *) calloc (1, sizeof(struct passwd)); + TEST(entry, "PASSWD_ENTRY"); + + entry->pw_uid = UicValue.uid; + entry->pw_gid = UicValue.gid; + entry->pw_salt = UserSalt; + entry->pw_encrypt = UserEncrypt; + + sptr = UserName; + cptr = calloc (UserNameLen+1, sizeof(char)); + TEST(cptr, "USERNAME"); + strncpy (cptr, sptr, UserNameLen); + cptr[UserNameLen] = '\0'; + entry->pw_name = cptr; + + cptr = calloc(8, sizeof(char)); + TEST(cptr, "PASSWORD"); + memcpy(cptr, UserPwd, 8); + entry->pw_passwd = cptr; + + sptr = UserOwner; sptr++; + cptr = calloc ((int)UserOwner[0]+1, sizeof(char)); + TEST(cptr, "FULLNAME"); + strncpy (cptr, sptr, (int)UserOwner[0]); + cptr[(int)UserOwner[0]] = '\0'; + entry->pw_gecos = cptr; + + cptr = calloc ((int)UserDevice[0]+(int)UserDir[0]+1, sizeof(char)); + TEST(cptr, "HOME"); + sptr = UserDevice; sptr++; + strncpy (cptr, sptr, (int)UserDevice[0]); + sptr = UserDir; sptr++; + strncat (cptr, sptr, (int)UserDir[0]); + cptr[(int)UserDevice[0]+(int)UserDir[0]] = '\0'; + entry->pw_dir = cptr; + + cptr = calloc (strlen("SYS$SYSTEM:LOGINOUT.EXE")+1, sizeof(char)); + TEST(cptr,"SHELL"); + strcpy (cptr, "SYS$SYSTEM:LOGINOUT.EXE"); + entry->pw_shell = cptr; + + return (entry); +} diff --git a/driver/vms-hpwd.c b/driver/vms-hpwd.c new file mode 100644 index 00000000..707e3ea5 --- /dev/null +++ b/driver/vms-hpwd.c @@ -0,0 +1,75 @@ +/* + * VAX/VMS Password hashing routines: + * + * uses the System Service SYS$HASH_PASSWORD + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + */ + +#include +#include +#include +#include +/* + * Hashing routine + */ +hash_vms_password(output_buf,input_buf,input_length,username,encryption_type,salt) +char *output_buf; +char *input_buf; +int input_length; +char *username; +int encryption_type; +unsigned short salt; +{ + struct dsc$descriptor_s password; + struct dsc$descriptor_s user; + + /* + * Check the VMS Version. If this is V5.4 or later, then + * we can use the new system service SYS$HASH_PASSWORD. Else + * fail and return garbage. + */ + + static char VMS_Version[32]; + struct { + unsigned short int Size; + unsigned short int Code; + char *Buffer; + unsigned short int *Resultant_Size; + } Item_List[2]={32, SYI$_VERSION, VMS_Version, 0, 0, 0}; + struct {int Size; char *Ptr;} Descr1; + + /* + * Get the information + */ + sys$getsyiw(0,0,0,Item_List,0,0,0); + /* + * Call the old routine if this isn't V5.4 or later... + */ +#ifndef __DECC + if ((VMS_Version[1] < '5') || + ((VMS_Version[1] == '5') && (VMS_Version[3] < '4'))) { + printf("Unsupported OS version\n"); + return(1); + } +#endif /* !__DECC */ + /* + * Call the SYS$HASH_PASSWORD system service... + */ + password.dsc$b_dtype = DSC$K_DTYPE_T; + password.dsc$b_class = DSC$K_CLASS_S; + password.dsc$w_length = input_length; + password.dsc$a_pointer = input_buf; + user.dsc$b_dtype = DSC$K_DTYPE_T; + user.dsc$b_class = DSC$K_CLASS_S; + user.dsc$w_length = strlen(username); + user.dsc$a_pointer = username; + sys$hash_password (&password, encryption_type, salt, &user, output_buf); +} diff --git a/driver/vms-pwd.h b/driver/vms-pwd.h new file mode 100644 index 00000000..6cb73d3e --- /dev/null +++ b/driver/vms-pwd.h @@ -0,0 +1,48 @@ +/* @(#)pwd.h 1.7 89/08/24 SMI; from S5R2 1.1 */ + +#ifndef __pwd_h +#define __pwd_h + +#ifdef vax11c +#include +#else +#include +#endif /* vax11c */ + +#ifdef PASSWDROUTINES +#define EXTERN +#else +#define EXTERN extern +#endif /* PASSWDROUTINES */ + +struct passwd { + char *pw_name; + char *pw_passwd; + int pw_uid; + int pw_gid; + short pw_salt; + int pw_encrypt; + char *pw_age; + char *pw_comment; + char *pw_gecos; + char *pw_dir; + char *pw_shell; +}; + + +#ifndef _POSIX_SOURCE +extern struct passwd *getpwent(); + +struct comment { + char *c_dept; + char *c_name; + char *c_acct; + char *c_bin; +}; + +#endif + +EXTERN struct passwd *getpwuid(/* uid_t uid */); +EXTERN struct passwd *getpwnam(/* char *name */); + +#endif /* !__pwd_h */ diff --git a/driver/vms-validate.c b/driver/vms-validate.c new file mode 100644 index 00000000..8f7141d6 --- /dev/null +++ b/driver/vms-validate.c @@ -0,0 +1,75 @@ +/* + * validate a password for a user + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* + * Includes + */ +#include +#include +#include + +#include "vms-pwd.h" +int hash_vms_password(char *output_buf,char *input_buf,int input_length, + char *username,int encryption_type,unsigned short salt); + +/* + * + * Validate a VMS UserName/Password pair. + * + */ + +int validate_user(name,password) +char *name; +char *password; +{ + char password_buf[64]; + char username_buf[31]; + char encrypt_buf[8]; + register int i; + register char *cp,*cp1; + struct passwd *user_entry; + + /* + * Get the users UAF entry + */ + user_entry = getpwnam(name); + + /* + * If user_entry == NULL then we got a bad error + * return -1 to indicate a bad error + */ + if (user_entry == NULL) return (-1); + + /* + * Uppercase the password + */ + cp = password; + cp1 = password_buf; + while (*cp) + if (islower(*cp)) + *cp1++ = toupper(*cp++); + else + *cp1++ = *cp++; + /* + * Get the length of the password + */ + i = strlen(password); + /* + * Encrypt the password + */ + hash_vms_password(encrypt_buf,password_buf,i,user_entry->pw_name, + user_entry->pw_encrypt, user_entry->pw_salt); + if (memcmp(encrypt_buf,user_entry->pw_passwd,8) == 0) + return(1); + else return(0); +} + diff --git a/driver/vms_axp.opt b/driver/vms_axp.opt new file mode 100644 index 00000000..04d465df --- /dev/null +++ b/driver/vms_axp.opt @@ -0,0 +1,5 @@ +[-.UTILS]UTILS.OLB_AXP/LIB +SYS$SHARE:DECW$XMLIBSHR.EXE/SHARE +SYS$SHARE:DECW$XMULIBSHR.EXE/SHARE +SYS$SHARE:DECW$XTSHR.EXE/SHARE +SYS$SHARE:DECW$XLIBSHR.EXE/SHARE diff --git a/driver/vms_axp_12.opt b/driver/vms_axp_12.opt new file mode 100644 index 00000000..25dd1f18 --- /dev/null +++ b/driver/vms_axp_12.opt @@ -0,0 +1,5 @@ +[-.UTILS]UTILS.OLB_AXP/LIB +SYS$SHARE:DECW$XMLIBSHR12.EXE/SHARE +SYS$SHARE:DECW$XMULIBSHRR5.EXE/SHARE +SYS$SHARE:DECW$XTLIBSHRR5.EXE/SHARE +SYS$SHARE:DECW$XLIBSHR.EXE/SHARE diff --git a/driver/vms_decc.opt b/driver/vms_decc.opt new file mode 100644 index 00000000..65bec033 --- /dev/null +++ b/driver/vms_decc.opt @@ -0,0 +1,5 @@ +[-.UTILS]UTILS.OLB_DECC/LIB +SYS$SHARE:DECW$XMLIBSHR.EXE/SHARE +SYS$SHARE:DECW$XMULIBSHR.EXE/SHARE +SYS$SHARE:DECW$XTSHR.EXE/SHARE +SYS$SHARE:DECW$XLIBSHR.EXE/SHARE diff --git a/driver/vms_decc_12.opt b/driver/vms_decc_12.opt new file mode 100644 index 00000000..fdd9a802 --- /dev/null +++ b/driver/vms_decc_12.opt @@ -0,0 +1,5 @@ +[-.UTILS]UTILS.OLB_DECC/LIB +SYS$SHARE:DECW$XMLIBSHR12.EXE/SHARE +SYS$SHARE:DECW$XMULIBSHRR5.EXE/SHARE +SYS$SHARE:DECW$XTLIBSHRR5.EXE/SHARE +SYS$SHARE:DECW$XLIBSHR.EXE/SHARE diff --git a/driver/windows.c b/driver/windows.c new file mode 100644 index 00000000..52c93127 --- /dev/null +++ b/driver/windows.c @@ -0,0 +1,2001 @@ +/* windows.c --- turning the screen black; dealing with visuals, virtual roots. + * xscreensaver, Copyright (c) 1991-2010 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef VMS +# include /* for getpid() */ +# include "vms-gtod.h" /* for gettimeofday() */ +#endif /* VMS */ + +#ifndef VMS +# include /* for getpwuid() */ +#else /* VMS */ +# include "vms-pwd.h" +#endif /* VMS */ + +#ifdef HAVE_UNAME +# include /* for uname() */ +#endif /* HAVE_UNAME */ + +#include +/* #include / * for CARD32 */ +#include +#include /* for XSetClassHint() */ +#include +#include /* for time() */ +#include /* for the signal names */ +#include +#include + +/* You might think that to store an array of 32-bit quantities onto a + server-side property, you would pass an array of 32-bit data quantities + into XChangeProperty(). You would be wrong. You have to use an array + of longs, even if long is 64 bits (using 32 of each 64.) + */ +typedef long PROP32; + +#ifdef HAVE_MIT_SAVER_EXTENSION +# include +#endif /* HAVE_MIT_SAVER_EXTENSION */ + +#ifdef HAVE_XF86VMODE +# include +#endif /* HAVE_XF86VMODE */ + +#ifdef HAVE_XINERAMA +# include +#endif /* HAVE_XINERAMA */ + +/* This file doesn't need the Xt headers, so stub these types out... */ +#undef XtPointer +#define XtAppContext void* +#define XrmDatabase void* +#define XtIntervalId void* +#define XtPointer void* +#define Widget void* + +#include "xscreensaver.h" +#include "visual.h" +#include "fade.h" + + +extern int kill (pid_t, int); /* signal() is in sys/signal.h... */ + +Atom XA_VROOT, XA_XSETROOT_ID, XA_ESETROOT_PMAP_ID, XA_XROOTPMAP_ID; +Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID; +Atom XA_SCREENSAVER_STATUS; + + +extern saver_info *global_si_kludge; /* I hate C so much... */ + +static void maybe_transfer_grabs (saver_screen_info *ssi, + Window old_w, Window new_w, int new_screen); + +#define ALL_POINTER_EVENTS \ + (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \ + LeaveWindowMask | PointerMotionMask | PointerMotionHintMask | \ + Button1MotionMask | Button2MotionMask | Button3MotionMask | \ + Button4MotionMask | Button5MotionMask | ButtonMotionMask) + + +static const char * +grab_string(int status) +{ + switch (status) + { + case GrabSuccess: return "GrabSuccess"; + case AlreadyGrabbed: return "AlreadyGrabbed"; + case GrabInvalidTime: return "GrabInvalidTime"; + case GrabNotViewable: return "GrabNotViewable"; + case GrabFrozen: return "GrabFrozen"; + default: + { + static char foo[255]; + sprintf(foo, "unknown status: %d", status); + return foo; + } + } +} + +static int +grab_kbd(saver_info *si, Window w, int screen_no) +{ + saver_preferences *p = &si->prefs; + int status = XGrabKeyboard (si->dpy, w, True, + /* I don't really understand Sync vs Async, + but these seem to work... */ + GrabModeSync, GrabModeAsync, + CurrentTime); + if (status == GrabSuccess) + { + si->keyboard_grab_window = w; + si->keyboard_grab_screen = screen_no; + } + + if (p->verbose_p) + fprintf(stderr, "%s: %d: grabbing keyboard on 0x%lx... %s.\n", + blurb(), screen_no, (unsigned long) w, grab_string(status)); + return status; +} + + +static int +grab_mouse (saver_info *si, Window w, Cursor cursor, int screen_no) +{ + saver_preferences *p = &si->prefs; + int status = XGrabPointer (si->dpy, w, True, ALL_POINTER_EVENTS, + GrabModeAsync, GrabModeAsync, w, + cursor, CurrentTime); + if (status == GrabSuccess) + { + si->mouse_grab_window = w; + si->mouse_grab_screen = screen_no; + } + + if (p->verbose_p) + fprintf(stderr, "%s: %d: grabbing mouse on 0x%lx... %s.\n", + blurb(), screen_no, (unsigned long) w, grab_string(status)); + return status; +} + + +static void +ungrab_kbd(saver_info *si) +{ + saver_preferences *p = &si->prefs; + XUngrabKeyboard(si->dpy, CurrentTime); + if (p->verbose_p) + fprintf(stderr, "%s: %d: ungrabbing keyboard (was 0x%lx).\n", + blurb(), si->keyboard_grab_screen, + (unsigned long) si->keyboard_grab_window); + si->keyboard_grab_window = 0; +} + + +static void +ungrab_mouse(saver_info *si) +{ + saver_preferences *p = &si->prefs; + XUngrabPointer(si->dpy, CurrentTime); + if (p->verbose_p) + fprintf(stderr, "%s: %d: ungrabbing mouse (was 0x%lx).\n", + blurb(), si->mouse_grab_screen, + (unsigned long) si->mouse_grab_window); + si->mouse_grab_window = 0; +} + + +/* Apparently there is this program called "rdesktop" which is a windows + terminal server client for Unix. It would seem that this program holds + the keyboard GRABBED the whole time it has focus! This is, of course, + completely idiotic: the whole point of grabbing is to get events when + you do *not* have focus, so grabbing *only when* you have focus is + completely redundant -- unless your goal is to make xscreensaver not + able to ever lock the screen when your program is running. + + If xscreensaver blanks while rdesktop still has a keyboard grab, then + when we try to prompt for the password, we won't get the characters: + they'll be typed into rdesktop. + + Perhaps rdesktop will release its keyboard grab if it loses focus? + What the hell, let's give it a try. If we fail to grab the keyboard + four times in a row, we forcibly set focus to "None" and try four + more times. (We don't touch focus unless we're already having a hard + time getting a grab.) + */ +static void +nuke_focus (saver_info *si, int screen_no) +{ + saver_preferences *p = &si->prefs; + Window focus = 0; + int rev = 0; + + XGetInputFocus (si->dpy, &focus, &rev); + + if (p->verbose_p) + { + char w[255], r[255]; + + if (focus == PointerRoot) strcpy (w, "PointerRoot"); + else if (focus == None) strcpy (w, "None"); + else sprintf (w, "0x%lx", (unsigned long) focus); + + if (rev == RevertToParent) strcpy (r, "RevertToParent"); + else if (rev == RevertToPointerRoot) strcpy (r, "RevertToPointerRoot"); + else if (rev == RevertToNone) strcpy (r, "RevertToNone"); + else sprintf (r, "0x%x", rev); + + fprintf (stderr, "%s: %d: removing focus from %s / %s.\n", + blurb(), screen_no, w, r); + } + + XSetInputFocus (si->dpy, None, RevertToNone, CurrentTime); + XSync (si->dpy, False); +} + + +static void +ungrab_keyboard_and_mouse (saver_info *si) +{ + ungrab_mouse (si); + ungrab_kbd (si); +} + + +static Bool +grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor, + int screen_no) +{ + Status mstatus = 0, kstatus = 0; + int i; + int retries = 4; + Bool focus_fuckus = False; + + AGAIN: + + for (i = 0; i < retries; i++) + { + XSync (si->dpy, False); + kstatus = grab_kbd (si, window, screen_no); + if (kstatus == GrabSuccess) + break; + + /* else, wait a second and try to grab again. */ + sleep (1); + } + + if (kstatus != GrabSuccess) + { + fprintf (stderr, "%s: couldn't grab keyboard! (%s)\n", + blurb(), grab_string(kstatus)); + + if (! focus_fuckus) + { + focus_fuckus = True; + nuke_focus (si, screen_no); + goto AGAIN; + } + } + + for (i = 0; i < retries; i++) + { + XSync (si->dpy, False); + mstatus = grab_mouse (si, window, cursor, screen_no); + if (mstatus == GrabSuccess) + break; + + /* else, wait a second and try to grab again. */ + sleep (1); + } + + if (mstatus != GrabSuccess) + fprintf (stderr, "%s: couldn't grab pointer! (%s)\n", + blurb(), grab_string(mstatus)); + + + /* When should we allow blanking to proceed? The current theory + is that a keyboard grab is mandatory; a mouse grab is optional. + + - If we don't have a keyboard grab, then we won't be able to + read a password to unlock, so the kbd grab is mandatory. + (We can't conditionalize this on locked_p, because someone + might run "xscreensaver-command -lock" at any time.) + + - If we don't have a mouse grab, then we might not see mouse + clicks as a signal to unblank -- but we will still see kbd + activity, so that's not a disaster. + + It has been suggested that we should allow blanking if locking + is disabled, and we have a mouse grab but no keyboard grab + (that is: kstatus != GrabSuccess && + mstatus == GrabSuccess && + si->locking_disabled_p) + That would allow screen blanking (but not locking) while the gdm + login screen had the keyboard grabbed, but one would have to use + the mouse to unblank. Keyboard characters would go to the gdm + login field without unblanking. I have not made this change + because I'm not completely convinced it is a safe thing to do. + */ + + if (kstatus != GrabSuccess) /* Do not blank without a kbd grab. */ + { + /* If we didn't get both grabs, release the one we did get. */ + ungrab_keyboard_and_mouse (si); + return False; + } + + return True; /* Grab is good, go ahead and blank. */ +} + + +int +move_mouse_grab (saver_info *si, Window to, Cursor cursor, int to_screen_no) +{ + Window old = si->mouse_grab_window; + + if (old == 0) + return grab_mouse (si, to, cursor, to_screen_no); + else + { + saver_preferences *p = &si->prefs; + int status; + + XSync (si->dpy, False); + XGrabServer (si->dpy); /* ############ DANGER! */ + XSync (si->dpy, False); + + if (p->verbose_p) + fprintf(stderr, "%s: grabbing server...\n", blurb()); + + ungrab_mouse (si); + status = grab_mouse (si, to, cursor, to_screen_no); + + if (status != GrabSuccess) /* Augh! */ + { + sleep (1); /* Note dramatic evil of sleeping + with server grabbed. */ + XSync (si->dpy, False); + status = grab_mouse (si, to, cursor, to_screen_no); + } + + if (status != GrabSuccess) /* Augh! Try to get the old one back... */ + grab_mouse (si, old, cursor, to_screen_no); + + XUngrabServer (si->dpy); + XSync (si->dpy, False); /* ###### (danger over) */ + + if (p->verbose_p) + fprintf(stderr, "%s: ungrabbing server.\n", blurb()); + + return status; + } +} + + +/* Prints an error message to stderr and returns True if there is another + xscreensaver running already. Silently returns False otherwise. */ +Bool +ensure_no_screensaver_running (Display *dpy, Screen *screen) +{ + Bool status = 0; + int i; + Window root = RootWindowOfScreen (screen); + Window root2, parent, *kids; + unsigned int nkids; + XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler); + + if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) + abort (); + if (root != root2) + abort (); + if (parent) + abort (); + for (i = 0; i < nkids; i++) + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *version; + + if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &version) + == Success + && type != None) + { + unsigned char *id; + if (!XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &id) + == Success + || type == None) + id = (unsigned char *) "???"; + + fprintf (stderr, + "%s: already running on display %s (window 0x%x)\n from process %s.\n", + blurb(), DisplayString (dpy), (int) kids [i], + (char *) id); + status = True; + } + + else if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &version) + == Success + && type != None + && !strcmp ((char *) version, "gnome-screensaver")) + { + fprintf (stderr, + "%s: \"%s\" is already running on display %s (window 0x%x)\n", + blurb(), (char *) version, + DisplayString (dpy), (int) kids [i]); + status = True; + break; + } + } + + if (kids) XFree ((char *) kids); + XSync (dpy, False); + XSetErrorHandler (old_handler); + return status; +} + + + +/* Virtual-root hackery */ + +#ifdef _VROOT_H_ +ERROR! You must not include vroot.h in this file. +#endif + +static void +store_vroot_property (Display *dpy, Window win, Window value) +{ +#if 0 + if (p->verbose_p) + fprintf (stderr, + "%s: storing XA_VROOT = 0x%x (%s) = 0x%x (%s)\n", blurb(), + win, + (win == screensaver_window ? "ScreenSaver" : + (win == real_vroot ? "VRoot" : + (win == real_vroot_value ? "Vroot_value" : "???"))), + value, + (value == screensaver_window ? "ScreenSaver" : + (value == real_vroot ? "VRoot" : + (value == real_vroot_value ? "Vroot_value" : "???")))); +#endif + XChangeProperty (dpy, win, XA_VROOT, XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &value, 1); +} + +static void +remove_vroot_property (Display *dpy, Window win) +{ +#if 0 + if (p->verbose_p) + fprintf (stderr, "%s: removing XA_VROOT from 0x%x (%s)\n", blurb(), win, + (win == screensaver_window ? "ScreenSaver" : + (win == real_vroot ? "VRoot" : + (win == real_vroot_value ? "Vroot_value" : "???")))); +#endif + XDeleteProperty (dpy, win, XA_VROOT); +} + + +static Bool safe_XKillClient (Display *dpy, XID id); + +static void +kill_xsetroot_data_1 (Display *dpy, Window window, + Atom prop, const char *atom_name, + Bool verbose_p) +{ + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + + /* If the user has been using xv or xsetroot as a screensaver (to display + an image on the screensaver window, as a kind of slideshow) then the + pixmap and its associated color cells have been put in RetainPermanent + CloseDown mode. Since we're not destroying the xscreensaver window, + but merely unmapping it, we need to free these resources or those + colormap cells will stay allocated while the screensaver is off. (We + could just delete the screensaver window and recreate it later, but + that could cause other problems.) This code does an atomic read-and- + delete of the _XSETROOT_ID property, and if it held a pixmap, then we + cause the RetainPermanent resources of the client which created it + (and which no longer exists) to be freed. + + Update: it seems that Gnome and KDE do this same trick, but with the + properties "ESETROOT_PMAP_ID" and/or "_XROOTPMAP_ID" instead of + "_XSETROOT_ID". So, we'll kill those too. + */ + if (XGetWindowProperty (dpy, window, prop, 0, 1, + True, AnyPropertyType, &type, &format, &nitems, + &bytesafter, &dataP) + == Success + && type != None) + { + Pixmap *pixP = (Pixmap *) dataP; + if (pixP && *pixP && type == XA_PIXMAP && format == 32 && + nitems == 1 && bytesafter == 0) + { + if (verbose_p) + fprintf (stderr, "%s: destroying %s data (0x%lX).\n", + blurb(), atom_name, *pixP); + safe_XKillClient (dpy, *pixP); + } + else + fprintf (stderr, + "%s: deleted unrecognised %s property: \n" + "\t%lu, %lu; type: %lu, format: %d, " + "nitems: %lu, bytesafter %ld\n", + blurb(), atom_name, + (unsigned long) pixP, (pixP ? *pixP : 0), type, + format, nitems, bytesafter); + } +} + + +static void +kill_xsetroot_data (Display *dpy, Window w, Bool verbose_p) +{ + kill_xsetroot_data_1 (dpy, w, XA_XSETROOT_ID, "_XSETROOT_ID", verbose_p); + kill_xsetroot_data_1 (dpy, w, XA_ESETROOT_PMAP_ID, "ESETROOT_PMAP_ID", + verbose_p); + kill_xsetroot_data_1 (dpy, w, XA_XROOTPMAP_ID, "_XROOTPMAP_ID", verbose_p); +} + + +static void +save_real_vroot (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + Display *dpy = si->dpy; + Screen *screen = ssi->screen; + int i; + Window root = RootWindowOfScreen (screen); + Window root2, parent, *kids; + unsigned int nkids; + XErrorHandler old_handler; + + /* It's possible that a window might be deleted between our call to + XQueryTree() and our call to XGetWindowProperty(). Don't die if + that happens (but just ignore that window, it's not the one we're + interested in anyway.) + */ + XSync (dpy, False); + old_handler = XSetErrorHandler (BadWindow_ehandler); + XSync (dpy, False); + + ssi->real_vroot = 0; + ssi->real_vroot_value = 0; + if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) + abort (); + if (root != root2) + abort (); + if (parent) + abort (); + for (i = 0; i < nkids; i++) + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + Window *vrootP; + int j; + + /* Skip this window if it is the xscreensaver window of any other + screen (this can happen in the Xinerama case.) + */ + for (j = 0; j < si->nscreens; j++) + { + saver_screen_info *ssi2 = &si->screens[j]; + if (kids[i] == ssi2->screensaver_window) + goto SKIP; + } + + if (XGetWindowProperty (dpy, kids[i], XA_VROOT, 0, 1, False, XA_WINDOW, + &type, &format, &nitems, &bytesafter, + &dataP) + != Success) + continue; + if (! dataP) + continue; + + vrootP = (Window *) dataP; + if (ssi->real_vroot) + { + if (*vrootP == ssi->screensaver_window) abort (); + fprintf (stderr, + "%s: more than one virtual root window found (0x%x and 0x%x).\n", + blurb(), (int) ssi->real_vroot, (int) kids [i]); + exit (1); + } + ssi->real_vroot = kids [i]; + ssi->real_vroot_value = *vrootP; + SKIP: + ; + } + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + if (ssi->real_vroot) + { + remove_vroot_property (si->dpy, ssi->real_vroot); + XSync (dpy, False); + } + + XFree ((char *) kids); +} + + +static Bool +restore_real_vroot_1 (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + if (p->verbose_p && ssi->real_vroot) + fprintf (stderr, + "%s: restoring __SWM_VROOT property on the real vroot (0x%lx).\n", + blurb(), (unsigned long) ssi->real_vroot); + if (ssi->screensaver_window) + remove_vroot_property (si->dpy, ssi->screensaver_window); + if (ssi->real_vroot) + { + store_vroot_property (si->dpy, ssi->real_vroot, ssi->real_vroot_value); + ssi->real_vroot = 0; + ssi->real_vroot_value = 0; + /* make sure the property change gets there before this process + terminates! We might be doing this because we have intercepted + SIGTERM or something. */ + XSync (si->dpy, False); + return True; + } + return False; +} + +Bool +restore_real_vroot (saver_info *si) +{ + int i; + Bool did_any = False; + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (restore_real_vroot_1 (ssi)) + did_any = True; + } + return did_any; +} + + +/* Signal hackery to ensure that the vroot doesn't get left in an + inconsistent state + */ + +const char * +signal_name(int signal) +{ + switch (signal) { + case SIGHUP: return "SIGHUP"; + case SIGINT: return "SIGINT"; + case SIGQUIT: return "SIGQUIT"; + case SIGILL: return "SIGILL"; + case SIGTRAP: return "SIGTRAP"; +#ifdef SIGABRT + case SIGABRT: return "SIGABRT"; +#endif + case SIGFPE: return "SIGFPE"; + case SIGKILL: return "SIGKILL"; + case SIGBUS: return "SIGBUS"; + case SIGSEGV: return "SIGSEGV"; + case SIGPIPE: return "SIGPIPE"; + case SIGALRM: return "SIGALRM"; + case SIGTERM: return "SIGTERM"; +#ifdef SIGSTOP + case SIGSTOP: return "SIGSTOP"; +#endif +#ifdef SIGCONT + case SIGCONT: return "SIGCONT"; +#endif +#ifdef SIGUSR1 + case SIGUSR1: return "SIGUSR1"; +#endif +#ifdef SIGUSR2 + case SIGUSR2: return "SIGUSR2"; +#endif +#ifdef SIGEMT + case SIGEMT: return "SIGEMT"; +#endif +#ifdef SIGSYS + case SIGSYS: return "SIGSYS"; +#endif +#ifdef SIGCHLD + case SIGCHLD: return "SIGCHLD"; +#endif +#ifdef SIGPWR + case SIGPWR: return "SIGPWR"; +#endif +#ifdef SIGWINCH + case SIGWINCH: return "SIGWINCH"; +#endif +#ifdef SIGURG + case SIGURG: return "SIGURG"; +#endif +#ifdef SIGIO + case SIGIO: return "SIGIO"; +#endif +#ifdef SIGVTALRM + case SIGVTALRM: return "SIGVTALRM"; +#endif +#ifdef SIGXCPU + case SIGXCPU: return "SIGXCPU"; +#endif +#ifdef SIGXFSZ + case SIGXFSZ: return "SIGXFSZ"; +#endif +#ifdef SIGDANGER + case SIGDANGER: return "SIGDANGER"; +#endif + default: + { + static char buf[50]; + sprintf(buf, "signal %d\n", signal); + return buf; + } + } +} + + + +static RETSIGTYPE +restore_real_vroot_handler (int sig) +{ + saver_info *si = global_si_kludge; /* I hate C so much... */ + + signal (sig, SIG_DFL); + if (restore_real_vroot (si)) + fprintf (real_stderr, "\n%s: %s intercepted, vroot restored.\n", + blurb(), signal_name(sig)); + kill (getpid (), sig); +} + +static void +catch_signal (saver_info *si, int sig, RETSIGTYPE (*handler) (int)) +{ +# ifdef HAVE_SIGACTION + + struct sigaction a; + a.sa_handler = handler; + sigemptyset (&a.sa_mask); + a.sa_flags = 0; + + /* On Linux 2.4.9 (at least) we need to tell the kernel to not mask delivery + of this signal from inside its handler, or else when we execvp() the + process again, it starts up with SIGHUP blocked, meaning that killing + it with -HUP only works *once*. You'd think that execvp() would reset + all the signal masks, but it doesn't. + */ +# if defined(SA_NOMASK) + a.sa_flags |= SA_NOMASK; +# elif defined(SA_NODEFER) + a.sa_flags |= SA_NODEFER; +# endif + + if (sigaction (sig, &a, 0) < 0) +# else /* !HAVE_SIGACTION */ + if (((long) signal (sig, handler)) == -1L) +# endif /* !HAVE_SIGACTION */ + { + char buf [255]; + sprintf (buf, "%s: couldn't catch %s", blurb(), signal_name(sig)); + perror (buf); + saver_exit (si, 1, 0); + } +} + +static RETSIGTYPE saver_sighup_handler (int sig); + +void +handle_signals (saver_info *si) +{ + catch_signal (si, SIGHUP, saver_sighup_handler); + + catch_signal (si, SIGINT, restore_real_vroot_handler); + catch_signal (si, SIGQUIT, restore_real_vroot_handler); + catch_signal (si, SIGILL, restore_real_vroot_handler); + catch_signal (si, SIGTRAP, restore_real_vroot_handler); +#ifdef SIGIOT + catch_signal (si, SIGIOT, restore_real_vroot_handler); +#endif + catch_signal (si, SIGABRT, restore_real_vroot_handler); +#ifdef SIGEMT + catch_signal (si, SIGEMT, restore_real_vroot_handler); +#endif + catch_signal (si, SIGFPE, restore_real_vroot_handler); + catch_signal (si, SIGBUS, restore_real_vroot_handler); + catch_signal (si, SIGSEGV, restore_real_vroot_handler); +#ifdef SIGSYS + catch_signal (si, SIGSYS, restore_real_vroot_handler); +#endif + catch_signal (si, SIGTERM, restore_real_vroot_handler); +#ifdef SIGXCPU + catch_signal (si, SIGXCPU, restore_real_vroot_handler); +#endif +#ifdef SIGXFSZ + catch_signal (si, SIGXFSZ, restore_real_vroot_handler); +#endif +#ifdef SIGDANGER + catch_signal (si, SIGDANGER, restore_real_vroot_handler); +#endif +} + + +static RETSIGTYPE +saver_sighup_handler (int sig) +{ + saver_info *si = global_si_kludge; /* I hate C so much... */ + + /* Re-establish SIGHUP handler */ + catch_signal (si, SIGHUP, saver_sighup_handler); + + fprintf (stderr, "%s: %s received: restarting...\n", + blurb(), signal_name(sig)); + + if (si->screen_blanked_p) + { + int i; + for (i = 0; i < si->nscreens; i++) + kill_screenhack (&si->screens[i]); + unblank_screen (si); + XSync (si->dpy, False); + } + + restart_process (si); /* Does not return */ + abort (); +} + + + +void +saver_exit (saver_info *si, int status, const char *dump_core_reason) +{ + saver_preferences *p = &si->prefs; + static Bool exiting = False; + Bool bugp; + Bool vrs; + + if (exiting) + exit(status); + + exiting = True; + + vrs = restore_real_vroot (si); + emergency_kill_subproc (si); + shutdown_stderr (si); + + if (p->verbose_p && vrs) + fprintf (real_stderr, "%s: old vroot restored.\n", blurb()); + + fflush(real_stdout); + +#ifdef VMS /* on VMS, 1 is the "normal" exit code instead of 0. */ + if (status == 0) status = 1; + else if (status == 1) status = -1; +#endif + + bugp = !!dump_core_reason; + + if (si->prefs.debug_p && !dump_core_reason) + dump_core_reason = "because of -debug"; + + if (dump_core_reason) + { + /* Note that the Linux man page for setuid() says If uid is + different from the old effective uid, the process will be + forbidden from leaving core dumps. + */ + char cwd[4096]; /* should really be PATH_MAX, but who cares. */ + cwd[0] = 0; + fprintf(real_stderr, "%s: dumping core (%s)\n", blurb(), + dump_core_reason); + + if (bugp) + fprintf(real_stderr, + "%s: see http://www.jwz.org/xscreensaver/bugs.html\n" + "\t\t\tfor bug reporting information.\n\n", + blurb()); + +# if defined(HAVE_GETCWD) + if (!getcwd (cwd, sizeof(cwd))) +# elif defined(HAVE_GETWD) + if (!getwd (cwd)) +# endif + strcpy(cwd, "unknown."); + + fprintf (real_stderr, "%s: current directory is %s\n", blurb(), cwd); + describe_uids (si, real_stderr); + + /* Do this to drop a core file, so that we can get a stack trace. */ + abort(); + } + + exit (status); +} + + +/* Managing the actual screensaver window */ + +Bool +window_exists_p (Display *dpy, Window window) +{ + XErrorHandler old_handler; + XWindowAttributes xgwa; + xgwa.screen = 0; + old_handler = XSetErrorHandler (BadWindow_ehandler); + XGetWindowAttributes (dpy, window, &xgwa); + XSync (dpy, False); + XSetErrorHandler (old_handler); + return (xgwa.screen != 0); +} + +static void +store_saver_id (saver_screen_info *ssi) +{ + XClassHint class_hints; + saver_info *si = ssi->global; + unsigned long pid = (unsigned long) getpid (); + char buf[20]; + struct passwd *p = getpwuid (getuid ()); + const char *name, *host; + char *id; + + /* First store the name and class on the window. + */ + class_hints.res_name = progname; + class_hints.res_class = progclass; + XSetClassHint (si->dpy, ssi->screensaver_window, &class_hints); + XStoreName (si->dpy, ssi->screensaver_window, "screensaver"); + + /* Then store the xscreensaver version number. + */ + XChangeProperty (si->dpy, ssi->screensaver_window, + XA_SCREENSAVER_VERSION, + XA_STRING, 8, PropModeReplace, + (unsigned char *) si->version, + strlen (si->version)); + + /* Now store the XSCREENSAVER_ID property, that says what user and host + xscreensaver is running as. + */ + + if (p && p->pw_name && *p->pw_name) + name = p->pw_name; + else if (p) + { + sprintf (buf, "%lu", (unsigned long) p->pw_uid); + name = buf; + } + else + name = "???"; + +# if defined(HAVE_UNAME) + { + struct utsname uts; + if (uname (&uts) < 0) + host = "???"; + else + host = uts.nodename; + } +# elif defined(VMS) + host = getenv("SYS$NODE"); +# else /* !HAVE_UNAME && !VMS */ + host = "???"; +# endif /* !HAVE_UNAME && !VMS */ + + id = (char *) malloc (strlen(name) + strlen(host) + 50); + sprintf (id, "%lu (%s@%s)", pid, name, host); + + XChangeProperty (si->dpy, ssi->screensaver_window, + XA_SCREENSAVER_ID, XA_STRING, + 8, PropModeReplace, + (unsigned char *) id, strlen (id)); + free (id); +} + + +void +store_saver_status (saver_info *si) +{ + PROP32 *status; + int size = si->nscreens + 2; + int i; + + status = (PROP32 *) calloc (size, sizeof(PROP32)); + + status[0] = (PROP32) (si->screen_blanked_p + ? (si->locked_p ? XA_LOCK : XA_BLANK) + : 0); + status[1] = (PROP32) si->blank_time; + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + status [2 + i] = ssi->current_hack + 1; + } + + XChangeProperty (si->dpy, + RootWindow (si->dpy, 0), /* always screen #0 */ + XA_SCREENSAVER_STATUS, + XA_INTEGER, 32, PropModeReplace, + (unsigned char *) status, size); + free (status); +} + + +static Bool error_handler_hit_p = False; + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + error_handler_hit_p = True; + return 0; +} + + +/* Returns True if successful, False if an X error occurred. + We need this because other programs might have done things to + our window that will cause XChangeWindowAttributes() to fail: + if that happens, we give up, destroy the window, and re-create + it. + */ +static Bool +safe_XChangeWindowAttributes (Display *dpy, Window window, + unsigned long mask, + XSetWindowAttributes *attrs) +{ + XErrorHandler old_handler; + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + XChangeWindowAttributes (dpy, window, mask, attrs); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + return (!error_handler_hit_p); +} + + +/* This might not be necessary, but just in case. */ +static Bool +safe_XConfigureWindow (Display *dpy, Window window, + unsigned long mask, XWindowChanges *changes) +{ + XErrorHandler old_handler; + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + XConfigureWindow (dpy, window, mask, changes); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + return (!error_handler_hit_p); +} + +/* This might not be necessary, but just in case. */ +static Bool +safe_XDestroyWindow (Display *dpy, Window window) +{ + XErrorHandler old_handler; + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + XDestroyWindow (dpy, window); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + return (!error_handler_hit_p); +} + + +static Bool +safe_XKillClient (Display *dpy, XID id) +{ + XErrorHandler old_handler; + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + XKillClient (dpy, id); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + return (!error_handler_hit_p); +} + + +#ifdef HAVE_XF86VMODE +Bool +safe_XF86VidModeGetViewPort (Display *dpy, int screen, int *xP, int *yP) +{ + Bool result; + XErrorHandler old_handler; + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + result = XF86VidModeGetViewPort (dpy, screen, xP, yP); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + return (error_handler_hit_p + ? False + : result); +} + +/* There is no "safe_XF86VidModeGetModeLine" because it fails with an + untrappable I/O error instead of an X error -- so one must call + safe_XF86VidModeGetViewPort first, and assume that both have the + same error condition. Thank you XFree, may I have another. + */ + +#endif /* HAVE_XF86VMODE */ + + +static void +initialize_screensaver_window_1 (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + Bool install_cmap_p = ssi->install_cmap_p; /* not p->install_cmap_p */ + + /* This resets the screensaver window as fully as possible, since there's + no way of knowing what some random client may have done to us in the + meantime. We could just destroy and recreate the window, but that has + its own set of problems... + */ + XColor black; + XSetWindowAttributes attrs; + unsigned long attrmask; + static Bool printed_visual_info = False; /* only print the message once. */ + Window horked_window = 0; + + black.red = black.green = black.blue = 0; + + if (ssi->cmap == DefaultColormapOfScreen (ssi->screen)) + ssi->cmap = 0; + + if (ssi->current_visual != DefaultVisualOfScreen (ssi->screen)) + /* It's not the default visual, so we have no choice but to install. */ + install_cmap_p = True; + + if (install_cmap_p) + { + if (! ssi->cmap) + { + ssi->cmap = XCreateColormap (si->dpy, + RootWindowOfScreen (ssi->screen), + ssi->current_visual, AllocNone); + if (! XAllocColor (si->dpy, ssi->cmap, &black)) abort (); + ssi->black_pixel = black.pixel; + } + } + else + { + Colormap def_cmap = DefaultColormapOfScreen (ssi->screen); + if (ssi->cmap) + { + XFreeColors (si->dpy, ssi->cmap, &ssi->black_pixel, 1, 0); + if (ssi->cmap != ssi->demo_cmap && + ssi->cmap != def_cmap) + XFreeColormap (si->dpy, ssi->cmap); + } + ssi->cmap = def_cmap; + ssi->black_pixel = BlackPixelOfScreen (ssi->screen); + } + + attrmask = (CWOverrideRedirect | CWEventMask | CWBackingStore | CWColormap | + CWBackPixel | CWBackingPixel | CWBorderPixel); + attrs.override_redirect = True; + + /* When use_mit_saver_extension or use_sgi_saver_extension is true, we won't + actually be reading these events during normal operation; but we still + need to see Button events for demo-mode to work properly. + */ + attrs.event_mask = (KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask); + + attrs.backing_store = NotUseful; + attrs.colormap = ssi->cmap; + attrs.background_pixel = ssi->black_pixel; + attrs.backing_pixel = ssi->black_pixel; + attrs.border_pixel = ssi->black_pixel; + + if (!p->verbose_p || printed_visual_info) + ; + else if (ssi->current_visual == DefaultVisualOfScreen (ssi->screen)) + { + fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number); + describe_visual (stderr, ssi->screen, ssi->current_visual, + install_cmap_p); + } + else + { + fprintf (stderr, "%s: using visual: ", blurb()); + describe_visual (stderr, ssi->screen, ssi->current_visual, + install_cmap_p); + fprintf (stderr, "%s: default visual: ", blurb()); + describe_visual (stderr, ssi->screen, + DefaultVisualOfScreen (ssi->screen), + ssi->install_cmap_p); + } + printed_visual_info = True; + +#ifdef HAVE_MIT_SAVER_EXTENSION + if (si->using_mit_saver_extension) + { + XScreenSaverInfo *info; + Window root = RootWindowOfScreen (ssi->screen); + +#if 0 + /* This call sets the server screensaver timeouts to what we think + they should be (based on the resources and args xscreensaver was + started with.) It's important that we do this to sync back up + with the server - if we have turned on prematurely, as by an + ACTIVATE ClientMessage, then the server may decide to activate + the screensaver while it's already active. That's ok for us, + since we would know to ignore that ScreenSaverActivate event, + but a side effect of this would be that the server would map its + saver window (which we then hide again right away) meaning that + the bits currently on the screen get blown away. Ugly. */ + + /* #### Ok, that doesn't work - when we tell the server that the + screensaver is "off" it sends us a Deactivate event, which is + sensible... but causes the saver to never come on. Hmm. */ + disable_builtin_screensaver (si, True); +#endif /* 0 */ + +#if 0 + /* #### The MIT-SCREEN-SAVER extension gives us access to the + window that the server itself uses for saving the screen. + However, using this window in any way, in particular, calling + XScreenSaverSetAttributes() as below, tends to make the X server + crash. So fuck it, let's try and get along without using it... + + It's also inconvenient to use this window because it doesn't + always exist (though the ID is constant.) So to use this + window, we'd have to reimplement the ACTIVATE ClientMessage to + tell the *server* to tell *us* to turn on, to cause the window + to get created at the right time. Gag. */ + XScreenSaverSetAttributes (si->dpy, root, + 0, 0, width, height, 0, + current_depth, InputOutput, visual, + attrmask, &attrs); + XSync (si->dpy, False); +#endif /* 0 */ + + info = XScreenSaverAllocInfo (); + XScreenSaverQueryInfo (si->dpy, root, info); + ssi->server_mit_saver_window = info->window; + if (! ssi->server_mit_saver_window) abort (); + XFree (info); + } +#endif /* HAVE_MIT_SAVER_EXTENSION */ + + if (ssi->screensaver_window) + { + XWindowChanges changes; + unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth; + changes.x = ssi->x; + changes.y = ssi->y; + changes.width = ssi->width; + changes.height = ssi->height; + changes.border_width = 0; + + if (! (safe_XConfigureWindow (si->dpy, ssi->screensaver_window, + changesmask, &changes) && + safe_XChangeWindowAttributes (si->dpy, ssi->screensaver_window, + attrmask, &attrs))) + { + horked_window = ssi->screensaver_window; + ssi->screensaver_window = 0; + } + } + + if (!ssi->screensaver_window) + { + ssi->screensaver_window = + XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen), + ssi->x, ssi->y, ssi->width, ssi->height, + 0, ssi->current_depth, InputOutput, + ssi->current_visual, attrmask, &attrs); + reset_stderr (ssi); + + if (horked_window) + { + fprintf (stderr, + "%s: someone horked our saver window (0x%lx)! Recreating it...\n", + blurb(), (unsigned long) horked_window); + maybe_transfer_grabs (ssi, horked_window, ssi->screensaver_window, + ssi->number); + safe_XDestroyWindow (si->dpy, horked_window); + horked_window = 0; + } + + if (p->verbose_p) + fprintf (stderr, "%s: %d: saver window is 0x%lx.\n", + blurb(), ssi->number, + (unsigned long) ssi->screensaver_window); + } + + store_saver_id (ssi); /* store window name and IDs */ + + if (!ssi->cursor) + { + Pixmap bit; + bit = XCreatePixmapFromBitmapData (si->dpy, ssi->screensaver_window, + "\000", 1, 1, + BlackPixelOfScreen (ssi->screen), + BlackPixelOfScreen (ssi->screen), + 1); + ssi->cursor = XCreatePixmapCursor (si->dpy, bit, bit, &black, &black, + 0, 0); + XFreePixmap (si->dpy, bit); + } + + XSetWindowBackground (si->dpy, ssi->screensaver_window, ssi->black_pixel); + + if (si->demoing_p) + XUndefineCursor (si->dpy, ssi->screensaver_window); + else + XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor); +} + +void +initialize_screensaver_window (saver_info *si) +{ + int i; + for (i = 0; i < si->nscreens; i++) + initialize_screensaver_window_1 (&si->screens[i]); +} + + +/* Called when the RANDR (Resize and Rotate) extension tells us that + the size of the screen has changed while the screen was blanked. + Call update_screen_layout() first, then call this to synchronize + the size of the saver windows to the new sizes of the screens. + */ +void +resize_screensaver_window (saver_info *si) +{ + saver_preferences *p = &si->prefs; + int i; + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + XWindowAttributes xgwa; + + /* Make sure a window exists -- it might not if a monitor was just + added for the first time. + */ + if (! ssi->screensaver_window) + { + initialize_screensaver_window_1 (ssi); + if (p->verbose_p) + fprintf (stderr, + "%s: %d: newly added window 0x%lx %dx%d+%d+%d\n", + blurb(), i, (unsigned long) ssi->screensaver_window, + ssi->width, ssi->height, ssi->x, ssi->y); + } + + /* Make sure the window is the right size -- it might not be if + the monitor changed resolution, or if a badly-behaved hack + screwed with it. + */ + XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa); + if (xgwa.x != ssi->x || + xgwa.y != ssi->y || + xgwa.width != ssi->width || + xgwa.height != ssi->height) + { + XWindowChanges changes; + unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth; + changes.x = ssi->x; + changes.y = ssi->y; + changes.width = ssi->width; + changes.height = ssi->height; + changes.border_width = 0; + + if (p->verbose_p) + fprintf (stderr, + "%s: %d: resize 0x%lx from %dx%d+%d+%d to %dx%d+%d+%d\n", + blurb(), i, (unsigned long) ssi->screensaver_window, + xgwa.width, xgwa.height, xgwa.x, xgwa.y, + ssi->width, ssi->height, ssi->x, ssi->y); + + if (! safe_XConfigureWindow (si->dpy, ssi->screensaver_window, + changesmask, &changes)) + fprintf (stderr, "%s: %d: someone horked our saver window" + " (0x%lx)! Unable to resize it!\n", + blurb(), i, (unsigned long) ssi->screensaver_window); + } + + /* Now (if blanked) make sure that it's mapped and running a hack -- + it might not be if we just added it. (We also might be re-using + an old window that existed for a previous monitor that was + removed and re-added.) + + Note that spawn_screenhack() calls select_visual() which may destroy + and re-create the window via initialize_screensaver_window_1(). + */ + if (si->screen_blanked_p) + { + if (ssi->cmap) + XInstallColormap (si->dpy, ssi->cmap); + XMapRaised (si->dpy, ssi->screensaver_window); + if (! ssi->pid) + spawn_screenhack (ssi); + + /* Make sure the act of adding a screen doesn't present as + pointer motion (and thus cause an unblank). */ + { + Window root, child; + int x, y; + unsigned int mask; + XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child, + &ssi->poll_mouse_last_root_x, + &ssi->poll_mouse_last_root_y, + &x, &y, &mask); + } + } + } + + /* Kill off any savers running on no-longer-extant monitors. + */ + for (; i < si->ssi_count; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->pid) + kill_screenhack (ssi); + if (ssi->screensaver_window) + { + XUnmapWindow (si->dpy, ssi->screensaver_window); + restore_real_vroot_1 (ssi); + } + } +} + + +void +raise_window (saver_info *si, + Bool inhibit_fade, Bool between_hacks_p, Bool dont_clear) +{ + saver_preferences *p = &si->prefs; + int i; + + if (si->demoing_p) + inhibit_fade = True; + + if (si->emergency_lock_p) + inhibit_fade = True; + + if (!dont_clear) + initialize_screensaver_window (si); + + reset_watchdog_timer (si, True); + + if (p->fade_p && si->fading_possible_p && !inhibit_fade) + { + Window *current_windows = (Window *) + calloc(sizeof(Window), si->nscreens); + Colormap *current_maps = (Colormap *) + calloc(sizeof(Colormap), si->nscreens); + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + current_windows[i] = ssi->screensaver_window; + current_maps[i] = (between_hacks_p + ? ssi->cmap + : DefaultColormapOfScreen (ssi->screen)); + /* Ensure that the default background of the window is really black, + not a pixmap or something. (This does not clear the window.) */ + XSetWindowBackground (si->dpy, ssi->screensaver_window, + ssi->black_pixel); + } + + if (p->verbose_p) fprintf (stderr, "%s: fading...\n", blurb()); + + XGrabServer (si->dpy); /* ############ DANGER! */ + + /* Clear the stderr layer on each screen. + */ + if (!dont_clear) + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->stderr_overlay_window) + /* Do this before the fade, since the stderr cmap won't fade + even if we uninstall it (beats me...) */ + clear_stderr (ssi); + } + + /* Note! The server is grabbed, and this will take several seconds + to complete! */ + fade_screens (si->dpy, current_maps, + current_windows, si->nscreens, + p->fade_seconds/1000, p->fade_ticks, True, !dont_clear); + + free(current_maps); + free(current_windows); + current_maps = 0; + current_windows = 0; + + if (p->verbose_p) fprintf (stderr, "%s: fading done.\n", blurb()); + +#ifdef HAVE_MIT_SAVER_EXTENSION + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->server_mit_saver_window && + window_exists_p (si->dpy, ssi->server_mit_saver_window)) + XUnmapWindow (si->dpy, ssi->server_mit_saver_window); + } +#endif /* HAVE_MIT_SAVER_EXTENSION */ + + XUngrabServer (si->dpy); + XSync (si->dpy, False); /* ###### (danger over) */ + } + else + { + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (!dont_clear) + XClearWindow (si->dpy, ssi->screensaver_window); + if (!dont_clear || ssi->stderr_overlay_window) + clear_stderr (ssi); + XMapRaised (si->dpy, ssi->screensaver_window); +#ifdef HAVE_MIT_SAVER_EXTENSION + if (ssi->server_mit_saver_window && + window_exists_p (si->dpy, ssi->server_mit_saver_window)) + XUnmapWindow (si->dpy, ssi->server_mit_saver_window); +#endif /* HAVE_MIT_SAVER_EXTENSION */ + } + } + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->cmap) + XInstallColormap (si->dpy, ssi->cmap); + } +} + + +int +mouse_screen (saver_info *si) +{ + saver_preferences *p = &si->prefs; + Window pointer_root, pointer_child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + int i; + + if (si->nscreens == 1) + return 0; + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (XQueryPointer (si->dpy, RootWindowOfScreen (ssi->screen), + &pointer_root, &pointer_child, + &root_x, &root_y, &win_x, &win_y, &mask) && + root_x >= ssi->x && + root_y >= ssi->y && + root_x < ssi->x + ssi->width && + root_y < ssi->y + ssi->height) + { + if (p->verbose_p) + fprintf (stderr, "%s: mouse is on screen %d of %d\n", + blurb(), i, si->nscreens); + return i; + } + } + + /* couldn't figure out where the mouse is? Oh well. */ + return 0; +} + + +Bool +blank_screen (saver_info *si) +{ + int i; + Bool ok; + Window w; + int mscreen; + + /* Note: we do our grabs on the root window, not on the screensaver window. + If we grabbed on the saver window, then the demo mode and lock dialog + boxes wouldn't get any events. + + By "the root window", we mean "the root window that contains the mouse." + We use to always grab the mouse on screen 0, but that has the effect of + moving the mouse to screen 0 from whichever screen it was on, on + multi-head systems. + */ + mscreen = mouse_screen (si); + w = RootWindowOfScreen(si->screens[mscreen].screen); + ok = grab_keyboard_and_mouse (si, w, + (si->demoing_p ? 0 : si->screens[0].cursor), + mscreen); + + +# if 0 + if (si->using_mit_saver_extension || si->using_sgi_saver_extension) + /* If we're using a server extension, then failure to get a grab is + not a big deal -- even without the grab, we will still be able + to un-blank when there is user activity, since the server will + tell us. */ + /* #### No, that's not true: if we don't have a keyboard grab, + then we can't read passwords to unlock. + */ + ok = True; +# endif /* 0 */ + + if (!ok) + return False; + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->real_screen_p) + save_real_vroot (ssi); + store_vroot_property (si->dpy, + ssi->screensaver_window, + ssi->screensaver_window); + +#ifdef HAVE_XF86VMODE + { + int ev, er; + if (!XF86VidModeQueryExtension (si->dpy, &ev, &er) || + !safe_XF86VidModeGetViewPort (si->dpy, i, + &ssi->blank_vp_x, + &ssi->blank_vp_y)) + ssi->blank_vp_x = ssi->blank_vp_y = -1; + } +#endif /* HAVE_XF86VMODE */ + } + + raise_window (si, False, False, False); + + si->screen_blanked_p = True; + si->blank_time = time ((time_t) 0); + si->last_wall_clock_time = 0; + + store_saver_status (si); /* store blank time */ + + return True; +} + + +void +unblank_screen (saver_info *si) +{ + saver_preferences *p = &si->prefs; + Bool unfade_p = (si->fading_possible_p && p->unfade_p); + int i; + + monitor_power_on (si); + reset_watchdog_timer (si, False); + + if (si->demoing_p) + unfade_p = False; + + if (unfade_p) + { + Window *current_windows = (Window *) + calloc(sizeof(Window), si->nscreens); + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + current_windows[i] = ssi->screensaver_window; + /* Ensure that the default background of the window is really black, + not a pixmap or something. (This does not clear the window.) */ + XSetWindowBackground (si->dpy, ssi->screensaver_window, + ssi->black_pixel); + } + + if (p->verbose_p) fprintf (stderr, "%s: unfading...\n", blurb()); + + + XSync (si->dpy, False); + XGrabServer (si->dpy); /* ############ DANGER! */ + XSync (si->dpy, False); + + /* Clear the stderr layer on each screen. + */ + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + clear_stderr (ssi); + } + + XUngrabServer (si->dpy); + XSync (si->dpy, False); /* ###### (danger over) */ + + fade_screens (si->dpy, 0, + current_windows, si->nscreens, + p->fade_seconds/1000, p->fade_ticks, + False, False); + + free(current_windows); + current_windows = 0; + + if (p->verbose_p) fprintf (stderr, "%s: unfading done.\n", blurb()); + } + else + { + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->cmap) + { + Colormap c = DefaultColormapOfScreen (ssi->screen); + /* avoid technicolor */ + XClearWindow (si->dpy, ssi->screensaver_window); + if (c) XInstallColormap (si->dpy, c); + } + XUnmapWindow (si->dpy, ssi->screensaver_window); + } + } + + + /* If the focus window does has a non-default colormap, then install + that colormap as well. (On SGIs, this will cause both the root map + and the focus map to be installed simultaneously. It'd be nice to + pick up the other colormaps that had been installed, too; perhaps + XListInstalledColormaps could be used for that?) + */ + { + Window focus = 0; + int revert_to; + XGetInputFocus (si->dpy, &focus, &revert_to); + if (focus && focus != PointerRoot && focus != None) + { + XWindowAttributes xgwa; + xgwa.colormap = 0; + XGetWindowAttributes (si->dpy, focus, &xgwa); + if (xgwa.colormap && + xgwa.colormap != DefaultColormapOfScreen (xgwa.screen)) + XInstallColormap (si->dpy, xgwa.colormap); + } + } + + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + kill_xsetroot_data (si->dpy, ssi->screensaver_window, p->verbose_p); + } + + store_saver_status (si); /* store unblank time */ + ungrab_keyboard_and_mouse (si); + restore_real_vroot (si); + + /* Unmap the windows a second time, dammit -- just to avoid a race + with the screen-grabbing hacks. (I'm not sure if this is really + necessary; I'm stabbing in the dark now.) + */ + for (i = 0; i < si->nscreens; i++) + XUnmapWindow (si->dpy, si->screens[i].screensaver_window); + + si->screen_blanked_p = False; + si->blank_time = time ((time_t) 0); + si->last_wall_clock_time = 0; + + store_saver_status (si); /* store unblank time */ +} + + +/* Transfer any grabs from the old window to the new. + Actually I think none of this is necessary, since we always + hold our grabs on the root window, but I wrote this before + re-discovering that... + */ +static void +maybe_transfer_grabs (saver_screen_info *ssi, + Window old_w, Window new_w, + int new_screen_no) +{ + saver_info *si = ssi->global; + + /* If the old window held our mouse grab, transfer the grab to the new + window. (Grab the server while so doing, to avoid a race condition.) + */ + if (old_w == si->mouse_grab_window) + { + XGrabServer (si->dpy); /* ############ DANGER! */ + ungrab_mouse (si); + grab_mouse (si, ssi->screensaver_window, + (si->demoing_p ? 0 : ssi->cursor), + new_screen_no); + XUngrabServer (si->dpy); + XSync (si->dpy, False); /* ###### (danger over) */ + } + + /* If the old window held our keyboard grab, transfer the grab to the new + window. (Grab the server while so doing, to avoid a race condition.) + */ + if (old_w == si->keyboard_grab_window) + { + XGrabServer (si->dpy); /* ############ DANGER! */ + ungrab_kbd(si); + grab_kbd(si, ssi->screensaver_window, ssi->number); + XUngrabServer (si->dpy); + XSync (si->dpy, False); /* ###### (danger over) */ + } +} + + +static Visual * +get_screen_gl_visual (saver_info *si, int real_screen_number) +{ + int i; + int nscreens = ScreenCount (si->dpy); + + if (! si->best_gl_visuals) + si->best_gl_visuals = (Visual **) + calloc (nscreens + 1, sizeof (*si->best_gl_visuals)); + + for (i = 0; i < nscreens; i++) + if (! si->best_gl_visuals[i]) + si->best_gl_visuals[i] = + get_best_gl_visual (si, ScreenOfDisplay (si->dpy, i)); + + if (real_screen_number < 0 || real_screen_number >= nscreens) abort(); + return si->best_gl_visuals[real_screen_number]; +} + + +Bool +select_visual (saver_screen_info *ssi, const char *visual_name) +{ + XWindowAttributes xgwa; + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + Bool install_cmap_p = p->install_cmap_p; + Bool was_installed_p = (ssi->cmap != DefaultColormapOfScreen(ssi->screen)); + Visual *new_v = 0; + Bool got_it; + + /* On some systems (most recently, MacOS X) OpenGL programs get confused + when you kill one and re-start another on the same window. So maybe + it's best to just always destroy and recreate the xscreensaver window + when changing hacks, instead of trying to reuse the old one? + */ + Bool always_recreate_window_p = True; + + get_screen_gl_visual (si, 0); /* let's probe all the GL visuals early */ + + /* We make sure the existing window is actually on ssi->screen before + trying to use it, in case things moved around radically when monitors + were added or deleted. If we don't do this we could get a BadMatch + even though the depths match. I think. + */ + memset (&xgwa, 0, sizeof(xgwa)); + if (ssi->screensaver_window) + XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa); + + if (visual_name && *visual_name) + { + if (!strcmp(visual_name, "default-i") || + !strcmp(visual_name, "Default-i") || + !strcmp(visual_name, "Default-I") + ) + { + visual_name = "default"; + install_cmap_p = True; + } + else if (!strcmp(visual_name, "default-n") || + !strcmp(visual_name, "Default-n") || + !strcmp(visual_name, "Default-N")) + { + visual_name = "default"; + install_cmap_p = False; + } + else if (!strcmp(visual_name, "gl") || + !strcmp(visual_name, "Gl") || + !strcmp(visual_name, "GL")) + { + new_v = get_screen_gl_visual (si, ssi->real_screen_number); + if (!new_v && p->verbose_p) + fprintf (stderr, "%s: no GL visuals.\n", progname); + } + + if (!new_v) + new_v = get_visual (ssi->screen, visual_name, True, False); + } + else + { + new_v = ssi->default_visual; + } + + got_it = !!new_v; + + if (new_v && new_v != DefaultVisualOfScreen(ssi->screen)) + /* It's not the default visual, so we have no choice but to install. */ + install_cmap_p = True; + + ssi->install_cmap_p = install_cmap_p; + + if ((ssi->screen != xgwa.screen) || + (new_v && + (always_recreate_window_p || + (ssi->current_visual != new_v) || + (install_cmap_p != was_installed_p)))) + { + Colormap old_c = ssi->cmap; + Window old_w = ssi->screensaver_window; + if (! new_v) + new_v = ssi->current_visual; + + if (p->verbose_p) + { + fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number); + describe_visual (stderr, ssi->screen, new_v, install_cmap_p); +#if 0 + fprintf (stderr, "%s: from ", blurb()); + describe_visual (stderr, ssi->screen, ssi->current_visual, + was_installed_p); +#endif + } + + reset_stderr (ssi); + ssi->current_visual = new_v; + ssi->current_depth = visual_depth(ssi->screen, new_v); + ssi->cmap = 0; + ssi->screensaver_window = 0; + + initialize_screensaver_window_1 (ssi); + + /* stderr_overlay_window is a child of screensaver_window, so we need + to destroy that as well (actually, we just need to invalidate and + drop our pointers to it, but this will destroy it, which is ok so + long as it happens before old_w itself is destroyed.) */ + reset_stderr (ssi); + + raise_window (si, True, True, False); + store_vroot_property (si->dpy, + ssi->screensaver_window, ssi->screensaver_window); + + /* Transfer any grabs from the old window to the new. */ + maybe_transfer_grabs (ssi, old_w, ssi->screensaver_window, ssi->number); + + /* Now we can destroy the old window without horking our grabs. */ + XDestroyWindow (si->dpy, old_w); + + if (p->verbose_p) + fprintf (stderr, "%s: %d: destroyed old saver window 0x%lx.\n", + blurb(), ssi->number, (unsigned long) old_w); + + if (old_c && + old_c != DefaultColormapOfScreen (ssi->screen) && + old_c != ssi->demo_cmap) + XFreeColormap (si->dpy, old_c); + } + + return got_it; +} diff --git a/driver/xdpyinfo.c b/driver/xdpyinfo.c new file mode 100644 index 00000000..7a0b0783 --- /dev/null +++ b/driver/xdpyinfo.c @@ -0,0 +1,1085 @@ +/* + * $ TOG: xdpyinfo.c /main/35 1998/02/09 13:57:05 kaleb $ + * + * xdpyinfo - print information about X display connecton + * + * +Copyright 1988, 1998 The Open Group + +All Rights Reserved. + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of The Open Group shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from The Open Group. + * + * Author: Jim Fulton, MIT X Consortium + * + * GLX and Overlay support added by Jamie Zawinski , 11-Nov-99 + * + * To compile: + * cc -DHAVE_GLX xdpyinfo.c -o xdpyinfo -lGL -lX11 -lXext [-lXtst] -lm + * + * Other defines to consider: + * -DMITSHM -DHAVE_XDBE -DHAVE_XIE -DHAVE_XTEST -DHAVE_SYNC + * -DHAVE_XRECORD + */ + +#include +#include +#include /* for CARD32 */ +#include +#ifdef HAVE_XIE +#include +#endif /* HAVE_XIE */ +#ifdef HAVE_XTEST +#include +#endif /* HAVE_XTEST */ +#ifdef HAVE_XSYNC +#include +#endif /* HAVE_XSYNC */ +#ifdef HAVE_XDBE +#include +#endif /* HAVE_XDBE */ +#ifdef HAVE_XRECORD +#include +#endif /* HAVE_XRECORD */ +#ifdef MITSHM +#include +#endif +#include +#include + +#ifdef HAVE_GLX +# include +# include +#endif /* HAVE_GLX */ + +#define HAVE_OVERLAY /* jwz: no compile-time deps, so do this all the time */ + +char *ProgramName; +Bool queryExtensions = False; + +static int StrCmp(a, b) + char **a, **b; +{ + return strcmp(*a, *b); +} + + +#ifdef HAVE_GLX /* Added by jwz, 11-Nov-99 */ + +static void +print_glx_versions (dpy) + Display *dpy; +{ + /* Note: with Mesa 3.0, this lies: it prints the info from the + client's GL library, rather than the info from the GLX server. + + Note also that we can't protect these calls by only doing + them when the GLX extension is present, because with Mesa, + the server doesn't have that extension (but the GL library + works anyway.) + */ + int scr = DefaultScreen (dpy); + const char *vend, *vers; + vend = glXQueryServerString (dpy, scr, GLX_VENDOR); + if (!vend) return; + vers = glXQueryServerString (dpy, scr, GLX_VERSION); + printf ("GLX vendor: %s (%s)\n", + vend, (vers ? vers : "unknown version")); +} + +static void +print_glx_visual_info (dpy, vip) + Display *dpy; + XVisualInfo *vip; +{ + int status, value = False; + + status = glXGetConfig (dpy, vip, GLX_USE_GL, &value); + if (status == GLX_NO_EXTENSION) + /* dpy does not support the GLX extension. */ + return; + + if (status == GLX_BAD_VISUAL || value == False) + { + printf (" GLX supported: no\n"); + return; + } + else + { + printf (" GLX supported: yes\n"); + } + + if (!glXGetConfig (dpy, vip, GLX_LEVEL, &value) && + value != 0) + printf (" GLX level: %d\n", value); + + if (!glXGetConfig (dpy, vip, GLX_RGBA, &value) && value) + { + int r=0, g=0, b=0, a=0; + glXGetConfig (dpy, vip, GLX_RED_SIZE, &r); + glXGetConfig (dpy, vip, GLX_GREEN_SIZE, &g); + glXGetConfig (dpy, vip, GLX_BLUE_SIZE, &b); + glXGetConfig (dpy, vip, GLX_ALPHA_SIZE, &a); + printf (" GLX type: RGBA (%2d, %2d, %2d, %2d)\n", + r, g, b, a); + + r=0, g=0, b=0, a=0; + glXGetConfig (dpy, vip, GLX_ACCUM_RED_SIZE, &r); + glXGetConfig (dpy, vip, GLX_ACCUM_GREEN_SIZE, &g); + glXGetConfig (dpy, vip, GLX_ACCUM_BLUE_SIZE, &b); + glXGetConfig (dpy, vip, GLX_ACCUM_ALPHA_SIZE, &a); + printf (" GLX accum: RGBA (%2d, %2d, %2d, %2d)\n", + r, g, b, a); + } + else + { + value = 0; + glXGetConfig (dpy, vip, GLX_BUFFER_SIZE, &value); + printf (" GLX type: indexed (%d)\n", value); + } + +# if 0 /* redundant */ + if (!glXGetConfig (dpy, vip, GLX_X_VISUAL_TYPE_EXT, &value)) + printf (" GLX class: %s\n", + (value == GLX_TRUE_COLOR_EXT ? "TrueColor" : + value == GLX_DIRECT_COLOR_EXT ? "DirectColor" : + value == GLX_PSEUDO_COLOR_EXT ? "PseudoColor" : + value == GLX_STATIC_COLOR_EXT ? "StaticColor" : + value == GLX_GRAY_SCALE_EXT ? "Grayscale" : + value == GLX_STATIC_GRAY_EXT ? "StaticGray" : "???")); +# endif + +# ifdef GLX_VISUAL_CAVEAT_EXT + if (!glXGetConfig (dpy, vip, GLX_VISUAL_CAVEAT_EXT, &value) && + value != GLX_NONE_EXT) + printf (" GLX rating: %s\n", + (value == GLX_NONE_EXT ? "none" : + value == GLX_SLOW_VISUAL_EXT ? "slow" : +# ifdef GLX_NON_CONFORMANT_EXT + value == GLX_NON_CONFORMANT_EXT ? "non-conformant" : +# endif + "???")); +# endif + + if (!glXGetConfig (dpy, vip, GLX_DOUBLEBUFFER, &value)) + printf (" GLX double-buffer: %s\n", (value ? "yes" : "no")); + + if (!glXGetConfig (dpy, vip, GLX_STEREO, &value) && + value) + printf (" GLX stereo: %s\n", (value ? "yes" : "no")); + + if (!glXGetConfig (dpy, vip, GLX_AUX_BUFFERS, &value) && + value != 0) + printf (" GLX aux buffers: %d\n", value); + + if (!glXGetConfig (dpy, vip, GLX_DEPTH_SIZE, &value)) + printf (" GLX depth size: %d\n", value); + + if (!glXGetConfig (dpy, vip, GLX_STENCIL_SIZE, &value) && + value != 0) + printf (" GLX stencil size: %d\n", value); + +# ifdef GLX_SAMPLE_BUFFERS_SGIS + if (!glXGetConfig (dpy, vip, GLX_SAMPLE_BUFFERS_SGIS, &value) && + value != 0) + { + int bufs = value; + if (!glXGetConfig (dpy, vip, GLX_SAMPLES_SGIS, &value)) + printf (" GLX multisamplers: %d (%d)\n", bufs, value); + } +# endif + + if (!glXGetConfig (dpy, vip, GLX_TRANSPARENT_TYPE_EXT, &value) && + value != GLX_NONE_EXT) + { + if (value == GLX_NONE_EXT) + printf (" GLX transparency: none\n"); + else if (value == GLX_TRANSPARENT_INDEX_EXT) + { + if (!glXGetConfig (dpy, vip, GLX_TRANSPARENT_INDEX_VALUE_EXT,&value)) + printf (" GLX transparency: indexed (%d)\n", value); + } + else if (value == GLX_TRANSPARENT_RGB_EXT) + { + int r=0, g=0, b=0, a=0; + glXGetConfig (dpy, vip, GLX_TRANSPARENT_RED_VALUE_EXT, &r); + glXGetConfig (dpy, vip, GLX_TRANSPARENT_GREEN_VALUE_EXT, &g); + glXGetConfig (dpy, vip, GLX_TRANSPARENT_BLUE_VALUE_EXT, &b); + glXGetConfig (dpy, vip, GLX_TRANSPARENT_ALPHA_VALUE_EXT, &a); + printf (" GLX transparency: RGBA (%2d, %2d, %2d, %2d)\n", + r, g, b, a); + } + } +} +#endif /* HAVE_GLX */ + + +#ifdef HAVE_OVERLAY /* Added by jwz, 11-Nov-99 */ + + /* If the server's root window contains a SERVER_OVERLAY_VISUALS property, + then that identifies the visuals which correspond to the video hardware's + overlay planes. Windows created in these kinds of visuals may have + transparent pixels that let other layers shine through. + + This might not be an X Consortium standard, but it turns out that + SGI, HP, DEC, and IBM all use this same mechanism. So that's close + enough for me. + + Documentation on the SERVER_OVERLAY_VISUALS property can be found at: + http://www.hp.com/xwindow/sharedInfo/Whitepapers/Visuals/server_overlay_visuals.html + */ + +struct overlay +{ + CARD32 visual_id; + CARD32 transparency; /* 0: none; 1: pixel; 2: mask */ + CARD32 value; /* the transparent pixel */ + CARD32 layer; /* -1: underlay; 0: normal; 1: popup; 2: overlay */ +}; + +struct overlay_list +{ + int count; + struct overlay *list; +}; + +static struct overlay_list *overlays = 0; + +static void +find_overlay_info (dpy) + Display *dpy; +{ + int screen; + Atom OVERLAY = XInternAtom (dpy, "SERVER_OVERLAY_VISUALS", False); + + overlays = (struct overlay_list *) calloc (sizeof (struct overlay_list), + ScreenCount (dpy)); + + for (screen = 0; screen < ScreenCount (dpy); screen++) + { + Window window = RootWindow (dpy, screen); + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + struct overlay *data = 0; + int result = XGetWindowProperty (dpy, window, OVERLAY, + 0, (65536 / sizeof (long)), False, + OVERLAY, &actual_type, &actual_format, + &nitems, &bytes_after, + (unsigned char **) &data); + if (result == Success && + actual_type == OVERLAY && + actual_format == 32 && + nitems > 0) + { + overlays[screen].count = (nitems / + (sizeof(struct overlay) / sizeof(CARD32))); + overlays[screen].list = data; + } + else if (data) + XFree((char *) data); + } +} + +static void +print_overlay_visual_info (vip) + XVisualInfo *vip; +{ + int i; + int vis = vip->visualid; + int scr = vip->screen; + if (!overlays) return; + for (i = 0; i < overlays[scr].count; i++) + if (vis == overlays[scr].list[i].visual_id) + { + struct overlay *ov = &overlays[scr].list[i]; + printf (" Overlay info: layer %ld (%s), ", + (long) ov->layer, + (ov->layer == -1 ? "underlay" : + ov->layer == 0 ? "normal" : + ov->layer == 1 ? "popup" : + ov->layer == 2 ? "overlay" : "???")); + if (ov->transparency == 1) + printf ("transparent pixel %lu\n", (unsigned long) ov->value); + else if (ov->transparency == 2) + printf ("transparent mask 0x%x\n", (unsigned long) ov->value); + else + printf ("opaque\n"); + } +} +#endif /* HAVE_OVERLAY */ + + +void +print_extension_info (dpy) + Display *dpy; +{ + int n = 0; + char **extlist = XListExtensions (dpy, &n); + + printf ("number of extensions: %d\n", n); + + if (extlist) { + register int i; + int opcode, event, error; + + qsort(extlist, n, sizeof(char *), StrCmp); + for (i = 0; i < n; i++) { + if (!queryExtensions) { + printf (" %s\n", extlist[i]); + continue; + } + XQueryExtension(dpy, extlist[i], &opcode, &event, &error); + printf (" %s (opcode: %d", extlist[i], opcode); + if (event) + printf (", base event: %d", event); + if (error) + printf (", base error: %d", error); + printf(")\n"); + } + /* do not free, Xlib can depend on contents being unaltered */ + /* XFreeExtensionList (extlist); */ + } +} + +void +print_display_info (dpy) + Display *dpy; +{ + char dummybuf[40]; + char *cp; + int minkeycode, maxkeycode; + int i, n; + long req_size; + XPixmapFormatValues *pmf; + Window focuswin; + int focusrevert; + + printf ("name of display: %s\n", DisplayString (dpy)); + printf ("version number: %d.%d\n", + ProtocolVersion (dpy), ProtocolRevision (dpy)); + printf ("vendor string: %s\n", ServerVendor (dpy)); + printf ("vendor release number: %d\n", VendorRelease (dpy)); + +#ifdef HAVE_GLX + print_glx_versions (dpy); +#endif /* HAVE_GLX */ + + req_size = XExtendedMaxRequestSize (dpy); + if (!req_size) req_size = XMaxRequestSize (dpy); + printf ("maximum request size: %ld bytes\n", req_size * 4); + printf ("motion buffer size: %d\n", XDisplayMotionBufferSize (dpy)); + + switch (BitmapBitOrder (dpy)) { + case LSBFirst: cp = "LSBFirst"; break; + case MSBFirst: cp = "MSBFirst"; break; + default: + sprintf (dummybuf, "unknown order %d", BitmapBitOrder (dpy)); + cp = dummybuf; + break; + } + printf ("bitmap unit, bit order, padding: %d, %s, %d\n", + BitmapUnit (dpy), cp, BitmapPad (dpy)); + + switch (ImageByteOrder (dpy)) { + case LSBFirst: cp = "LSBFirst"; break; + case MSBFirst: cp = "MSBFirst"; break; + default: + sprintf (dummybuf, "unknown order %d", ImageByteOrder (dpy)); + cp = dummybuf; + break; + } + printf ("image byte order: %s\n", cp); + + pmf = XListPixmapFormats (dpy, &n); + printf ("number of supported pixmap formats: %d\n", n); + if (pmf) { + printf ("supported pixmap formats:\n"); + for (i = 0; i < n; i++) { + printf (" depth %d, bits_per_pixel %d, scanline_pad %d\n", + pmf[i].depth, pmf[i].bits_per_pixel, pmf[i].scanline_pad); + } + XFree ((char *) pmf); + } + + + /* + * when we get interfaces to the PixmapFormat stuff, insert code here + */ + + XDisplayKeycodes (dpy, &minkeycode, &maxkeycode); + printf ("keycode range: minimum %d, maximum %d\n", + minkeycode, maxkeycode); + + XGetInputFocus (dpy, &focuswin, &focusrevert); + printf ("focus: "); + switch (focuswin) { + case PointerRoot: + printf ("PointerRoot\n"); + break; + case None: + printf ("None\n"); + break; + default: + printf("window 0x%lx, revert to ", focuswin); + switch (focusrevert) { + case RevertToParent: + printf ("Parent\n"); + break; + case RevertToNone: + printf ("None\n"); + break; + case RevertToPointerRoot: + printf ("PointerRoot\n"); + break; + default: /* should not happen */ + printf ("%d\n", focusrevert); + break; + } + break; + } + + print_extension_info (dpy); + + printf ("default screen number: %d\n", DefaultScreen (dpy)); + printf ("number of screens: %d\n", ScreenCount (dpy)); +} + +void +print_visual_info (vip) + XVisualInfo *vip; +{ + char errorbuf[40]; /* for sprintfing into */ + char *class = NULL; /* for printing */ + + switch (vip->class) { + case StaticGray: class = "StaticGray"; break; + case GrayScale: class = "GrayScale"; break; + case StaticColor: class = "StaticColor"; break; + case PseudoColor: class = "PseudoColor"; break; + case TrueColor: class = "TrueColor"; break; + case DirectColor: class = "DirectColor"; break; + default: + sprintf (errorbuf, "unknown class %d", vip->class); + class = errorbuf; + break; + } + + printf (" visual:\n"); + printf (" visual id: 0x%lx\n", vip->visualid); + printf (" class: %s\n", class); + printf (" depth: %d plane%s\n", vip->depth, + vip->depth == 1 ? "" : "s"); + if (vip->class == TrueColor || vip->class == DirectColor) + printf (" available colormap entries: %d per subfield\n", + vip->colormap_size); + else + printf (" available colormap entries: %d\n", + vip->colormap_size); + printf (" red, green, blue masks: 0x%lx, 0x%lx, 0x%lx\n", + vip->red_mask, vip->green_mask, vip->blue_mask); + printf (" significant bits in color specification: %d bits\n", + vip->bits_per_rgb); +} + +void +print_screen_info (dpy, scr) + Display *dpy; + int scr; +{ + Screen *s = ScreenOfDisplay (dpy, scr); /* opaque structure */ + XVisualInfo viproto; /* fill in for getting info */ + XVisualInfo *vip; /* retured info */ + int nvi; /* number of elements returned */ + int i; /* temp variable: iterator */ + char eventbuf[80]; /* want 79 chars per line + nul */ + static char *yes = "YES", *no = "NO", *when = "WHEN MAPPED"; + double xres, yres; + int ndepths = 0, *depths = NULL; + unsigned int width, height; + + + /* + * there are 2.54 centimeters to an inch; so there are 25.4 millimeters. + * + * dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) + * = N pixels / (M inch / 25.4) + * = N * 25.4 pixels / M inch + */ + + xres = ((((double) DisplayWidth(dpy,scr)) * 25.4) / + ((double) DisplayWidthMM(dpy,scr))); + yres = ((((double) DisplayHeight(dpy,scr)) * 25.4) / + ((double) DisplayHeightMM(dpy,scr))); + + printf ("\n"); + printf ("screen #%d:\n", scr); + printf (" dimensions: %dx%d pixels (%dx%d millimeters)\n", + DisplayWidth (dpy, scr), DisplayHeight (dpy, scr), + DisplayWidthMM(dpy, scr), DisplayHeightMM (dpy, scr)); + printf (" resolution: %dx%d dots per inch\n", + (int) (xres + 0.5), (int) (yres + 0.5)); + depths = XListDepths (dpy, scr, &ndepths); + if (!depths) ndepths = 0; + printf (" depths (%d): ", ndepths); + for (i = 0; i < ndepths; i++) { + printf ("%d", depths[i]); + if (i < ndepths - 1) { + putchar (','); + putchar (' '); + } + } + putchar ('\n'); + if (depths) XFree ((char *) depths); + printf (" root window id: 0x%lx\n", RootWindow (dpy, scr)); + printf (" depth of root window: %d plane%s\n", + DisplayPlanes (dpy, scr), + DisplayPlanes (dpy, scr) == 1 ? "" : "s"); + printf (" number of colormaps: minimum %d, maximum %d\n", + MinCmapsOfScreen(s), MaxCmapsOfScreen(s)); + printf (" default colormap: 0x%lx\n", DefaultColormap (dpy, scr)); + printf (" default number of colormap cells: %d\n", + DisplayCells (dpy, scr)); + printf (" preallocated pixels: black %d, white %d\n", + BlackPixel (dpy, scr), WhitePixel (dpy, scr)); + printf (" options: backing-store %s, save-unders %s\n", + (DoesBackingStore (s) == NotUseful) ? no : + ((DoesBackingStore (s) == Always) ? yes : when), + DoesSaveUnders (s) ? yes : no); + XQueryBestSize (dpy, CursorShape, RootWindow (dpy, scr), 65535, 65535, + &width, &height); + if (width == 65535 && height == 65535) + printf (" largest cursor: unlimited\n"); + else + printf (" largest cursor: %dx%d\n", width, height); + printf (" current input event mask: 0x%lx\n", EventMaskOfScreen (s)); + (void) print_event_mask (eventbuf, 79, 4, EventMaskOfScreen (s)); + + + nvi = 0; + viproto.screen = scr; + vip = XGetVisualInfo (dpy, VisualScreenMask, &viproto, &nvi); + printf (" number of visuals: %d\n", nvi); + printf (" default visual id: 0x%lx\n", + XVisualIDFromVisual (DefaultVisual (dpy, scr))); + for (i = 0; i < nvi; i++) { + print_visual_info (vip+i); +#ifdef HAVE_OVERLAY + print_overlay_visual_info (vip+i); +#endif /* HAVE_OVERLAY */ +#ifdef HAVE_GLX + print_glx_visual_info (dpy, vip+i); +#endif /* HAVE_GLX */ + } + if (vip) XFree ((char *) vip); +} + +/* + * The following routine prints out an event mask, wrapping events at nice + * boundaries. + */ + +#define MASK_NAME_WIDTH 25 + +static struct _event_table { + char *name; + long value; +} event_table[] = { + { "KeyPressMask ", KeyPressMask }, + { "KeyReleaseMask ", KeyReleaseMask }, + { "ButtonPressMask ", ButtonPressMask }, + { "ButtonReleaseMask ", ButtonReleaseMask }, + { "EnterWindowMask ", EnterWindowMask }, + { "LeaveWindowMask ", LeaveWindowMask }, + { "PointerMotionMask ", PointerMotionMask }, + { "PointerMotionHintMask ", PointerMotionHintMask }, + { "Button1MotionMask ", Button1MotionMask }, + { "Button2MotionMask ", Button2MotionMask }, + { "Button3MotionMask ", Button3MotionMask }, + { "Button4MotionMask ", Button4MotionMask }, + { "Button5MotionMask ", Button5MotionMask }, + { "ButtonMotionMask ", ButtonMotionMask }, + { "KeymapStateMask ", KeymapStateMask }, + { "ExposureMask ", ExposureMask }, + { "VisibilityChangeMask ", VisibilityChangeMask }, + { "StructureNotifyMask ", StructureNotifyMask }, + { "ResizeRedirectMask ", ResizeRedirectMask }, + { "SubstructureNotifyMask ", SubstructureNotifyMask }, + { "SubstructureRedirectMask ", SubstructureRedirectMask }, + { "FocusChangeMask ", FocusChangeMask }, + { "PropertyChangeMask ", PropertyChangeMask }, + { "ColormapChangeMask ", ColormapChangeMask }, + { "OwnerGrabButtonMask ", OwnerGrabButtonMask }, + { NULL, 0 }}; + +int print_event_mask (buf, lastcol, indent, mask) + char *buf; /* string to write into */ + int lastcol; /* strlen(buf)+1 */ + int indent; /* amount by which to indent */ + long mask; /* event mask */ +{ + struct _event_table *etp; + int len; + int bitsfound = 0; + + buf[0] = buf[lastcol] = '\0'; /* just in case */ + +#define INDENT() { register int i; len = indent; \ + for (i = 0; i < indent; i++) buf[i] = ' '; } + + INDENT (); + + for (etp = event_table; etp->name; etp++) { + if (mask & etp->value) { + if (len + MASK_NAME_WIDTH > lastcol) { + puts (buf); + INDENT (); + } + strcpy (buf+len, etp->name); + len += MASK_NAME_WIDTH; + bitsfound++; + } + } + + if (bitsfound) puts (buf); + +#undef INDENT + + return (bitsfound); +} + +void +print_standard_extension_info(dpy, extname, majorrev, minorrev) + Display *dpy; + char *extname; + int majorrev, minorrev; +{ + int opcode, event, error; + + printf("%s version %d.%d ", extname, majorrev, minorrev); + + XQueryExtension(dpy, extname, &opcode, &event, &error); + printf ("opcode: %d", opcode); + if (event) + printf (", base event: %d", event); + if (error) + printf (", base error: %d", error); + printf("\n"); +} + +int +print_multibuf_info(dpy, extname) + Display *dpy; + char *extname; +{ + int i, j; /* temp variable: iterator */ + int nmono, nstereo; /* count */ + XmbufBufferInfo *mono_info = NULL, *stereo_info = NULL; /* arrays */ + static char *fmt = + " visual id, max buffers, depth: 0x%lx, %d, %d\n"; + int scr = 0; + int majorrev, minorrev; + + if (!XmbufGetVersion(dpy, &majorrev, &minorrev)) + return 0; + + print_standard_extension_info(dpy, extname, majorrev, minorrev); + + for (i = 0; i < ScreenCount (dpy); i++) + { + if (!XmbufGetScreenInfo (dpy, RootWindow(dpy, scr), &nmono, &mono_info, + &nstereo, &stereo_info)) { + fprintf (stderr, + "%s: unable to get multibuffer info for screen %d\n", + ProgramName, scr); + } else { + printf (" screen %d number of mono multibuffer types: %d\n", i, nmono); + for (j = 0; j < nmono; j++) { + printf (fmt, mono_info[j].visualid, mono_info[j].max_buffers, + mono_info[j].depth); + } + printf (" number of stereo multibuffer types: %d\n", nstereo); + for (j = 0; j < nstereo; j++) { + printf (fmt, stereo_info[j].visualid, + stereo_info[j].max_buffers, stereo_info[j].depth); + } + if (mono_info) XFree ((char *) mono_info); + if (stereo_info) XFree ((char *) stereo_info); + } + } + return 1; +} /* end print_multibuf_info */ + + +/* XIE stuff */ + +#ifdef HAVE_XIE + +char *subset_names[] = { NULL, "FULL", "DIS" }; +char *align_names[] = { NULL, "Alignable", "Arbitrary" }; +char *group_names[] = { /* 0 */ "Default", + /* 2 */ "ColorAlloc", + /* 4 */ "Constrain", + /* 6 */ "ConvertFromRGB", + /* 8 */ "ConvertToRGB", + /* 10 */ "Convolve", + /* 12 */ "Decode", + /* 14 */ "Dither", + /* 16 */ "Encode", + /* 18 */ "Gamut", + /* 20 */ "Geometry", + /* 22 */ "Histogram", + /* 24 */ "WhiteAdjust" + }; + +int +print_xie_info(dpy, extname) + Display *dpy; + char *extname; +{ + XieExtensionInfo *xieInfo; + int i; + int ntechs; + XieTechnique *techs; + XieTechniqueGroup prevGroup; + + if (!XieInitialize(dpy, &xieInfo )) + return 0; + + print_standard_extension_info(dpy, extname, + xieInfo->server_major_rev, xieInfo->server_minor_rev); + + printf(" service class: %s\n", subset_names[xieInfo->service_class]); + printf(" alignment: %s\n", align_names[xieInfo->alignment]); + printf(" uncnst_mantissa: %d\n", xieInfo->uncnst_mantissa); + printf(" uncnst_min_exp: %d\n", xieInfo->uncnst_min_exp); + printf(" uncnst_max_exp: %d\n", xieInfo->uncnst_max_exp); + printf(" cnst_levels:"); + for (i = 0; i < xieInfo->n_cnst_levels; i++) + printf(" %d", xieInfo->cnst_levels[i]); + printf("\n"); + + if (!XieQueryTechniques(dpy, xieValAll, &ntechs, &techs)) + return 1; + + prevGroup = -1; + + for (i = 0; i < ntechs; i++) + { + if (techs[i].group != prevGroup) + { + printf(" technique group: %s\n", group_names[techs[i].group >> 1]); + prevGroup = techs[i].group; + } + printf(" %s\tspeed: %d needs_param: %s number: %d\n", + techs[i].name, + techs[i].speed, (techs[i].needs_param ? "True " : "False"), + techs[i].number); + } + return 1; +} /* end print_xie_info */ + +#endif /* HAVE_XIE */ + + +#ifdef HAVE_XTEST +int +print_xtest_info(dpy, extname) + Display *dpy; + char *extname; +{ + int majorrev, minorrev, foo; + + if (!XTestQueryExtension(dpy, &foo, &foo, &majorrev, &minorrev)) + return 0; + print_standard_extension_info(dpy, extname, majorrev, minorrev); + return 1; +} +#endif /* HAVE_XTEST */ + +#ifdef HAVE_XSYNC +int +print_sync_info(dpy, extname) + Display *dpy; + char *extname; +{ + int majorrev, minorrev; + XSyncSystemCounter *syscounters; + int ncounters, i; + + if (!XSyncInitialize(dpy, &majorrev, &minorrev)) + return 0; + print_standard_extension_info(dpy, extname, majorrev, minorrev); + + syscounters = XSyncListSystemCounters(dpy, &ncounters); + printf(" system counters: %d\n", ncounters); + for (i = 0; i < ncounters; i++) + { + printf(" %s id: 0x%08x resolution_lo: %d resolution_hi: %d\n", + syscounters[i].name, syscounters[i].counter, + XSyncValueLow32(syscounters[i].resolution), + XSyncValueHigh32(syscounters[i].resolution)); + } + XSyncFreeSystemCounterList(syscounters); + return 1; +} +#endif /* HAVE_XSYNC */ + +int +print_shape_info(dpy, extname) + Display *dpy; + char *extname; +{ + int majorrev, minorrev; + + if (!XShapeQueryVersion(dpy, &majorrev, &minorrev)) + return 0; + print_standard_extension_info(dpy, extname, majorrev, minorrev); + return 1; +} + +#ifdef MITSHM +int +print_mitshm_info(dpy, extname) + Display *dpy; + char *extname; +{ + int majorrev, minorrev; + Bool sharedPixmaps; + + if (!XShmQueryVersion(dpy, &majorrev, &minorrev, &sharedPixmaps)) + return 0; + print_standard_extension_info(dpy, extname, majorrev, minorrev); + printf(" shared pixmaps: "); + if (sharedPixmaps) + { + int format = XShmPixmapFormat(dpy); + printf("yes, format: %d\n", format); + } + else + { + printf("no\n"); + } + return 1; +} +#endif /* MITSHM */ + +#ifdef HAVE_XDBE +int +print_dbe_info(dpy, extname) + Display *dpy; + char *extname; +{ + int majorrev, minorrev; + XdbeScreenVisualInfo *svi; + int numscreens = 0; + int iscrn, ivis; + + if (!XdbeQueryExtension(dpy, &majorrev, &minorrev)) + return 0; + + print_standard_extension_info(dpy, extname, majorrev, minorrev); + svi = XdbeGetVisualInfo(dpy, (Drawable *)NULL, &numscreens); + for (iscrn = 0; iscrn < numscreens; iscrn++) + { + printf(" Double-buffered visuals on screen %d\n", iscrn); + for (ivis = 0; ivis < svi[iscrn].count; ivis++) + { + printf(" visual id 0x%lx depth %d perflevel %d\n", + svi[iscrn].visinfo[ivis].visual, + svi[iscrn].visinfo[ivis].depth, + svi[iscrn].visinfo[ivis].perflevel); + } + } + XdbeFreeVisualInfo(svi); + return 1; +} +#endif /* HAVE_XDBE */ + +#ifdef HAVE_XRECORD +int +print_record_info(dpy, extname) + Display *dpy; + char *extname; +{ + int majorrev, minorrev; + + if (!XRecordQueryVersion(dpy, &majorrev, &minorrev)) + return 0; + print_standard_extension_info(dpy, extname, majorrev, minorrev); + return 1; +} +#endif /* HAVE_XRECORD */ + +/* utilities to manage the list of recognized extensions */ + + +typedef int (*ExtensionPrintFunc)( +#if NeedFunctionPrototypes + Display *, char * +#endif +); + +typedef struct { + char *extname; + ExtensionPrintFunc printfunc; + Bool printit; +} ExtensionPrintInfo; + +ExtensionPrintInfo known_extensions[] = +{ +#ifdef MITSHM + {"MIT-SHM", print_mitshm_info, False}, +#endif /* MITSHM */ + {MULTIBUFFER_PROTOCOL_NAME, print_multibuf_info, False}, + {"SHAPE", print_shape_info, False}, +#ifdef HAVE_XSYNC + {SYNC_NAME, print_sync_info, False}, +#endif /* HAVE_XSYNC */ +#ifdef HAVE_XIE + {xieExtName, print_xie_info, False}, +#endif /* HAVE_XIE */ +#ifdef HAVE_XTEST + {XTestExtensionName, print_xtest_info, False}, +#endif /* HAVE_XTEST */ +#ifdef HAVE_XDBE + {"DOUBLE-BUFFER", print_dbe_info, False}, +#endif /* HAVE_XDBE */ +#ifdef HAVE_XRECORD + {"RECORD", print_record_info, False} +#endif /* HAVE_XRECORD */ + /* add new extensions here */ + /* wish list: PEX XKB LBX */ +}; + +int num_known_extensions = sizeof known_extensions / sizeof known_extensions[0]; + +void +print_known_extensions(f) + FILE *f; +{ + int i; + for (i = 0; i < num_known_extensions; i++) + { + fprintf(f, "%s ", known_extensions[i].extname); + } +} + +void +mark_extension_for_printing(extname) + char *extname; +{ + int i; + + if (strcmp(extname, "all") == 0) + { + for (i = 0; i < num_known_extensions; i++) + known_extensions[i].printit = True; + } + else + { + for (i = 0; i < num_known_extensions; i++) + { + if (strcmp(extname, known_extensions[i].extname) == 0) + { + known_extensions[i].printit = True; + return; + } + } + printf("%s extension not supported by %s\n", extname, ProgramName); + } +} + +void +print_marked_extensions(dpy) + Display *dpy; +{ + int i; + for (i = 0; i < num_known_extensions; i++) + { + if (known_extensions[i].printit) + { + printf("\n"); + if (! (*known_extensions[i].printfunc)(dpy, + known_extensions[i].extname)) + { + printf("%s extension not supported by server\n", + known_extensions[i].extname); + } + } + } +} + +static void usage () +{ + fprintf (stderr, "usage: %s [options]\n", ProgramName); + fprintf (stderr, "-display displayname\tserver to query\n"); + fprintf (stderr, "-queryExtensions\tprint info returned by XQueryExtension\n"); + fprintf (stderr, "-ext all\t\tprint detailed info for all supported extensions\n"); + fprintf (stderr, "-ext extension-name\tprint detailed info for extension-name if one of:\n "); + print_known_extensions(stderr); + fprintf (stderr, "\n"); + exit (1); +} + +int main (argc, argv) + int argc; + char *argv[]; +{ + Display *dpy; /* X connection */ + char *displayname = NULL; /* server to contact */ + int i; /* temp variable: iterator */ + Bool multibuf = False; + int mbuf_event_base, mbuf_error_base; + + ProgramName = argv[0]; + + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + int len = strlen(arg); + + if (!strncmp("-display", arg, len)) { + if (++i >= argc) usage (); + displayname = argv[i]; + } else if (!strncmp("-queryExtensions", arg, len)) { + queryExtensions = True; + } else if (!strncmp("-ext", arg, len)) { + if (++i >= argc) usage (); + mark_extension_for_printing(argv[i]); + } else + usage (); + } + + dpy = XOpenDisplay (displayname); + if (!dpy) { + fprintf (stderr, "%s: unable to open display \"%s\".\n", + ProgramName, XDisplayName (displayname)); + exit (1); + } + +#ifdef HAVE_OVERLAY + find_overlay_info (dpy); +#endif /* HAVE_OVERLAY */ + + print_display_info (dpy); + for (i = 0; i < ScreenCount (dpy); i++) { + print_screen_info (dpy, i); + } + + print_marked_extensions(dpy); + + XCloseDisplay (dpy); + exit (0); +} diff --git a/driver/xscreensaver-command.c b/driver/xscreensaver-command.c new file mode 100644 index 00000000..5ec74417 --- /dev/null +++ b/driver/xscreensaver-command.c @@ -0,0 +1,439 @@ +/* xscreensaver-command, Copyright (c) 1991-2008 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +/* #include / * for CARD32 */ +#include +#include +#include /* for XGetClassHint() */ +#include + +#include /* only needed to get through xscreensaver.h */ + + +/* You might think that to read an array of 32-bit quantities out of a + server-side property, you would pass an array of 32-bit data quantities + into XGetWindowProperty(). You would be wrong. You have to use an array + of longs, even if long is 64 bits (using 32 of each 64.) + */ +typedef long PROP32; + +#include "remote.h" +#include "version.h" + +#ifdef _VROOT_H_ +ERROR! you must not include vroot.h in this file +#endif + +char *progname; + +Atom XA_VROOT; +Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE; +Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT; +Atom XA_BLANK, XA_LOCK; +static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV; +static Atom XA_RESTART, XA_PREFS, XA_THROTTLE, XA_UNTHROTTLE; + +static char *screensaver_version; +# ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than the + length ISO C89 compilers are required to support" in the + usage string... */ +# endif +static char *usage = "\n\ +usage: %s -