From 295236120a1ae85554ac32414f00ca08e358d517 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Mon, 18 Apr 2011 20:56:15 -0400 Subject: [PATCH 1/1] http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz -rw-r--r-- 1 zblaxell zblaxell 5923496 Apr 18 17:03 xscreensaver-5.13.tar.gz 3bdac6122e5b7b0cffcc90b3b75dc7fa001c0181 xscreensaver-5.13.tar.gz --- INSTALL | 183 + Makefile.in | 373 + OSX/._XScreenSaver.icns | Bin 0 -> 103819 bytes OSX/._XScreenSaverDMG.icns | Bin 0 -> 218 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 | 103 + OSX/Makefile | 144 + 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 -> 103291 bytes OSX/XScreenSaver.plist | 30 + OSX/XScreenSaverConfigSheet.h | 38 + OSX/XScreenSaverConfigSheet.m | 1924 + OSX/XScreenSaverDMG.icns | Bin 0 -> 110173 bytes OSX/XScreenSaverGLView.h | 26 + OSX/XScreenSaverGLView.m | 259 + OSX/XScreenSaverSubclass.m | 33 + OSX/XScreenSaverView.h | 43 + OSX/XScreenSaverView.m | 717 + OSX/bindist-DS_Store | Bin 0 -> 12292 bytes OSX/bindist.rtf | 62 + OSX/jwxyz-timers.h | 29 + OSX/jwxyz-timers.m | 383 + OSX/jwxyz.h | 698 + OSX/jwxyz.m | 2917 ++ OSX/main.m | 17 + OSX/osxgrabscreen.m | 382 + OSX/update-info-plist.pl | 281 + OSX/xscreensaver_Prefix.pch | 15 + README | 1375 + README.VMS | 57 + README.hacking | 180 + aclocal.m4 | 443 + config.guess | 1516 + config.h-vms | 284 + config.h.in | 476 + config.sub | 1626 + configure | 22977 ++++++++++++ configure.in | 4364 +++ driver/.gdbinit | 27 + driver/Makefile.in | 1023 + driver/README | 6 + driver/XScreenSaver-Xm.ad | 126 + driver/XScreenSaver.ad.in | 493 + driver/XScreenSaver_Xm_ad.h | 108 + driver/XScreenSaver_ad.h | 358 + driver/auth.h | 54 + driver/compile_axp.com | 15 + driver/compile_decc.com | 15 + driver/demo-Gtk-conf.c | 1996 + 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 | 5312 +++ driver/demo-Xm-widgets.c | 907 + driver/demo-Xm.c | 1875 + driver/dpms.c | 269 + driver/exec.c | 299 + driver/exec.h | 21 + driver/link_axp.com | 15 + driver/link_decc.com | 15 + driver/lock.c | 2260 ++ 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 | 1647 + driver/prefs.h | 35 + driver/remote.c | 595 + driver/remote.h | 24 + driver/screens.c | 1077 + driver/screensaver-properties.desktop.in | 8 + driver/setuid.c | 361 + driver/splash.c | 866 + driver/stderr.c | 548 + driver/subprocs.c | 1373 + driver/test-apm.c | 101 + driver/test-fade.c | 123 + driver/test-grab.c | 89 + driver/test-mlstring.c | 312 + driver/test-passwd.c | 303 + 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 | 1603 + driver/types.h | 420 + 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 | 1098 + driver/xscreensaver-command.c | 439 + driver/xscreensaver-command.man | 263 + driver/xscreensaver-demo.glade2 | 3089 ++ 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 | 555 + driver/xscreensaver-getimage-file.man | 49 + driver/xscreensaver-getimage-video | 178 + driver/xscreensaver-getimage-video.man | 51 + driver/xscreensaver-getimage.c | 1951 + driver/xscreensaver-getimage.man | 70 + driver/xscreensaver-text | 942 + driver/xscreensaver-text.man | 85 + driver/xscreensaver.c | 2346 ++ driver/xscreensaver.h | 207 + driver/xscreensaver.man | 906 + driver/xscreensaver.pam | 16 + driver/xset.c | 395 + hacks/._barcode.c | Bin 0 -> 170 bytes hacks/._celtic.c | Bin 0 -> 170 bytes hacks/._demon.c | Bin 0 -> 167 bytes hacks/._eruption.c | Bin 0 -> 170 bytes hacks/._flow.c | Bin 0 -> 170 bytes hacks/._interaggregate.c | Bin 0 -> 170 bytes hacks/._noseguy.c | Bin 0 -> 170 bytes hacks/._petri.c | Bin 0 -> 170 bytes hacks/._shadebobs.c | Bin 0 -> 170 bytes hacks/._slidescreen.c | Bin 0 -> 170 bytes hacks/._zoom.c | Bin 0 -> 170 bytes 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 | 435 + 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 | 2010 + hacks/apple2.c | 860 + hacks/apple2.h | 121 + hacks/apple2.man | 206 + hacks/asm6502.c | 2254 ++ 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 | 1116 + 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 | 30 + 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 | 44 + 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 | 37 + 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 | 36 + 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 | 779 + hacks/epicycle.man | 204 + hacks/eruption.c | 505 + 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 | 1813 + hacks/fontglide.man | 124 + hacks/forest.c | 260 + hacks/forest.man | 62 + hacks/fps.c | 238 + hacks/fps.h | 28 + hacks/fpsI.h | 36 + hacks/fuzzyflakes.c | 639 + hacks/fuzzyflakes.man | 112 + hacks/galaxy.c | 462 + hacks/galaxy.man | 87 + hacks/glx/._bouncingcow.c | Bin 0 -> 170 bytes hacks/glx/._buildlwo.c | Bin 0 -> 170 bytes hacks/glx/._cubestorm.c | Bin 0 -> 170 bytes hacks/glx/._flyingtoasters.c | Bin 0 -> 170 bytes hacks/glx/._gears.c | Bin 0 -> 170 bytes hacks/glx/._glknots.c | Bin 0 -> 170 bytes hacks/glx/._gltext.c | Bin 0 -> 167 bytes hacks/glx/._grab-ximage.c | Bin 0 -> 170 bytes hacks/glx/._jigsaw.c | Bin 0 -> 170 bytes hacks/glx/._klein.c | Bin 0 -> 170 bytes hacks/glx/._lavalite.c | Bin 0 -> 170 bytes hacks/glx/._menger.c | Bin 0 -> 170 bytes hacks/glx/._moebiusgears.c | Bin 0 -> 170 bytes hacks/glx/._rubikblocks.man | Bin 0 -> 225 bytes hacks/glx/._spheremonics.c | Bin 0 -> 170 bytes hacks/glx/._tangram.c | Bin 0 -> 170 bytes hacks/glx/Makefile.in | 2400 ++ hacks/glx/README | 10 + hacks/glx/antinspect.c | 694 + hacks/glx/antinspect.man | 56 + hacks/glx/antmaze.c | 1592 + hacks/glx/antmaze.man | 52 + hacks/glx/ants.h | 45 + hacks/glx/antspotlight.c | 794 + hacks/glx/antspotlight.man | 56 + hacks/glx/atlantis.c | 581 + hacks/glx/atlantis.h | 123 + hacks/glx/atlantis.man | 78 + hacks/glx/atunnel.c | 325 + hacks/glx/atunnel.man | 83 + hacks/glx/b_draw.c | 239 + hacks/glx/b_lockglue.c | 234 + hacks/glx/b_sphere.c | 219 + hacks/glx/blinkbox.c | 597 + hacks/glx/blinkbox.man | 73 + hacks/glx/blocktube.c | 447 + hacks/glx/blocktube.man | 73 + hacks/glx/boing.c | 664 + hacks/glx/boing.man | 105 + hacks/glx/bouncingcow.c | 552 + hacks/glx/bouncingcow.man | 73 + hacks/glx/boxed.c | 1306 + hacks/glx/boxed.h | 4116 +++ hacks/glx/boxed.man | 56 + hacks/glx/bubble3d.c | 280 + hacks/glx/bubble3d.h | 95 + hacks/glx/bubble3d.man | 62 + hacks/glx/buildlwo.c | 96 + hacks/glx/buildlwo.h | 32 + hacks/glx/cage.c | 470 + hacks/glx/cage.man | 61 + hacks/glx/carousel.c | 904 + hacks/glx/carousel.man | 109 + hacks/glx/chessgames.h | 343 + hacks/glx/chessmodels.c | 1708 + hacks/glx/chessmodels.h | 44 + hacks/glx/circuit.c | 2274 ++ 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 | 908 + hacks/glx/cube21.man | 147 + hacks/glx/cubenetic.c | 609 + hacks/glx/cubenetic.man | 89 + hacks/glx/cubestorm.c | 425 + hacks/glx/cubestorm.man | 77 + hacks/glx/cubicgrid.c | 290 + hacks/glx/cubicgrid.man | 83 + hacks/glx/dangerball.c | 384 + hacks/glx/dangerball.man | 72 + hacks/glx/dnalogo.c | 2268 ++ 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 | 903 + hacks/glx/endgame.man | 72 + hacks/glx/engine.c | 1000 + 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 | 556 + hacks/glx/extrusion.h | 53 + hacks/glx/extrusion.man | 71 + hacks/glx/flipflop.c | 872 + hacks/glx/flipflop.man | 95 + hacks/glx/flipscreen3d.c | 534 + hacks/glx/flipscreen3d.man | 61 + hacks/glx/fliptext.c | 1116 + 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 | 567 + hacks/glx/flurry.h | 285 + hacks/glx/flurry.man | 73 + hacks/glx/flyingtoasters.c | 858 + hacks/glx/flyingtoasters.man | 86 + hacks/glx/font-ximage.c | 251 + hacks/glx/font-ximage.h | 30 + hacks/glx/fps-gl.c | 98 + hacks/glx/gears.c | 943 + hacks/glx/gears.man | 79 + hacks/glx/gflux.c | 835 + hacks/glx/gflux.man | 111 + hacks/glx/glblur.c | 639 + hacks/glx/glblur.man | 76 + hacks/glx/glcells.c | 1316 + 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 | 2060 ++ hacks/glx/glhanoi.man | 83 + hacks/glx/glknots.c | 460 + hacks/glx/glknots.man | 81 + hacks/glx/gllist.c | 9 + hacks/glx/gllist.h | 22 + hacks/glx/glmatrix.c | 1067 + hacks/glx/glmatrix.man | 117 + hacks/glx/glplanet.c | 672 + 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 | 280 + hacks/glx/glschool_gl.h | 37 + hacks/glx/glslideshow.c | 1229 + hacks/glx/glslideshow.man | 131 + hacks/glx/glsnake.c | 2643 ++ hacks/glx/glsnake.man | 98 + hacks/glx/gltext.c | 646 + 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 | 504 + 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 | 1074 + hacks/glx/jigglypuff.man | 121 + hacks/glx/jigsaw.c | 1165 + hacks/glx/jigsaw.man | 90 + hacks/glx/juggler3d.c | 3069 ++ 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 | 1569 + hacks/glx/lavalite.man | 160 + hacks/glx/lockward.c | 934 + hacks/glx/lockward.man | 79 + hacks/glx/marching.c | 639 + hacks/glx/marching.h | 48 + hacks/glx/menger.c | 550 + hacks/glx/menger.man | 78 + hacks/glx/mirrorblob.c | 1848 + hacks/glx/mirrorblob.man | 107 + hacks/glx/moebius.c | 753 + hacks/glx/moebius.man | 65 + hacks/glx/moebiusgears.c | 411 + hacks/glx/moebiusgears.man | 86 + hacks/glx/molecule.c | 1668 + hacks/glx/molecule.man | 160 + hacks/glx/molecules.sh | 17 + hacks/glx/morph3d.c | 833 + hacks/glx/morph3d.man | 57 + hacks/glx/noof.c | 473 + hacks/glx/noof.man | 52 + hacks/glx/normals.c | 52 + hacks/glx/normals.h | 38 + hacks/glx/photopile.c | 780 + hacks/glx/photopile.man | 113 + hacks/glx/pinion.c | 1488 + hacks/glx/pinion.man | 82 + hacks/glx/pipeobjs.c | 3262 ++ hacks/glx/pipes.c | 1080 + hacks/glx/pipes.man | 85 + hacks/glx/polyhedra-gl.c | 664 + 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 | 814 + hacks/glx/providence.man | 66 + hacks/glx/pulsar.c | 517 + hacks/glx/pulsar.man | 102 + hacks/glx/queens.c | 535 + hacks/glx/queens.man | 66 + hacks/glx/rotator.c | 247 + hacks/glx/rotator.h | 60 + hacks/glx/rubik.c | 2114 ++ hacks/glx/rubik.man | 69 + hacks/glx/rubikblocks.c | 652 + 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 | 861 + hacks/glx/sballs.man | 125 + hacks/glx/shark.c | 1395 + hacks/glx/sierpinski3d.c | 577 + hacks/glx/sierpinski3d.man | 67 + hacks/glx/skytentacles.c | 1091 + hacks/glx/skytentacles.man | 102 + hacks/glx/sonar-icmp.c | 1199 + hacks/glx/sonar-sim.c | 112 + hacks/glx/sonar.c | 1018 + hacks/glx/sonar.h | 68 + hacks/glx/sonar.man | 163 + hacks/glx/sphere.c | 137 + 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 | 595 + hacks/glx/stairs.man | 56 + hacks/glx/starwars.c | 1109 + 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 | 187 + hacks/glx/stonerview.h | 61 + hacks/glx/stonerview.man | 53 + hacks/glx/superquadrics.c | 796 + hacks/glx/superquadrics.man | 70 + hacks/glx/surfaces.c | 680 + hacks/glx/surfaces.man | 127 + hacks/glx/swim.c | 232 + hacks/glx/tangram.c | 1063 + 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 | 1252 + 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 | 891 + hacks/glx/topblock.h | 45 + hacks/glx/topblock.man | 170 + hacks/glx/trackball.c | 330 + hacks/glx/trackball.h | 82 + hacks/glx/tube.c | 394 + hacks/glx/tube.h | 32 + hacks/glx/tunnel_draw.c | 505 + hacks/glx/tunnel_draw.h | 11 + hacks/glx/voronoi.c | 506 + hacks/glx/voronoi.man | 88 + hacks/glx/wfront2gl.pl | 359 + hacks/glx/whale.c | 1887 + hacks/glx/xlock-gl-utils.c | 224 + 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/salvinorin.pdb | 92 + 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 | 971 + 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 | 487 + 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 | 639 + hacks/memscroller.man | 78 + hacks/metaballs.c | 429 + 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 | 1792 + hacks/pacman.h | 231 + hacks/pacman.man | 67 + hacks/pacman_ai.c | 876 + 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 | 758 + 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 | 1131 + 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 | 931 + hacks/screenhack.h | 61 + hacks/screenhackI.h | 138 + hacks/shadebobs.c | 465 + hacks/shadebobs.man | 65 + hacks/sierpinski.c | 224 + hacks/sierpinski.man | 69 + hacks/slidescreen.c | 475 + hacks/slidescreen.man | 97 + hacks/slip.c | 367 + 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 | 325 + 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 | 738 + 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 | 363 + 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 | 3773 ++ hacks/webcollage-cocoa.m | 420 + hacks/webcollage-helper-cocoa.m | 407 + 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 | 721 + hacks/wormhole.man | 69 + hacks/xanalogtv.c | 617 + hacks/xanalogtv.man | 84 + hacks/xflame.c | 816 + hacks/xflame.man | 72 + hacks/xjack.c | 478 + hacks/xjack.man | 52 + hacks/xlockmore.c | 547 + hacks/xlockmore.h | 207 + hacks/xlockmoreI.h | 158 + hacks/xlyap.c | 1919 + hacks/xlyap.man | 223 + hacks/xmatrix.c | 1906 + 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 | 269 + hacks/zoom.man | 111 + install-sh | 250 + intltool-extract.in | 309 + intltool-merge.in | 567 + intltool-update.in | 613 + makevms.com | 57 + po/._de.po | Bin 0 -> 476 bytes po/._ja.po | Bin 0 -> 225 bytes po/._nb.po | Bin 0 -> 536 bytes po/._nl.po | Bin 0 -> 460 bytes po/._pt_BR.po | Bin 0 -> 510 bytes po/ChangeLog | 300 + po/Makefile.in.in | 384 + po/POTFILES.in | 227 + po/ca.po | 8905 +++++ po/da.po | 9938 +++++ po/de.po | 9219 +++++ 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 | 11325 ++++++ po/pl.po | 9387 +++++ po/pt.po | 9995 +++++ po/pt_BR.po | 9511 +++++ 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/Makefile.in | 302 + 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 | 701 + utils/colors.h | 140 + utils/compile_axp.com | 25 + utils/compile_decc.com | 25 + utils/erase.c | 758 + utils/erase.h | 20 + utils/fade.c | 962 + utils/fade.h | 21 + utils/grabclient.c | 874 + utils/grabscreen.c | 925 + 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 | 319 + utils/spline.h | 51 + utils/usleep.c | 64 + utils/usleep.h | 20 + utils/utils.h | 27 + utils/version.h | 2 + utils/visual-gl.c | 308 + 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 | 30397 ++++++++++++++++ 1302 files changed, 698844 insertions(+) create mode 100644 INSTALL create mode 100644 Makefile.in create mode 100644 OSX/._XScreenSaver.icns create mode 100644 OSX/._XScreenSaverDMG.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/._barcode.c create mode 100644 hacks/._celtic.c create mode 100644 hacks/._demon.c create mode 100644 hacks/._eruption.c create mode 100644 hacks/._flow.c create mode 100644 hacks/._interaggregate.c create mode 100644 hacks/._noseguy.c create mode 100644 hacks/._petri.c create mode 100644 hacks/._shadebobs.c create mode 100644 hacks/._slidescreen.c create mode 100644 hacks/._zoom.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/._bouncingcow.c create mode 100644 hacks/glx/._buildlwo.c create mode 100644 hacks/glx/._cubestorm.c create mode 100644 hacks/glx/._flyingtoasters.c create mode 100644 hacks/glx/._gears.c create mode 100644 hacks/glx/._glknots.c create mode 100644 hacks/glx/._gltext.c create mode 100644 hacks/glx/._grab-ximage.c create mode 100644 hacks/glx/._jigsaw.c create mode 100644 hacks/glx/._klein.c create mode 100644 hacks/glx/._lavalite.c create mode 100644 hacks/glx/._menger.c create mode 100644 hacks/glx/._moebiusgears.c create mode 100644 hacks/glx/._rubikblocks.man create mode 100644 hacks/glx/._spheremonics.c create mode 100644 hacks/glx/._tangram.c 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 100755 hacks/glx/wfront2gl.pl 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/salvinorin.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/._de.po create mode 100644 po/._ja.po create mode 100644 po/._nb.po create mode 100644 po/._nl.po create mode 100644 po/._pt_BR.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/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/._XScreenSaver.icns b/OSX/._XScreenSaver.icns new file mode 100644 index 0000000000000000000000000000000000000000..d83450109dea0ac58a4c2e36be6af3edcf7b5962 GIT binary patch literal 103819 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 literal 0 HcmV?d00001 diff --git a/OSX/._XScreenSaverDMG.icns b/OSX/._XScreenSaverDMG.icns new file mode 100644 index 0000000000000000000000000000000000000000..d5f4b86235183b8731b370e8b32335006064b222 GIT binary patch literal 218 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@d_XY@oxb!7-S~rIm6hPv|~s}5QAd0 z9#96P1Ed>d3Niqh1`_9EVBql!4lcCN8edx|S|xF1k)mrmoJ8Zq7!|j?Mu7Rw4!f 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 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«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 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..327162a3 --- /dev/null +++ b/OSX/InvertedSlider.m @@ -0,0 +1,103 @@ +/* xscreensaver, Copyright (c) 2006-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. +* +* 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; +{ + // #### "Build and Analyze" says this leaks. Unsure whether this is true. + 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..0d816049 --- /dev/null +++ b/OSX/Makefile @@ -0,0 +1,144 @@ +# XScreenSaver for MacOS X, Copyright (c) 2006 by Jamie Zawinski. + +XCODE_TARGET = "All Savers" +XCODEBUILD=xcodebuild + +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" ; \ + EXTRAS=../../xdaliclock/OSX/build/Release/*.saver ; \ + 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 $$EXTRAS ; 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" ; \ + seticon -d ../../xdaliclock/OSX/daliclockSaver.icns $$STAGE/DaliClock.saver;\ + 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..dcbd3498 --- /dev/null +++ b/OSX/PrefsReader.m @@ -0,0 +1,300 @@ +/* xscreensaver, Copyright (c) 2006-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. + */ + +/* 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++; + } + +#if 0 + // make sure there's no resource mentioned in defaults and not options. + NSEnumerator *enumerator = [defsdict keyEnumerator]; + NSString *key; + while ((key = [enumerator nextObject])) { + 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..32f32794 --- /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.13 + CFBundleSignature + ???? + CFBundleVersion + 5.13 + LSMinimumSystemVersion + 10.4 + NSMainNibFile + SaverTester + NSPrincipalClass + NSApplication + + diff --git a/OSX/XScreenSaver.icns b/OSX/XScreenSaver.icns new file mode 100644 index 0000000000000000000000000000000000000000..95068d11ce8a94b37f9d208a3c3375bb467d85b1 GIT binary patch literal 103291 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 literal 0 HcmV?d00001 diff --git a/OSX/XScreenSaver.plist b/OSX/XScreenSaver.plist new file mode 100644 index 00000000..a4077419 --- /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.13 + CFBundleSignature + ???? + CFBundleVersion + 5.13 + 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..b529dadd --- /dev/null +++ b/OSX/XScreenSaverConfigSheet.m @@ -0,0 +1,1924 @@ +/* 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; +} + + +static void +link_atk_label_to_widget(GtkWidget *label, GtkWidget *widget) +{ + AtkObject *atk_label = gtk_widget_get_accessible (label); + AtkObject *atk_widget = gtk_widget_get_accessible (widget); + + atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR, + atk_widget); + atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY, + atk_label); +} + +/* 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: + { + GtkWidget *entry = gtk_entry_new (); + parent = insert_fake_hbox (parent); + if (label) + { + GtkWidget *w = gtk_label_new (_(label)); + link_atk_label_to_widget (w, entry); + 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 = entry; + 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...")); + link_atk_label_to_widget (L, entry); + 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)); + link_atk_label_to_widget (labelw, scale); + 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)); + link_atk_label_to_widget (w, scale); + 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)); + link_atk_label_to_widget (w, scale); + 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), GET_ADJ_VALUE(adj)); + set_widget_min_width (GTK_WIDGET (spin), MIN_SPINBUTTON_WIDTH); + + if (label) + { + GtkWidget *w = gtk_label_new (_(label)); + link_atk_label_to_widget (w, spin); + 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 (GET_PARENT (dialog)) + dialog = GET_PARENT (dialog); + 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 (GET_PARENT (dialog)) + dialog = GET_PARENT (dialog); + 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 (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), + GET_ADJ_VALUE(adj)) - 1 + : GET_ADJ_VALUE(adj)); + + 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 (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), 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..5259dc5a --- /dev/null +++ b/driver/demo-Gtk.c @@ -0,0 +1,5312 @@ +/* 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 */ + +/* Deal with deprecation of direct access to struct fields on the way to GTK3 + See http://live.gnome.org/GnomeGoals/UseGseal + */ +#if GTK_CHECK_VERSION(2,14,0) +# define GET_PARENT(w) gtk_widget_get_parent (w) +# define GET_WINDOW(w) gtk_widget_get_window (w) +# define GET_ACTION_AREA(d) gtk_dialog_get_action_area (d) +# define GET_CONTENT_AREA(d) gtk_dialog_get_content_area (d) +# define GET_ADJ_VALUE(a) gtk_adjustment_get_value (a) +# define SET_ADJ_VALUE(a,v) gtk_adjustment_set_value (a, v) +# define SET_ADJ_UPPER(a,v) gtk_adjustment_set_upper (a, v) +#else +# define GET_PARENT(w) ((w)->parent) +# define GET_WINDOW(w) ((w)->window) +# define GET_ACTION_AREA(d) ((d)->action_area) +# define GET_CONTENT_AREA(d) ((d)->vbox) +# define GET_ADJ_VALUE(a) ((a)->value) +# define SET_ADJ_VALUE(a,v) (a)->value = v +# define SET_ADJ_UPPER(a,v) (a)->upper = v +#endif + +#if GTK_CHECK_VERSION(2,18,0) +# define SET_CAN_DEFAULT(w) gtk_widget_set_can_default ((w), TRUE) +# define GET_SENSITIVE(w) gtk_widget_get_sensitive (w) +#else +# define SET_CAN_DEFAULT(w) GTK_WIDGET_SET_FLAGS ((w), GTK_CAN_DEFAULT) +# define GET_SENSITIVE(w) GTK_WIDGET_IS_SENSITIVE (w) +#endif + +#if GTK_CHECK_VERSION(2,20,0) +# define GET_REALIZED(w) gtk_widget_get_realized (w) +#else +# define GET_REALIZED(w) GTK_WIDGET_REALIZED (w) +#endif + +/* 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 (GET_WINDOW (GTK_WIDGET (vp)), + &ignore, &ignore, &ignore, &parent_h, &ignore); + gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (selected)), + &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 (GET_PARENT (shell)) + shell = GET_PARENT (shell); + 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 Bool +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 && !GET_WINDOW (parent)) + parent = GET_PARENT (parent); + + if (!parent || + !GET_WINDOW (parent)) /* too early to pop up transient dialogs */ + { + fprintf (stderr, "%s: too early for dialog?\n", progname); + return False; + } + + 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 (GET_CONTENT_AREA (GTK_DIALOG (dialog))), + 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 (GET_CONTENT_AREA (GTK_DIALOG (dialog))), + label, TRUE, TRUE, 0); + gtk_widget_show (label); + + label = gtk_hbutton_box_new (); + gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))), + 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); + SET_CAN_DEFAULT (ok); + gtk_widget_show (ok); + gtk_widget_grab_focus (ok); + + if (cancel) + { + SET_CAN_DEFAULT (cancel); + 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 (GET_WINDOW (GTK_WIDGET (dialog)), + GET_WINDOW (GTK_WIDGET (parent))); + +#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); + return True; +} + + +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 (GET_PARENT (parent)) + parent = GET_PARENT (parent); + + hbox = gtk_hbox_new (FALSE, 20); + gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))), + 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 (GET_ACTION_AREA (GTK_DIALOG (dialog))), + 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 (GET_WINDOW (GTK_WIDGET (dialog)), + GET_WINDOW (GTK_WIDGET (parent))); + gdk_window_show (GET_WINDOW (GTK_WIDGET (dialog))); + gdk_window_raise (GET_WINDOW (GTK_WIDGET (dialog))); + } +} + + +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"); + gboolean was = GET_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 */ + static Bool already_warned_about_missing_image_directory = False; /* very long name... */ + + 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, when: + - not being warned before + - image directory is changed and the directory doesn't exist + */ + if (p2->image_directory && + *p2->image_directory && + !directory_p (p2->image_directory) && + ( !already_warned_about_missing_image_directory || + ( p->image_directory && + *p->image_directory && + strcmp(p->image_directory, p2->image_directory) + ) + ) + ) + { + char b[255]; + sprintf (b, "Warning:\n\n" "Directory does not exist: \"%s\"\n", + p2->image_directory); + if (warning_dialog (s->toplevel_widget, b, D_NONE, 100)) + already_warned_about_missing_image_directory = True; + } + + + /* 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 (GET_PARENT (widget))); + 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 = GET_ADJ_VALUE (adj); + + 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_get_style (GTK_WIDGET (list)); + 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 = GET_WINDOW (s->toplevel_widget); + 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), + gtk_widget_get_style (w)->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; + GtkStyle *style; + + if (!s->toplevel_widget) return; /* very early */ + p = name_to_widget (s, "preview"); + window = GET_WINDOW (p); + + if (!window) return; + + /* Flush the widget background down into the window, in case a subproc + has changed it. */ + style = gtk_widget_get_style (p); + gdk_window_set_background (window, &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 (GET_REALIZED (pr)) + { + GdkWindow *window = GET_WINDOW (pr); + Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0); + Window id; + gtk_widget_hide (pr); + gtk_widget_unrealize (pr); + gtk_widget_realize (pr); + gtk_widget_show (pr); + id = (window ? GDK_WINDOW_XWINDOW (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 (!GET_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 = GET_WINDOW (widget); + GtkStyle *style = gtk_style_copy (gtk_widget_get_style (widget)); + 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 = GET_WINDOW (pr); + + 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 = GET_PARENT (window); + 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 (GET_WINDOW (GTK_WIDGET (s->toplevel_widget))); /* 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..3de78f2d --- /dev/null +++ b/driver/dpms.c @@ -0,0 +1,269 @@ +/* dpms.c --- syncing the X Display Power Management values + * xscreensaver, Copyright (c) 2001-2011 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, Bool on_p) +{ + if ((!!on_p) != monitor_powered_on_p (si)) + { + int event_number, error_number; + if (!DPMSQueryExtension(si->dpy, &event_number, &error_number) || + !DPMSCapable(si->dpy)) + { + if (si->prefs.verbose_p) + fprintf (stderr, + "%s: unable to power %s monitor: no DPMS extension.\n", + blurb(), (on_p ? "on" : "off")); + return; + } + + DPMSForceLevel(si->dpy, (on_p ? DPMSModeOn : DPMSModeOff)); + XSync(si->dpy, False); + + if ((!!on_p) != monitor_powered_on_p (si)) /* double-check */ + fprintf (stderr, + "%s: DPMSForceLevel(dpy, %s) did not change monitor power state.\n", + blurb(), + (on_p ? "DPMSModeOn" : "DPMSModeOff")); + } +} + +#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, Bool on_p) +{ + 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..3a84355a --- /dev/null +++ b/driver/lock.c @@ -0,0 +1,2260 @@ +/* lock.c --- handling the password dialog for locking-mode. + * xscreensaver, Copyright (c) 1993-2011 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 HAVE_RANDR +# include +#endif /* HAVE_RANDR */ + +#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; + + +#define MAX_BYTES_PER_CHAR 8 /* UTF-8 uses no more than 3, I think */ +#define MAX_PASSWD_CHARS 128 /* Longest possible passphrase */ + +struct passwd_dialog_data { + + saver_screen_info *prompt_screen; + int previous_mouse_x, previous_mouse_y; + + /* "Characters" in the password may be a variable number of bytes long. + typed_passwd contains the raw bytes. + typed_passwd_char_size indicates the size in bytes of each character, + so that we can make backspace work. + */ + char typed_passwd [MAX_PASSWD_CHARS * MAX_BYTES_PER_CHAR]; + char typed_passwd_char_size [MAX_PASSWD_CHARS]; + + 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 border; + 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" ); + pw->border = get_pixel_resource (si->dpy, cmap, + "passwd.borderColor", + "Dialog.borderColor"); + + 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; +} + + +Bool debug_passwd_window_p = False; /* used only by test-passwd.c */ + + +/** + * 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; + + if (debug_passwd_window_p) + attrs.override_redirect = False; /* kludge for test-passwd.c */ + + 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); + XSetWindowBorder (si->dpy, si->passwd_dialog, pw->border); + + /* 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); + + 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->typed_passwd_char_size, 0, sizeof(pw->typed_passwd_char_size)); + 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); + XSync (si->dpy, False); + + while (XCheckMaskEvent (si->dpy, PointerMotionMask, &event)) + if (p->verbose_p) + fprintf (stderr, "%s: discarding MotionNotify event.\n", blurb()); + +#ifdef HAVE_XINPUT + if (si->using_xinput_extension && si->xinput_DeviceMotionNotify) + while (XCheckTypedEvent (si->dpy, si->xinput_DeviceMotionNotify, &event)) + if (p->verbose_p) + fprintf (stderr, "%s: discarding DeviceMotionNotify event.\n", + blurb()); +#endif + + 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; + unsigned char decoded [MAX_BYTES_PER_CHAR * 10]; /* leave some slack */ + KeySym keysym = 0; + + /* XLookupString may return more than one character via XRebindKeysym; + and on some systems it returns multi-byte UTF-8 characters (contrary + to its documentation, which says it returns only Latin1.) + */ + int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded), + &keysym, compose_status); + +#if 0 + { + const char *ks = XKeysymToString (keysym); + int i; + fprintf(stderr, "## %-12s\t=> %d\t", (ks ? ks : "(null)"), decoded_size); + for (i = 0; i < decoded_size; i++) + fprintf(stderr, "%c", decoded[i]); + fprintf(stderr, "\t"); + for (i = 0; i < decoded_size; i++) + fprintf(stderr, "\\%03o", ((unsigned char *)decoded)[i]); + fprintf(stderr, "\n"); + } +#endif + + if (decoded_size > MAX_BYTES_PER_CHAR) + { + /* The multi-byte character returned is too large. */ + XBell (si->dpy, 0); + return; + } + + decoded[decoded_size] = 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; + + if (decoded_size == 1) /* Handle single-char commands */ + { + switch (*decoded) + { + case '\010': case '\177': /* Backspace */ + { + /* kludgey way to get the number of "logical" characters. */ + int nchars = strlen (pw->typed_passwd_char_size); + int nbytes = strlen (pw->typed_passwd); + if (nbytes <= 0) + XBell (si->dpy, 0); + else + { + int i; + for (i = pw->typed_passwd_char_size[nchars-1]; i >= 0; i--) + { + if (nbytes < 0) abort(); + pw->typed_passwd[nbytes--] = 0; + } + pw->typed_passwd_char_size[nchars-1] = 0; + } + } + break; + + case '\012': case '\015': /* Enter */ + finished_typing_passwd (si, pw); + break; + + case '\033': /* Escape */ + si->unlock_state = ul_cancel; + break; + + case '\025': case '\030': /* Erase line */ + memset (pw->typed_passwd, 0, sizeof (pw->typed_passwd)); + memset (pw->typed_passwd_char_size, 0, + sizeof (pw->typed_passwd_char_size)); + break; + + default: + if (*decoded < ' ' && *decoded != '\t') /* Other ctrl char */ + XBell (si->dpy, 0); + else + goto SELF_INSERT; + break; + } + } + else + { + int nbytes, nchars; + SELF_INSERT: + nbytes = strlen (pw->typed_passwd); + nchars = strlen (pw->typed_passwd_char_size); + if (nchars + 1 >= sizeof (pw->typed_passwd_char_size)-1 || + nbytes + decoded_size >= sizeof (pw->typed_passwd)-1) /* overflow */ + XBell (si->dpy, 0); + else + { + pw->typed_passwd_char_size[nchars] = decoded_size; + pw->typed_passwd_char_size[nchars+1] = 0; + memcpy (pw->typed_passwd + nbytes, decoded, decoded_size); + pw->typed_passwd[nbytes + decoded_size] = 0; + } + } + + if (pw->echo_input) + { + /* If the input is wider than the text box, only show the last portion, + to simulate a horizontally-scrolling text field. */ + int chars_in_pwfield = (pw->passwd_field_width / + pw->passwd_font->max_bounds.width); + const char *output = pw->typed_passwd; + if (strlen(output) > chars_in_pwfield) + output += (strlen(output) - chars_in_pwfield); + update_passwd_window (si, output, pw->ratio); + } + else if (pw->show_stars_p) + { + int nchars = strlen (pw->typed_passwd_char_size); + char *stars = 0; + stars = (char *) malloc(nchars + 1); + memset (stars, '*', nchars); + stars[nchars] = 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; + + /* 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 */ + } event; + + passwd_animate_timer ((XtPointer) si, 0); + + while (si->unlock_state == ul_read) + { + XtAppNextEvent (si->app, &event.x_event); + +#ifdef HAVE_RANDR + if (si->using_randr_extension && + (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 */ + + if (event.x_event.xany.window == si->passwd_dialog && + event.x_event.xany.type == Expose) + draw_passwd_window (si); + else if (event.x_event.xany.type == KeyPress) + { + handle_passwd_key (si, &event.x_event.xkey); + si->pw_data->caps_p = (event.x_event.xkey.state & LockMask); + } + else if (event.x_event.xany.type == ButtonPress || + event.x_event.xany.type == ButtonRelease) + { + si->pw_data->button_state_changed_p = True; + handle_unlock_button (si, &event.x_event); + if (si->pw_data->login_button_p) + handle_login_button (si, &event.x_event); + } + else + XtDispatchEvent (&event.x_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; + { + int j; + char *c = pw->typed_passwd_char_size; + for (j = 0; j < i; j++) + *c++ = 1; + *c = 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); + if (!newstr) + return NULL; + + (void) strcpy(newstr, str); + 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, OOM, etc.) + */ +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); + compose_status = 0; + + 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..85713935 --- /dev/null +++ b/driver/prefs.c @@ -0,0 +1,1647 @@ +/* dotfile.c --- management of the ~/.xscreensaver file. + * xscreensaver, Copyright (c) 1998-2011 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", /* not saved -- obsolete */ + "mitSaverExtension", /* not saved -- obsolete */ + "xidleExtension", /* not saved -- obsolete */ + "GetViewPortIsFullOfLies", + "procInterrupts", + "xinputExtensionDev", + "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") continue; /* don't save */ + CHECK("mitSaverExtension") continue; /* don't save */ + CHECK("xidleExtension") continue; /* don't save */ + CHECK("procInterrupts") type = pref_bool, b = p->use_proc_interrupts; + CHECK("xinputExtensionDev") type = pref_bool, b = p->use_xinput_extension; + 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; /* don't save */ + 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 /* obsolete. */ + p->use_sgi_saver_extension = get_boolean_resource (dpy, + "sgiSaverExtension", + "Boolean"); +#endif +#if 0 /* obsolete. */ + p->use_xinput_extension = get_boolean_resource (dpy, "xinputExtensionDev", + "Boolean"); +#endif +#if 0 /* broken and evil. */ + p->use_mit_saver_extension = get_boolean_resource (dpy, + "mitSaverExtension", + "Boolean"); +#endif + + 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..775036ac --- /dev/null +++ b/driver/remote.c @@ -0,0 +1,595 @@ +/* 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) + { +# if defined(HAVE_SELECT) + /* Wait for an event, but don't wait longer than 1 sec. Note that we + might do this multiple times if an event comes in, but it wasn't + the event we're waiting for. + */ + int fd = XConnectionNumber(dpy); + fd_set rset; + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + FD_ZERO (&rset); + FD_SET (fd, &rset); + select (fd+1, &rset, 0, 0, &tv); +# else /* !HAVE_SELECT */ + sleep(1); +# endif /* !HAVE_SELECT */ + } + + 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..02844b58 --- /dev/null +++ b/driver/splash.c @@ -0,0 +1,866 @@ +/* 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 border; + 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"); + sp->border = get_pixel_resource (si->dpy, cmap, + "splash.borderColor", + "Dialog.borderColor"); + + 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); + XSetWindowBorder (si->dpy, si->splash_dialog, sp->border); + + + 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..fb621cde --- /dev/null +++ b/driver/subprocs.c @@ -0,0 +1,1373 @@ +/* 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; + int errfds[2]; + int errin = -1, errout = -1; + 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]; + + if (!si->prefs.verbose_p) + { + if (pipe (errfds)) + { + perror ("error creating pipe:"); + return 0; + } + + errin = errfds [0]; + errout = errfds [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: + { + close (in); /* don't need this one */ + close (ConnectionNumber (si->dpy)); /* close display fd */ + + if (dup2 (out, STDOUT_FILENO) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stdout:"); + return 0; + } + + if (! si->prefs.verbose_p) + { + close(errin); + if (dup2 (errout, STDERR_FILENO) < 0) + { + perror ("could not dup() a new stderr:"); + 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); + + if (! si->prefs.verbose_p) + { + close (errout); + close (errin); + } + + /* 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..65615dd5 --- /dev/null +++ b/driver/test-passwd.c @@ -0,0 +1,303 @@ +/* xscreensaver, Copyright (c) 1998-2011 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 + +#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 on_p) {} +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) { } +void resize_screensaver_window (saver_info *si) { } +void describe_monitor_layout (saver_info *si) { } +Bool update_screen_layout (saver_info *si) { return 0; } + +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 +}; + +extern Bool debug_passwd_window_p; /* lock.c kludge */ + +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 (!setlocale(LC_ALL,"")) + fprintf (stderr, "%s: warning: could not set default locale\n", + progname); + + + 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; + + debug_passwd_window_p = True; + 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..760fd4cc --- /dev/null +++ b/driver/timers.c @@ -0,0 +1,1603 @@ +/* timers.c --- detecting when the user is idle, and other timer-related tasks. + * xscreensaver, Copyright (c) 1991-2011 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, True); +#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) || + si->using_xinput_extension); + + const char *why = 0; /* What caused the idle-state to change? */ + + 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. */ + why = "timeout"; + 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! */ + why = "large wall clock change"; + 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)) + { + why = "ClientMessage"; + 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. + */ + why = (event.x_event.xany.type == MotionNotify ?"mouse motion": + event.x_event.xany.type == KeyPress?"keyboard activity": + event.x_event.xany.type == ButtonPress ? "mouse click" : + "unknown user activity"); + 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) + { + why = "MIT ScreenSaverOn"; + 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) + { + why = "MIT ScreenSaverOff"; + 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) + { + why = "SGI ScreenSaverStart"; + 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) + { + why = "SGI ScreenSaverEnd"; + goto DONE; + } + } + else +#endif /* HAVE_SGI_SAVER_EXTENSION */ + +#ifdef HAVE_XINPUT + if ((!until_idle_p) && + (si->num_xinput_devices > 0) && + (event.x_event.type == si->xinput_DeviceMotionNotify || + event.x_event.type == si->xinput_DeviceButtonPress)) + /* Ignore DeviceButtonRelease, see ButtonRelease comment above. */ + { + + dispatch_event (si, &event.x_event); + if (si->demoing_p && + event.x_event.type == si->xinput_DeviceMotionNotify) + /* 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. + */ + { + why = (event.x_event.type == si->xinput_DeviceMotionNotify + ? "XI mouse motion" : + event.x_event.type == si->xinput_DeviceButtonPress + ? "XI mouse click" : "unknown XINPUT event"); + goto DONE; + } + } + else +#endif /* HAVE_XINPUT */ + +#ifdef HAVE_RANDR + if (si->using_randr_extension && + (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 (p->verbose_p) + { + if (! why) why = "unknown reason"; + fprintf (stderr, "%s: %s (%s)\n", blurb(), + (until_idle_p ? "user is idle" : "user is active"), + why); + } + + /* 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..63bec258 --- /dev/null +++ b/driver/types.h @@ -0,0 +1,420 @@ +/* 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 use_xinput_extension; + + 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; + Bool using_randr_extension; +# endif + + Bool using_xinput_extension; /* Note that `p->use_*' is the *request*, */ + /* and `si->using_*' is the *reality*. */ +#ifdef HAVE_XINPUT + int xinput_ext_event_number; /* may not be used */ + int xinput_ext_error_number; + int xinput_DeviceButtonPress; /* Extension device event codes. */ + int xinput_DeviceButtonRelease; /* Assigned by server at runtime */ + int xinput_DeviceMotionNotify; + struct xinput_dev_info *xinput_devices; + int num_xinput_devices; +# 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..e01a992c --- /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-2011 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, True); + 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..9f679665 --- /dev/null +++ b/driver/xdpyinfo.c @@ -0,0 +1,1098 @@ +/* + * $ 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); + +# if defined(GL_SAMPLE_BUFFERS) +# define SB GL_SAMPLE_BUFFERS +# define SM GL_SAMPLES +# elif defined(GLX_SAMPLE_BUFFERS) +# define SB GLX_SAMPLE_BUFFERS +# define SM GLX_SAMPLES +# elif defined(GLX_SAMPLE_BUFFERS_ARB) +# define SB GLX_SAMPLE_BUFFERS_ARB +# define SM GLX_SAMPLES_ARB +# elif defined(GLX_SAMPLE_BUFFERS_SGIS) +# define SB GLX_SAMPLE_BUFFERS_SGIS +# define SM GLX_SAMPLES_SGIS +# endif + +# ifdef SB + if (!glXGetConfig (dpy, vip, SB, &value) && value != 0) + { + int bufs = value; + if (!glXGetConfig (dpy, vip, SM, &value)) + printf (" GLX multisample: %d, %d\n", bufs, value); + } +# endif /* SB */ + + 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 -