summaryrefslogblamecommitdiffstats
path: root/driver/demo-Gtk.c
blob: b5e82e26af7a55766a8cd3d488e5a9cc38de1146 (plain) (tree)
1
2
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
                                                                           
                                                                    















                                                                              

                  


















                                                                               





                                                 
                                                    




















                                                                         



                                     








                                                                        









                            




                             





                                                             


                           


                    
                  
                                                      
                                                                

                   
                   
                




                          
                  

























































                                                                              

















                                                                               
                                             





































                                                                              

















                                                          
                                                                             
   






                                                        




















                                                                             
                                                        



                                          


                                








                                           
                 
     
                                                                  

                                                                 


                                                                   
            


                                     




                                           









                                                                          

             
                               

                          

                                                                         


                            
               
 
                                                    

     
                                                            










                                                                      
                                                             






































































































































































                                                                             



                                                  



























































































































































































































                                                                              

                                                                         


                    
                                                         

















                                                                        
                                                          
 



                                                    
                  
                  

                                                                         





                                               
         


                                                                       
                                                                     
















                                                                     
                                                                              












                                                               

                                                           


















                                                                         
                                     























































                                                                              
              



                    
                                                        













                                                                           
                                                                   


                                               
                                                                            







                                                  
                                                         






                                                            
                                                             






                                                            
                                                         






                                                            
                                                         












                                                                
                                            














































                                                                              


                                           








                                                                             
   

                                                                            
                                                      





















                                                                    




































































































                                                                               
                      




































































































































































































































































































                                                                               


















                                       






                                                                      
                               





















































































                                                                                           
















                                                                         












                                                                 
                                   



                                           

                                                     

















                                                                      

                                                                       











                                                                      




















                                                                         



                                

                                                    


















                                                   













                                           
                                       


























                                                                           
                                                   


















                                                                    

                                                                                   
 
                                                   











































                                                                              
                                            














































                                                                              

                                                                     














































                                                                         
                                       


















































































































































































































































































































































































































                                                                               





                                                          






































































































































































































































































































































































































































































                                                                              
                               



















































                                                                                                              






                                                             











































                                                                           



















































                                                                             

                                                         
                                                                       




                                                  
                                      



                                
                                
                                 






















                                                                       









                                                                               
                                                          
 

                                                              





                 









                                                                        









                                                        


                                                     



































                                                                            







                                                     























                                                            
                                                                          










                                                                        

                                                                               






































































                                                                              
                                     












































































































































































































                                                                                    
                                                                                   
















































                                                                  
                                                                    










































































































                                                                           




                                                                          


















































































































































































































































































































                                                                            
                                      

































































































































































































                                                                              




                                                                              




























































































































































































































































































































































































































                                                                               


                                                               































                                                                           

                                                   









                              




                                                                 


















                                             
                         
                              
                         

































































































































































































































































































































                                                                               
                                          



















































































































































































































                                                                            
                                                                      





































                                                                              
                                






















                                                                        
                                

















































































                                                                             




















                                                                         
 



                                                






































































































                                                                               
                                                                   











                                                                       









































                                                                              
/* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
 * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
 *
 * 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 "blurb.h"

#include <xscreensaver-intl.h>

#include <stdlib.h>

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

# ifdef __GNUC__
#  define STFU __extension__  /* ignore gcc -pendantic warnings in next sexp */
# else
#  define STFU /* */
# endif


#ifdef ENABLE_NLS
# include <locale.h>
#endif /* ENABLE_NLS */

#ifdef HAVE_UNAME
# include <sys/utsname.h>	/* for uname() */
#endif /* HAVE_UNAME */

#include <stdio.h>
#include <time.h>
#include <pwd.h>		/* for getpwuid() */
#include <sys/stat.h>
#include <sys/time.h>


#include <signal.h>
#include <errno.h>
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>		/* for waitpid() and associated macros */
#endif


#include <X11/Xproto.h>		/* for CARD32 */
#include <X11/Xatom.h>		/* for XA_INTEGER */
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

/* We don't actually use any widget internals, but these are included
   so that gdb will have debug info for the widgets... */
#include <X11/IntrinsicP.h>
#include <X11/ShellP.h>

#ifdef HAVE_XINERAMA
# include <X11/extensions/Xinerama.h>
#endif /* HAVE_XINERAMA */

#if (__GNUC__ >= 4)	/* Ignore useless warnings generated by gtk.h */
# undef inline
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstrict-prototypes"
# pragma GCC diagnostic ignored "-Wlong-long"
# pragma GCC diagnostic ignored "-Wvariadic-macros"
# pragma GCC diagnostic ignored "-Wpedantic"
#endif

#include <gtk/gtk.h>

#ifdef HAVE_CRAPPLET
# include <gnome.h>
# include <capplet-widget.h>
#endif

#include <gdk/gdkx.h>

#ifdef HAVE_GTK2
# include <gmodule.h>
#else  /* !HAVE_GTK2 */
# define G_MODULE_EXPORT /**/
#endif /* !HAVE_GTK2 */

#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

#if (__GNUC__ >= 4)
# pragma GCC diagnostic pop
#endif


#include "version.h"
#include "types.h"
#include "resources.h"		/* for parse_time() */
#include "remote.h"		/* for xscreensaver_command() */
#include "visual.h"
#include "atoms.h"
#include "usleep.h"
#include "xmu.h"

#include "logo-50.xpm"
#include "logo-180.xpm"

#include "demo-Gtk-conf.h"
#include "atoms.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#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)))


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
  GtkBuilder *gtk_ui;           /* 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;

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 Gtk-generated code, to avoid warnings.
 */
void exit_menu_cb (GtkAction *, gpointer user_data);
void about_menu_cb (GtkAction *, gpointer user_data);
void doc_menu_cb (GtkAction *, gpointer user_data);
void file_menu_cb (GtkAction *, gpointer user_data);
void activate_menu_cb (GtkAction *, gpointer user_data);
void lock_menu_cb (GtkAction *, gpointer user_data);
void kill_menu_cb (GtkAction *, 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);
void preview_theme_cb (GtkWidget *, gpointer user_data);

static void kill_gnome_screensaver (void);
static void kill_kde_screensaver (void);

/* Some random utility functions
 */

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->gtk_ui)
    {
      /* First try to load the UI file from the current directory;
         if there isn't one there, check the installed directory.
       */
# define UI_FILE "xscreensaver.ui"
      const char * const files[] = { UI_FILE,
                                     DEFAULT_ICONDIR "/" UI_FILE };
      int i;

      s->gtk_ui = gtk_builder_new ();

      for (i = 0; i < countof (files); i++)
        {
          struct stat st;
          if (!stat (files[i], &st))
            {
              GError* error = NULL;

              if (gtk_builder_add_from_file (s->gtk_ui, files[i], &error))
                break;
              else
                {
                  g_warning ("Couldn't load builder file %s: %s",
			     files[i], error->message);
                  g_error_free (error);
                }
            }
        }
      if (i >= countof (files))
	{
	  fprintf (stderr,
                   "%s: could not load \"" UI_FILE "\"\n"
                   "\tfrom " DEFAULT_ICONDIR "/ or current directory.\n",
                   blurb());
          exit (-1);
	}
# undef UI_FILE

      gtk_builder_connect_signals (s->gtk_ui, NULL);
    }

  w = GTK_WIDGET (gtk_builder_get_object (s->gtk_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 UI 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 warning dialog?"
               "\n\n\t%s\n\n",
               progname, message);
      free(msg);
      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
   GTK work on Cygwin; apparently all GTK callbacks need this magic extra
   declaration.  I do not pretend to understand.
 */

G_MODULE_EXPORT void
exit_menu_cb (GtkAction *menu_action, 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 (GtkAction *menu_action, gpointer user_data)
{
#if 1
  /* Let's just pop up the splash dialog instead. */
  preview_theme_cb (NULL, user_data);
#else
  char msg [2048];
  char copy[1024];
  char *desc = _("For updates, check https://www.jwz.org/xscreensaver/");

  char *version = strdup (screensaver_id + 17);
  char *year = strchr (version, '-');
  char *s = strchr (version, ' ');
  *s = 0;
  year = strchr (year+1, '-') + 1;
  s = strchr (year, ')');
  *s = 0;

  /* Ole Laursen <olau@hardworking.dk> 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-%s %s"), year, s);
#else  /* !HAVE_GTK2 */
  sprintf(copy, ("Copyright \251 1991-%s %s"), year, 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, version, "", 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;
    GSList *proxies = gtk_action_get_proxies (menu_action);
    GtkWidget *parent = GTK_WIDGET (proxies->data);
    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 (version);
    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)));
  }
#endif /* 0 */
}


G_MODULE_EXPORT void
doc_menu_cb (GtkAction *menu_action, 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) * 5) + 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, 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 (GtkAction *menu_action, 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 (GtkAction *menu_action, 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 (GtkAction *menu_action, 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 (GtkAction *menu_action, 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 -splash &") < 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)
        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 `sudo' 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 (!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));
  if (!model) abort();
  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); */
              memmove(s0, s, strlen(s) + 1);
              s = s0-1;
            }
        }
      else if (*s == '/' && !strncmp (s, "/./", 3)) {	/* delete "/./" */
        /* strcpy (s, s+2), s--; */
        memmove(s, s+2, strlen(s+2) + 1);
        s--;
       }
      else if (*s == '/' && !strncmp (s, "/.\000", 3))	/* delete "/.$" */
        *s = 0, s--;
    }

  /*
    Normalize consecutive slashes.
    Ignore doubled slashes after ":" to avoid mangling URLs.
  */

  for (s = p2; s && *s; s++){
    if (*s == ':') continue;
    if (!s[1] || !s[2]) continue;
    while (s[1] == '/' && s[2] == '/')
      /* strcpy (s+1, s+2); */
      memmove (s+1, s+2, strlen(s+2) + 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 */


static char *
theme_name_strip (const char *s)
{
  const char *in = s;
  char *s2 = strdup(s);
  char *out = s2;
  for (; *in; in++)
    if (*in >= 'A' && *in <= 'Z')
      *out++ = *in + ('a'-'A');
    else if (*in == ' ' || *in == '\t')
      ;
    else
      *out++ = *in;
  *out = 0;
  return s2;
}


/* 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)
{
  Display *dpy = GDK_DISPLAY();
  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 *) g_strdup(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");
  CHECKBOX (p2->dpms_quickoff_p,  "dpms_quickoff_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->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->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
     - image directory is not a URL
   */
  if (p2->image_directory &&
      *p2->image_directory &&
      !directory_p (p2->image_directory) &&
       strncmp(p2->image_directory, "http://", 7) &&
       strncmp(p2->image_directory, "https://", 8) &&
        ( !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. */
  {
    GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
    int menu_elt = gtk_combo_box_get_active (opt);
    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);
    }

  /* Theme menu. */
  {
    GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu"));
    char *themes = get_string_resource (dpy, "themeNames", "ThemeNames");
    int menu_index = gtk_combo_box_get_active (cbox);
    char *token = themes;
    char *name, *last;
    int i = 0;
    while ((name = strtok_r (token, ",", &last)))
      {
        token = 0;
        if (i == menu_index)
          {
            char *name2 = theme_name_strip (name);
            if (p->dialog_theme) free (p->dialog_theme);
            p2->dialog_theme = name2;
          }
        i++;
      }
  }

# define COPY(field, name) \
  if (p->field != p2->field) { \
    changed = True; \
    if (s->debug_p) \
      fprintf (stderr, "%s: %s => %ld\n", blurb(), \
               name, (unsigned long) 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_quickoff_p, "dpms_quickoff_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(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");

  COPY(dialog_theme,   "dialog_theme");
# 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)
    {
      sync_server_dpms_settings (GDK_DISPLAY(), p);
      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"));
  GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo"));
  GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis)));

  const char *visual = gtk_entry_get_text (visent);
  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 (visent, _("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;

  int menu_index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
  saver_mode 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;

  if (gdk_pointer_is_grabbed()) return;

  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 */

  /* No warning for URLs. */
  if ((!directory_p (path)) && strncmp(path, "http://", 6))
    {
      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
preview_theme_cb (GtkWidget *w, gpointer user_data)
{
  if (system ("xscreensaver-auth --splash &") < 0)
    fprintf (stderr, "%s: splash exec failed\n", blurb());
}


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, "<span foreground=\"#%02X%02X%02X\""
                      /*     " background=\"#%02X%02X%02X\""  */
                        ">%s</span>",
                   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)
{
  Display *dpy = GDK_DISPLAY();
  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 ("splash_button",     p->splash_p);
#endif
  TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
  TOGGLE_ACTIVE ("dpms_quickoff_button", p->dpms_quickoff_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 ("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);


  /* Theme menu */
  {
    GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu"));

    /* Without this, pref_changed_cb gets called an exponentially-increasing
       number of times on the themes menu, despite the call to
       gtk_list_store_clear(). */
    static Bool done_once = False;

    if (cbox && !done_once)
      {
        char *themes = get_string_resource (dpy, "themeNames", "ThemeNames");
        char *token = themes;
        char *name, *name2, *last;
        GtkListStore *model;
        GtkTreeIter iter;
        int i = 0;
        done_once = True;

        g_object_get (G_OBJECT (cbox), "model", &model, NULL);
        if (!model) abort();
        gtk_list_store_clear (model);

        gtk_signal_connect (GTK_OBJECT (cbox), "changed",
                            GTK_SIGNAL_FUNC (pref_changed_cb), (gpointer) s);

        while ((name = strtok_r (token, ",", &last)))
          {
            int L;
            token = 0;

            /* Strip leading and trailing whitespace */
            while (*name == ' ' || *name == '\t' || *name == '\n')
              name++;
            L = strlen(name);
            while (L && (name[L-1] == ' ' || name[L-1] == '\t' ||
                         name[L-1] == '\n'))
              name[--L] = 0;

            gtk_list_store_append (model, &iter);
            gtk_list_store_set (model, &iter, 0, name, -1);

            name2 = theme_name_strip (name);
            if (!strcmp (p->dialog_theme, name2))
              gtk_combo_box_set_active (cbox, i);
            free (name2);
            i++;
          }
      }
  }


  /* Map the `saver_mode' enum to mode menu to values. */
  {
    GtkComboBox *opt = GTK_COMBO_BOX (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_combo_box_set_active (opt, i);
    update_list_sensitivity (s);
  }

  {
    Bool dpms_supported = False;
    Display *dpy = GDK_DISPLAY();

#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);
    SENSITIZE ("dpms_quickoff_button",    dpms_supported);

    SENSITIZE ("fade_label",      (p->fade_p || p->unfade_p));
    SENSITIZE ("fade_spinbutton", (p->fade_p || p->unfade_p));

# undef SENSITIZE
  }
}


/* Allow the documentation label to re-flow when the text is changed.
   http://blog.borovsak.si/2009/05/wrapping-adn-resizing-gtklabel.html
 */
static void
cb_allocate (GtkWidget *label, GtkAllocation *allocation, gpointer data)
{
  gtk_widget_set_size_request (label, allocation->width - 8, -1);
}


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);
   */

  g_signal_connect (G_OBJECT (doc), "size-allocate", 
                    G_CALLBACK (cb_allocate), NULL);

# 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.")));

  {
    GtkWidget *w = name_to_widget (s, "dialog_vbox");
    gtk_widget_hide (w);
    gtk_widget_unrealize (w);
    gtk_widget_realize (w);
    gtk_widget_show (w);
  }
}


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_action", "lock_action", "kill_action",
                          /* "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++)
    {
      GtkAction *a = GTK_ACTION (gtk_builder_get_object (s->gtk_ui, names[i]));
      gtk_action_set_sensitive (a, 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_BOX_ENTRY (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"));
  GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (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_bin_get_child (GTK_BIN (vis))),
                      (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));
      gchar *s2 = g_str_to_ascii (name, 0);  /* Sort "Möbius" properly */
      gchar *s3 = g_ascii_strdown (s2, -1);
      free (name);
      free (s2);
      sort_hack_cmp_names_kludge[i] = s3;
    }

  /* 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-visual";
  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
    {
      /* We do this instead of relying on $XSCREENSAVER_WINDOW specifically
         so that third-party savers that don't implement -window-id will fail:
         otherwise we might have full-screen windows popping up when we were
         just trying to get a preview thumbnail.
       */
      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;
  GdkPixmap *pixmap =
    gdk_pixmap_create_from_xpm_d (window, &mask, 0,
                                  (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") ||
              !strcmp ((char *) name, "mate-screensaver") ||
              !strcmp ((char *) name, "cinnamon-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];
  if (!p) return False;
  if (!fgets (buf, sizeof(buf)-1, p)) return False;
  pclose (p);
  if (!strcmp (buf, "true\n"))
    return True;
  else
    return False;
}

static void
kill_kde_screensaver (void)
{
  /* Use empty body to kill warning from gcc -Wall with
     "warning: ignoring return value of 'system',
      declared with attribute warn_unused_result"
  */
  if (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 = "<UNKNOWN>";
  else
    lhost = uts.nodename;
# else  /* !HAVE_UNAME */
  strcat (lhost, "<UNKNOWN>");
# endif /* !HAVE_UNAME */

  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" : ""));
}


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

static 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");
}

static 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 = XSCREENSAVER_VERSION;

  /* 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, (char **) &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);

  init_xscreensaver_atoms (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

  init_xscreensaver_atoms (dpy);

  /* 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. */
  gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "mode_menu")),
                      "changed", GTK_SIGNAL_FUNC (mode_menu_item_cb),
                      (gpointer) s);
  if (s->nscreens <= 1)
    {
      GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
      GtkTreeModel *list = gtk_combo_box_get_model (opt);
      unsigned int i;
      for (i = 0; i < countof(mode_menu_order); i++)
        {
          /* The "random-same" mode menu item does not appear unless
             there are multiple screens.
           */
          if (mode_menu_order[i] == RANDOM_HACKS_SAME)
            {
              GtkTreeIter iter;
              gtk_tree_model_iter_nth_child (list, &iter, NULL, i);
              gtk_list_store_remove (GTK_LIST_STORE (list), &iter);
              break;
            }
        }

        /* recompute option-menu size */
        gtk_widget_unrealize (GTK_WIDGET (opt));
        gtk_widget_realize (GTK_WIDGET (opt));
    }


  /* 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);


  if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17)
    warning_dialog (s->toplevel_widget,
      _("Warning:\n\n"
        "This version of xscreensaver is VERY OLD!\n"
        "Please upgrade!\n"
        "\n"
        "https://www.jwz.org/xscreensaver/\n"
        "\n"
        "(If this is the latest version that your distro ships, then\n"
        "your distro is doing you a disservice. Build from source.)\n"
        ),
      D_NONE, 7);

  /* 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 */