1 /* xscreensaver, Copyright (c) 2006 Jamie Zawinski <jwz@jwz.org>
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
12 /* This is a subclass of Apple's ScreenSaverView that knows how to run
13 xscreensaver programs without X11 via the dark magic of the "jwxyz"
14 library. In xscreensaver terminology, this is the replacement for
15 the "screenhack.c" module.
18 #import "XScreenSaverView.h"
19 #import "XScreenSaverConfigSheet.h"
20 #import "screenhackI.h"
21 #import "xlockmoreI.h"
22 #import "jwxyz-timers.h"
24 extern struct xscreensaver_function_table *xscreensaver_function_table;
26 /* Global variables used by the screen savers
29 const char *progclass;
33 @implementation XScreenSaverView
35 - (struct xscreensaver_function_table *) findFunctionTable
37 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
38 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
40 NSString *path = [nsb bundlePath];
41 NSString *name = [[[path lastPathComponent] stringByDeletingPathExtension]
43 NSString *suffix = @"_xscreensaver_function_table";
44 NSString *table_name = [name stringByAppendingString:suffix];
46 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
50 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
52 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
54 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
55 NSAssert2 (addr, @"no symbol \"%@\" in bundle %@", table_name, path);
57 // NSLog (@"%@ = 0x%08X", table_name, (unsigned long) addr);
58 return (struct xscreensaver_function_table *) addr;
62 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
63 // to $PATH for the benefit of savers that include helper shell scripts.
67 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
68 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
70 NSString *nsdir = [nsb resourcePath];
71 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
72 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
73 const char *opath = getenv ("PATH");
74 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
75 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30);
76 strcpy (npath, "PATH=");
79 strcat (npath, opath);
88 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
89 // (e.g., "xscreensaver-text") know how to look up resources.
91 - (void) setResourcesEnv:(NSString *) name
93 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
94 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
96 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
97 char *env = (char *) malloc (strlen (s) + 40);
98 strcpy (env, "XSCREENSAVER_CLASSPATH=");
109 add_default_options (const XrmOptionDescRec *opts,
110 const char * const *defs,
111 XrmOptionDescRec **opts_ret,
112 const char ***defs_ret)
114 /* These aren't "real" command-line options (there are no actual command-line
115 options in the Cocoa version); but this is the somewhat kludgey way that
116 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
117 ../hacks/config/*.xml files communicate with the preferences database.
119 static const XrmOptionDescRec default_options [] = {
120 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
121 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
122 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
123 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
124 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
125 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
126 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
127 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
128 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
131 static const char *default_defaults [] = {
136 ".grabDesktopImages: yes",
137 ".chooseRandomImages: no",
138 ".imageDirectory: ~/Pictures",
143 for (i = 0; default_options[i].option; i++)
145 for (i = 0; opts[i].option; i++)
148 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
149 calloc (count + 1, sizeof (*opts2));
153 while (default_options[j].option) {
154 opts2[i] = default_options[j];
158 while (opts[j].option) {
169 for (i = 0; default_defaults[i]; i++)
171 for (i = 0; defs[i]; i++)
174 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
178 while (default_defaults[j]) {
179 defs2[i] = default_defaults[j];
192 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
194 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
197 xsft = [self findFunctionTable];
202 xsft->setup_cb (xsft, xsft->setup_arg);
204 /* The plist files for these preferences show up in
205 $HOME/Library/Preferences/ByHost/ in a file named like
206 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
208 NSString *name = [NSString stringWithCString:xsft->progclass
209 encoding:NSUTF8StringEncoding];
210 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
211 [self setResourcesEnv:name];
214 XrmOptionDescRec *opts = 0;
215 const char **defs = 0;
216 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
217 prefsReader = [[PrefsReader alloc]
218 initWithName:name xrmKeys:opts defaults:defs];
220 // free (opts); // bah, we need these! #### leak!
221 xsft->options = opts;
223 progname = progclass = xsft->progclass;
232 NSAssert(![self isAnimating], @"still animating");
233 NSAssert(!xdata, @"xdata not yet freed");
235 jwxyz_free_display (xdpy);
236 [prefsReader release];
240 - (PrefsReader *) prefsReader
246 - (void) startAnimation
248 NSAssert(![self isAnimating], @"already animating");
249 NSAssert(!initted_p && !xdata, @"already initialized");
250 [super startAnimation];
251 /* We can't draw on the window from this method, so we actually do the
252 initialization of the screen saver (xsft->init_cb) in the first call
253 to animateOneFrame() instead.
257 - (void)stopAnimation
259 NSAssert([self isAnimating], @"not animating");
263 [self lockFocus]; // in case something tries to draw from here
264 [self prepareContext];
265 xsft->free_cb (xdpy, xwindow, xdata);
268 // setup_p = NO; // #### wait, do we need this?
273 [super stopAnimation];
277 /* Hook for the XScreenSaverGLView subclass
279 - (void) prepareContext
283 /* Hook for the XScreenSaverGLView subclass
285 - (void) resizeContext
289 - (void) animateOneFrame
294 xdpy = jwxyz_make_display (self);
295 xwindow = XRootWindow (xdpy, 0);
301 xsft->setup_cb (xsft, xsft->setup_arg);
305 NSAssert(!xdata, @"xdata already initialized");
310 XSetWindowBackground (xdpy, xwindow,
311 get_pixel_resource (xdpy, 0,
312 "background", "Background"));
313 XClearWindow (xdpy, xwindow);
315 [[self window] setAcceptsMouseMovedEvents:YES];
317 /* Kludge: even though the init_cb functions are declared to take 2 args,
318 actually call them with 3, for the benefit of xlockmore_init() and
321 void *(*init_cb) (Display *, Window, void *) =
322 (void *(*) (Display *, Window, void *)) xsft->init_cb;
324 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
327 /* I don't understand why we have to do this *every frame*, but we do,
328 or else the cursor comes back on.
330 if (![self isPreview])
331 [NSCursor setHiddenUntilMouseMoves:YES];
333 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
334 This is bad, because some of the screen hacks want to delay for long
335 periods (like 5 seconds or a minute!) between frames, and running them
336 all at 60 FPS is no good.
338 So, we don't use setAnimationTimeInterval, and just let the framework call
339 us whenever. But, we only invoke the screen hack's "draw frame" method
340 when enough time has expired.
342 This means two extra calls to gettimeofday() per frame. For fast-cycling
343 screen savers, that might actually slow them down. Oh well.
345 #### Also, we do not run the draw callback faster than the system's
346 animationTimeInterval, so if any savers are pickier about timing
347 than that, this may slow them down too much. If that's a problem,
348 then we could call draw_cb in a loop here (with usleep) until the
349 next call would put us past animationTimeInterval... But a better
350 approach would probably be to just change the saver to not do that.
353 gettimeofday (&tv, 0);
354 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
355 if (now < next_frame_time) return;
357 [self prepareContext];
360 // We do this here instead of in setFrameSize so that all the
361 // Xlib drawing takes place under the animation timer.
362 [self resizeContext];
363 NSRect r = [self frame];
364 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
368 // Run any XtAppAddInput callbacks now.
369 // (Note that XtAppAddTimeOut callbacks have already been run by
370 // the Cocoa event loop.)
372 jwxyz_sources_run (display_sources_data (xdpy));
376 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
380 gettimeofday (&tv, 0);
381 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
382 next_frame_time = now + (delay / 1000000.0);
386 - (void)drawRect:(NSRect)rect
388 if (xwindow) // clear to the X window's bg color, not necessarily black.
389 XClearWindow (xdpy, xwindow);
391 [super drawRect:rect]; // early: black.
395 - (void) setFrameSize:(NSSize) newSize
397 [super setFrameSize:newSize];
398 if ([self isAnimating]) {
403 - (void) setFrame:(NSRect) newRect
405 [super setFrame:newRect];
406 if (xwindow) // inform Xlib that the window has changed.
407 jwxyz_window_resized (xdpy, xwindow);
411 +(BOOL) performGammaFade
416 - (BOOL) hasConfigureSheet
421 - (NSWindow *) configureSheet
423 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
424 NSString *file = [NSString stringWithCString:xsft->progclass
425 encoding:NSUTF8StringEncoding];
426 file = [file lowercaseString];
427 NSString *path = [bundle pathForResource:file ofType:@"xml"];
429 NSLog (@"%@.xml does not exist in the application bundle: %@/",
430 file, [bundle resourcePath]);
434 NSWindow *sheet = [[XScreenSaverConfigSheet alloc]
436 options:xsft->options
437 controller:[prefsReader userDefaultsController]];
439 // #### am I expected to retain this, or not? wtf.
440 // I thought not, but if I don't do this, we (sometimes) crash.
447 /* Announce our willingness to accept keyboard input.
449 - (BOOL)acceptsFirstResponder
455 /* Convert an NSEvent into an XEvent, and pass it along.
456 Returns YES if it was handled.
458 - (BOOL) doEvent: (NSEvent *) e
461 if (![self isPreview] || // no event handling if actually screen-saving!
462 ![self isAnimating] ||
467 memset (&xe, 0, sizeof(xe));
471 int flags = [e modifierFlags];
472 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
473 if (flags & NSShiftKeyMask) state |= ShiftMask;
474 if (flags & NSControlKeyMask) state |= ControlMask;
475 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
476 if (flags & NSCommandKeyMask) state |= Mod2Mask;
478 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
481 int y = [self frame].size.height - p.y;
489 xe.xbutton.state = state;
490 if ([e type] == NSScrollWheel)
491 xe.xbutton.button = ([e deltaY] > 0 ? Button4 : Button5);
493 xe.xbutton.button = [e buttonNumber] + 1;
498 xe.xmotion.state = state;
503 NSString *nss = [e characters];
504 const char *s = [nss cStringUsingEncoding:NSUTF8StringEncoding];
505 xe.xkey.keycode = (s && *s ? *s : 0);
506 xe.xkey.state = state;
514 [self prepareContext];
515 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
521 - (void) mouseDown: (NSEvent *) e
523 if (! [self doEvent:e type:ButtonPress])
527 - (void) mouseUp: (NSEvent *) e
529 if (! [self doEvent:e type:ButtonRelease])
533 - (void) otherMouseDown: (NSEvent *) e
535 if (! [self doEvent:e type:ButtonPress])
536 [super otherMouseDown:e];
539 - (void) otherMouseUp: (NSEvent *) e
541 if (! [self doEvent:e type:ButtonRelease])
542 [super otherMouseUp:e];
545 - (void) mouseMoved: (NSEvent *) e
547 if (! [self doEvent:e type:MotionNotify])
548 [super mouseMoved:e];
551 - (void) mouseDragged: (NSEvent *) e
553 if (! [self doEvent:e type:MotionNotify])
554 [super mouseDragged:e];
557 - (void) otherMouseDragged: (NSEvent *) e
559 if (! [self doEvent:e type:MotionNotify])
560 [super otherMouseDragged:e];
563 - (void) scrollWheel: (NSEvent *) e
565 if (! [self doEvent:e type:ButtonPress])
566 [super scrollWheel:e];
569 - (void) keyDown: (NSEvent *) e
571 if (! [self doEvent:e type:KeyPress])
575 - (void) keyUp: (NSEvent *) e
577 if (! [self doEvent:e type:KeyRelease])
584 /* Utility functions...
588 get_prefsReader (Display *dpy)
590 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
592 return [view prefsReader];
597 get_string_resource (Display *dpy, char *name, char *class)
599 return [get_prefsReader(dpy) getStringResource:name];
603 get_boolean_resource (Display *dpy, char *name, char *class)
605 return [get_prefsReader(dpy) getBooleanResource:name];
609 get_integer_resource (Display *dpy, char *name, char *class)
611 return [get_prefsReader(dpy) getIntegerResource:name];
615 get_float_resource (Display *dpy, char *name, char *class)
617 return [get_prefsReader(dpy) getFloatResource:name];