~cyphermox/ubuntu/precise/xscreensaver/merge-5.15-2

« back to all changes in this revision

Viewing changes to OSX/SaverRunner.m

  • Committer: Mathieu Trudel-Lapierre
  • Date: 2011-12-21 15:57:35 UTC
  • mfrom: (1.1.13 upstream)
  • Revision ID: mathieu@canonical.com-20111221155735-m43kxy7824n1p36y
Merging shared upstream rev into target branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* xscreensaver, Copyright (c) 2006-2011 Jamie Zawinski <jwz@jwz.org>
 
2
 *
 
3
 * Permission to use, copy, modify, distribute, and sell this software and its
 
4
 * documentation for any purpose is hereby granted without fee, provided that
 
5
 * the above copyright notice appear in all copies and that both that
 
6
 * copyright notice and this permission notice appear in supporting
 
7
 * documentation.  No representations are made about the suitability of this
 
8
 * software for any purpose.  It is provided "as is" without express or 
 
9
 * implied warranty.
 
10
 */
 
11
 
 
12
/* This program serves two purposes:
 
13
 
 
14
   First, It is a test harness for screen savers.  When it launches, it
 
15
   looks around for .saver bundles (in the current directory, and then in
 
16
   the standard directories) and puts up a pair of windows that allow you
 
17
   to select the saver to run.  This is less clicking than running them
 
18
   through System Preferences.  This is the "SaverTester.app" program.
 
19
 
 
20
   Second, it can be used to transform any screen saver into a standalone
 
21
   program.  Just put one (and only one) .saver bundle into the app
 
22
   bundle's Contents/PlugIns/ directory, and it will load and run that
 
23
   saver at start-up (without the saver-selection menu or other chrome).
 
24
   This is how the "Phosphor.app" and "Apple2.app" programs work.
 
25
 */
 
26
 
 
27
#import "SaverRunner.h"
 
28
#import "XScreenSaverGLView.h"
 
29
 
 
30
@implementation SaverRunner
 
31
 
 
32
- (ScreenSaverView *) makeSaverView: (NSString *) module
 
33
{
 
34
  NSString *name = [module stringByAppendingPathExtension:@"saver"];
 
35
  NSString *path = [saverDir stringByAppendingPathComponent:name];
 
36
  saverBundle = [NSBundle bundleWithPath:path];
 
37
  Class new_class = [saverBundle principalClass];
 
38
  NSAssert1 (new_class, @"unable to load \"%@\"", path);
 
39
 
 
40
 
 
41
  NSRect rect;
 
42
  rect.origin.x = rect.origin.y = 0;
 
43
  rect.size.width = 320;
 
44
  rect.size.height = 240;
 
45
 
 
46
  id instance = [[new_class alloc] initWithFrame:rect isPreview:YES];
 
47
  NSAssert1 (instance, @"unable to instantiate %@", new_class);
 
48
 
 
49
 
 
50
  /* KLUGE: Inform the underlying program that we're in "standalone"
 
51
     mode.  This is kind of horrible but I haven't thought of a more
 
52
     sensible way to make this work.
 
53
   */
 
54
  if ([saverNames count] == 1) {
 
55
    putenv (strdup ("XSCREENSAVER_STANDALONE=1"));
 
56
  }
 
57
 
 
58
  return (ScreenSaverView *) instance;
 
59
}
 
60
 
 
61
 
 
62
static ScreenSaverView *
 
63
find_saverView_child (NSView *v)
 
64
{
 
65
  NSArray *kids = [v subviews];
 
66
  int nkids = [kids count];
 
67
  int i;
 
68
  for (i = 0; i < nkids; i++) {
 
69
    NSObject *kid = [kids objectAtIndex:i];
 
70
    if ([kid isKindOfClass:[ScreenSaverView class]]) {
 
71
      return (ScreenSaverView *) kid;
 
72
    } else {
 
73
      ScreenSaverView *sv = find_saverView_child ((NSView *) kid);
 
74
      if (sv) return sv;
 
75
    }
 
76
  }
 
77
  return 0;
 
78
}
 
79
 
 
80
 
 
81
static ScreenSaverView *
 
82
find_saverView (NSView *v)
 
83
{
 
84
  while (1) {
 
85
    NSView *p = [v superview];
 
86
    if (p) v = p;
 
87
    else break;
 
88
  }
 
89
  return find_saverView_child (v);
 
90
}
 
91
 
 
92
 
 
93
static void
 
94
relabel_menus (NSObject *v, NSString *old_str, NSString *new_str)
 
95
{
 
96
  if ([v isKindOfClass:[NSMenu class]]) {
 
97
    NSMenu *m = (NSMenu *)v;
 
98
    [m setTitle: [[m title] stringByReplacingOccurrencesOfString:old_str
 
99
                            withString:new_str]];
 
100
    NSArray *kids = [m itemArray];
 
101
    int nkids = [kids count];
 
102
    int i;
 
103
    for (i = 0; i < nkids; i++) {
 
104
      relabel_menus ([kids objectAtIndex:i], old_str, new_str);
 
105
    }
 
106
  } else if ([v isKindOfClass:[NSMenuItem class]]) {
 
107
    NSMenuItem *mi = (NSMenuItem *)v;
 
108
    [mi setTitle: [[mi title] stringByReplacingOccurrencesOfString:old_str
 
109
                              withString:new_str]];
 
110
    NSMenu *m = [mi submenu];
 
111
    if (m) relabel_menus (m, old_str, new_str);
 
112
  }
 
113
}
 
114
 
 
115
 
 
116
- (void) openPreferences: (id) sender
 
117
{
 
118
  ScreenSaverView *sv;
 
119
 
 
120
  if ([sender isKindOfClass:[NSView class]]) {  // Sent from button
 
121
    sv = find_saverView ((NSView *) sender);
 
122
  } else {
 
123
    int i;
 
124
    NSWindow *w = 0;
 
125
    for (i = [windows count]-1; i >= 0; i--) {  // Sent from menubar
 
126
      w = [windows objectAtIndex:i];
 
127
      if ([w isKeyWindow]) break;
 
128
    }
 
129
    sv = find_saverView ([w contentView]);
 
130
  }
 
131
 
 
132
  NSAssert (sv, @"no saver view");
 
133
  NSWindow *prefs = [sv configureSheet];
 
134
 
 
135
  [NSApp beginSheet:prefs
 
136
     modalForWindow:[sv window]
 
137
      modalDelegate:self
 
138
     didEndSelector:@selector(preferencesClosed:returnCode:contextInfo:)
 
139
        contextInfo:nil];
 
140
  int code = [NSApp runModalForWindow:prefs];
 
141
  
 
142
  /* Restart the animation if the "OK" button was hit, but not if "Cancel".
 
143
     We have to restart *both* animations, because the xlockmore-style
 
144
     ones will blow up if one re-inits but the other doesn't.
 
145
   */
 
146
  if (code != NSCancelButton) {
 
147
    [sv stopAnimation];
 
148
    [sv startAnimation];
 
149
  }
 
150
}
 
151
 
 
152
- (void) preferencesClosed: (NSWindow *) sheet
 
153
                returnCode: (int) returnCode
 
154
               contextInfo: (void  *) contextInfo
 
155
{
 
156
  [NSApp stopModalWithCode:returnCode];
 
157
}
 
158
 
 
159
 
 
160
- (void)loadSaver:(NSString *)name
 
161
{
 
162
  int i;
 
163
  for (i = 0; i < [windows count]; i++) {
 
164
    NSWindow *window = [windows objectAtIndex:i];
 
165
    NSView *cv = [window contentView];
 
166
    ScreenSaverView *old_view = find_saverView (cv);
 
167
    NSView *sup = [old_view superview];
 
168
 
 
169
    NSString *old_title = [window title];
 
170
    if (!old_title) old_title = @"XScreenSaver";
 
171
    [window setTitle: name];
 
172
    relabel_menus (menubar, old_title, name);
 
173
 
 
174
    [old_view stopAnimation];
 
175
    [old_view removeFromSuperview];
 
176
 
 
177
    ScreenSaverView *new_view = [self makeSaverView:name];
 
178
    [new_view setFrame: [old_view frame]];
 
179
    [sup addSubview: new_view];
 
180
    [window makeFirstResponder:new_view];
 
181
    [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
 
182
    [new_view startAnimation];
 
183
  }
 
184
 
 
185
  NSUserDefaultsController *ctl =
 
186
    [NSUserDefaultsController sharedUserDefaultsController];
 
187
  [ctl save:self];
 
188
}
 
189
 
 
190
 
 
191
- (void)aboutPanel:(id)sender
 
192
{
 
193
  NSDictionary *bd = [saverBundle infoDictionary];
 
194
  NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20];
 
195
 
 
196
  [d setValue:[bd objectForKey:@"CFBundleName"] forKey:@"ApplicationName"];
 
197
  [d setValue:[bd objectForKey:@"CFBundleVersion"] forKey:@"Version"];
 
198
  [d setValue:[bd objectForKey:@"CFBundleShortVersionString"] 
 
199
     forKey:@"ApplicationVersion"];
 
200
  [d setValue:[bd objectForKey:@"NSHumanReadableCopyright"] forKey:@"Copy"];
 
201
  [d setValue:[[NSAttributedString alloc]
 
202
                initWithString: (NSString *) 
 
203
                  [bd objectForKey:@"CFBundleGetInfoString"]]
 
204
     forKey:@"Credits"];
 
205
 
 
206
  [[NSApplication sharedApplication]
 
207
    orderFrontStandardAboutPanelWithOptions:d];
 
208
}
 
209
 
 
210
 
 
211
 
 
212
- (void)selectedSaverDidChange:(NSDictionary *)change
 
213
{
 
214
  NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
 
215
  NSString *name = [prefs stringForKey:@"selectedSaverName"];
 
216
 
 
217
  if (! [saverNames containsObject:name]) {
 
218
    NSLog (@"Saver \"%@\" does not exist", name);
 
219
    return;
 
220
  }
 
221
 
 
222
  if (name) [self loadSaver: name];
 
223
}
 
224
 
 
225
 
 
226
- (NSArray *) listSaverBundleNamesInDir:(NSString *)dir
 
227
{
 
228
  NSArray *files = [[NSFileManager defaultManager]
 
229
                     contentsOfDirectoryAtPath:dir error:nil];
 
230
  if (! files) return 0;
 
231
 
 
232
  int n = [files count];
 
233
  NSMutableArray *result = [NSMutableArray arrayWithCapacity: n+1];
 
234
 
 
235
  int i;
 
236
  for (i = 0; i < n; i++) {
 
237
    NSString *p = [files objectAtIndex:i];
 
238
    if ([[p pathExtension] caseInsensitiveCompare:@"saver"]) 
 
239
      continue;
 
240
    [result addObject: [[p lastPathComponent] stringByDeletingPathExtension]];
 
241
  }
 
242
 
 
243
  return result;
 
244
}
 
245
 
 
246
 
 
247
- (NSArray *) listSaverBundleNames
 
248
{
 
249
  NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10];
 
250
 
 
251
  // First look in the bundle itself.
 
252
  [dirs addObject: [[NSBundle mainBundle] builtInPlugInsPath]];
 
253
 
 
254
  // Then look in the same directory as the executable.
 
255
  [dirs addObject: [[[NSBundle mainBundle] bundlePath]
 
256
                     stringByDeletingLastPathComponent]];
 
257
 
 
258
  // Then look in standard screensaver directories.
 
259
  [dirs addObject: @"~/Library/Screen Savers"];
 
260
  [dirs addObject: @"/Library/Screen Savers"];
 
261
  [dirs addObject: @"/System/Library/Screen Savers"];
 
262
 
 
263
  int i;
 
264
  for (i = 0; i < [dirs count]; i++) {
 
265
    NSString *dir = [dirs objectAtIndex:i];
 
266
    NSArray *names = [self listSaverBundleNamesInDir:dir];
 
267
    if (! names) continue;
 
268
 
 
269
    // Make sure this directory is on $PATH.
 
270
 
 
271
    const char *cdir = [dir cStringUsingEncoding:NSUTF8StringEncoding];
 
272
    const char *opath = getenv ("PATH");
 
273
    if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
 
274
    char *npath = (char *) malloc (strlen (opath) + strlen (cdir) + 30);
 
275
    strcpy (npath, "PATH=");
 
276
    strcat (npath, cdir);
 
277
    strcat (npath, ":");
 
278
    strcat (npath, opath);
 
279
    if (putenv (npath)) {
 
280
      perror ("putenv");
 
281
      abort();
 
282
    }
 
283
    /* Don't free (npath) -- MacOS's putenv() does not copy it. */
 
284
 
 
285
    saverDir   = [dir retain];
 
286
    saverNames = [names retain];
 
287
 
 
288
    return names;
 
289
  }
 
290
 
 
291
  NSString *err = @"no .saver bundles found in: ";
 
292
  for (i = 0; i < [dirs count]; i++) {
 
293
    if (i) err = [err stringByAppendingString:@", "];
 
294
    err = [err stringByAppendingString:[[dirs objectAtIndex:i] 
 
295
                                         stringByAbbreviatingWithTildeInPath]];
 
296
    err = [err stringByAppendingString:@"/"];
 
297
  }
 
298
  NSLog (@"%@", err);
 
299
  exit (1);
 
300
}
 
301
 
 
302
 
 
303
- (NSPopUpButton *) makeMenu
 
304
{
 
305
  NSRect rect;
 
306
  rect.origin.x = rect.origin.y = 0;
 
307
  rect.size.width = 10;
 
308
  rect.size.height = 10;
 
309
  NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
 
310
                                                    pullsDown:NO];
 
311
  int i;
 
312
  float max_width = 0;
 
313
  for (i = 0; i < [saverNames count]; i++) {
 
314
    NSString *name = [saverNames objectAtIndex:i];
 
315
    [popup addItemWithTitle:name];
 
316
    [[popup itemWithTitle:name] setRepresentedObject:name];
 
317
    [popup sizeToFit];
 
318
    NSRect r = [popup frame];
 
319
    if (r.size.width > max_width) max_width = r.size.width;
 
320
  }
 
321
 
 
322
  // Bind the menu to preferences, and trigger a callback when an item
 
323
  // is selected.
 
324
  //
 
325
  NSString *key = @"values.selectedSaverName";
 
326
  NSUserDefaultsController *prefs =
 
327
    [NSUserDefaultsController sharedUserDefaultsController];
 
328
  [prefs addObserver:self
 
329
         forKeyPath:key
 
330
            options:0
 
331
            context:@selector(selectedSaverDidChange:)];
 
332
  [popup   bind:@"selectedObject"
 
333
       toObject:prefs
 
334
    withKeyPath:key
 
335
        options:nil];
 
336
  [prefs setAppliesImmediately:YES];
 
337
 
 
338
  NSRect r = [popup frame];
 
339
  r.size.width = max_width;
 
340
  [popup setFrame:r];
 
341
  return popup;
 
342
}
 
343
 
 
344
 
 
345
/* This is called when the "selectedSaverName" pref changes, e.g.,
 
346
   when a menu selection is made.
 
347
 */
 
348
- (void)observeValueForKeyPath:(NSString *)keyPath
 
349
                      ofObject:(id)object
 
350
                        change:(NSDictionary *)change
 
351
                       context:(void *)context
 
352
{
 
353
  SEL dispatchSelector = (SEL)context;
 
354
  if (dispatchSelector != NULL) {
 
355
    [self performSelector:dispatchSelector withObject:change];
 
356
  } else {
 
357
    [super observeValueForKeyPath:keyPath
 
358
                         ofObject:object
 
359
                           change:change
 
360
                          context:context];
 
361
  }
 
362
}
 
363
 
 
364
 
 
365
- (NSWindow *) makeWindow
 
366
{
 
367
  NSRect rect;
 
368
  static int count = 0;
 
369
  Bool simple_p = ([saverNames count] == 1);
 
370
  NSButton *pb = 0;
 
371
  NSPopUpButton *menu = 0;
 
372
  NSBox *gbox = 0;
 
373
  NSBox *pbox = 0;
 
374
 
 
375
  NSRect sv_rect;
 
376
  sv_rect.origin.x = sv_rect.origin.y = 0;
 
377
  sv_rect.size.width = 320;
 
378
  sv_rect.size.height = 240;
 
379
  ScreenSaverView *sv = [[ScreenSaverView alloc]  // dummy placeholder
 
380
                          initWithFrame:sv_rect
 
381
                          isPreview:YES];
 
382
 
 
383
  // make a "Preferences" button
 
384
  //
 
385
  if (! simple_p) {
 
386
    rect.origin.x = 0;
 
387
    rect.origin.y = 0;
 
388
    rect.size.width = rect.size.height = 10;
 
389
    pb = [[NSButton alloc] initWithFrame:rect];
 
390
    [pb setTitle:@"Preferences"];
 
391
    [pb setBezelStyle:NSRoundedBezelStyle];
 
392
    [pb sizeToFit];
 
393
 
 
394
    rect.origin.x = ([sv frame].size.width -
 
395
                     [pb frame].size.width) / 2;
 
396
    [pb setFrameOrigin:rect.origin];
 
397
  
 
398
    // grab the click
 
399
    //
 
400
    [pb setTarget:self];
 
401
    [pb setAction:@selector(openPreferences:)];
 
402
 
 
403
    // Make a saver selection menu
 
404
    //
 
405
    menu = [self makeMenu];
 
406
    rect.origin.x = 2;
 
407
    rect.origin.y = 2;
 
408
    [menu setFrameOrigin:rect.origin];
 
409
 
 
410
    // make a box to wrap the saverView
 
411
    //
 
412
    rect = [sv frame];
 
413
    rect.origin.x = 0;
 
414
    rect.origin.y = [pb frame].origin.y + [pb frame].size.height;
 
415
    gbox = [[NSBox alloc] initWithFrame:rect];
 
416
    rect.size.width = rect.size.height = 10;
 
417
    [gbox setContentViewMargins:rect.size];
 
418
    [gbox setTitlePosition:NSNoTitle];
 
419
    [gbox addSubview:sv];
 
420
    [gbox sizeToFit];
 
421
 
 
422
    // make a box to wrap the other two boxes
 
423
    //
 
424
    rect.origin.x = rect.origin.y = 0;
 
425
    rect.size.width  = [gbox frame].size.width;
 
426
    rect.size.height = [gbox frame].size.height + [gbox frame].origin.y;
 
427
    pbox = [[NSBox alloc] initWithFrame:rect];
 
428
    [pbox setTitlePosition:NSNoTitle];
 
429
    [pbox setBorderType:NSNoBorder];
 
430
    [pbox addSubview:gbox];
 
431
    if (menu) [pbox addSubview:menu];
 
432
    if (pb)   [pbox addSubview:pb];
 
433
    [pbox sizeToFit];
 
434
 
 
435
    [pb   setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
 
436
    [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
 
437
    [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
 
438
    [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
 
439
  }
 
440
 
 
441
  [sv     setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
 
442
 
 
443
 
 
444
  // and make a window to hold that.
 
445
  //
 
446
  NSScreen *screen = [NSScreen mainScreen];
 
447
  rect = pbox ? [pbox frame] : [sv frame];
 
448
  rect.origin.x = ([screen frame].size.width  - rect.size.width)  / 2;
 
449
  rect.origin.y = ([screen frame].size.height - rect.size.height) / 2;
 
450
  
 
451
  rect.origin.x += rect.size.width * (count ? 0.55 : -0.55);
 
452
  
 
453
  NSWindow *window = [[NSWindow alloc]
 
454
                      initWithContentRect:rect
 
455
                                styleMask:(NSTitledWindowMask |
 
456
                                           NSClosableWindowMask |
 
457
                                           NSMiniaturizableWindowMask |
 
458
                                           NSResizableWindowMask)
 
459
                                  backing:NSBackingStoreBuffered
 
460
                                    defer:YES
 
461
                                   screen:screen];
 
462
  [window setMinSize:[window frameRectForContentRect:rect].size];
 
463
 
 
464
  [[window contentView] addSubview: (pbox ? (NSView *) pbox : (NSView *) sv)];
 
465
 
 
466
  [window makeKeyAndOrderFront:window];
 
467
  
 
468
  [sv startAnimation]; // this is the dummy saver
 
469
 
 
470
  count++;
 
471
 
 
472
  return window;
 
473
}
 
474
 
 
475
 
 
476
- (void)applicationDidFinishLaunching: (NSNotification *) notif
 
477
{
 
478
  [self listSaverBundleNames];
 
479
 
 
480
  int n = ([saverNames count] == 1 ? 1 : 2);
 
481
  NSMutableArray *a = [[NSMutableArray arrayWithCapacity: n+1] retain];
 
482
  windows = a;
 
483
  int i;
 
484
  for (i = 0; i < n; i++) {
 
485
    NSWindow *window = [self makeWindow];
 
486
    // Get the last-saved window position out of preferences.
 
487
    [window setFrameAutosaveName:
 
488
              [NSString stringWithFormat:@"XScreenSaverWindow%d", i]];
 
489
    [window setFrameUsingName:[window frameAutosaveName]];
 
490
    [a addObject: window];
 
491
  }
 
492
 
 
493
  if (n == 1) {
 
494
    [self loadSaver:[saverNames objectAtIndex:0]];
 
495
  } else {
 
496
 
 
497
    /* In the XCode project, each .saver scheme sets this env var when
 
498
       launching SaverTester.app so that it knows which one we are
 
499
       currently debugging.  If this is set, it overrides the default
 
500
       selection in the popup menu.  If unset, that menu persists to
 
501
       whatever it was last time.
 
502
     */
 
503
    const char *forced = getenv ("SELECTED_SAVER");
 
504
    if (forced && *forced) {
 
505
      NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
 
506
      NSString *s = [NSString stringWithCString:(char *)forced
 
507
                              encoding:NSUTF8StringEncoding];
 
508
      NSLog (@"selecting saver %@", s);
 
509
      [prefs setObject:s forKey:@"selectedSaverName"];
 
510
    }
 
511
 
 
512
    [self selectedSaverDidChange:nil];
 
513
  }
 
514
}
 
515
 
 
516
 
 
517
/* When the window closes, exit (even if prefs still open.)
 
518
*/
 
519
- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n
 
520
{
 
521
  return YES;
 
522
}
 
523
 
 
524
@end