1 /* xscreensaver, Copyright (c) 2006, 2007 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 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
318 drawing primitives will run on the GPU instead of the CPU.
319 It seems like it might make things worse rather than better,
320 though... Plus it makes us binary-incompatible with 10.4.
322 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
323 [[self window] setPreferredBackingLocation:
324 NSWindowBackingLocationVideoMemory];
328 /* Kludge: even though the init_cb functions are declared to take 2 args,
329 actually call them with 3, for the benefit of xlockmore_init() and
332 void *(*init_cb) (Display *, Window, void *) =
333 (void *(*) (Display *, Window, void *)) xsft->init_cb;
335 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
338 /* I don't understand why we have to do this *every frame*, but we do,
339 or else the cursor comes back on.
341 if (![self isPreview])
342 [NSCursor setHiddenUntilMouseMoves:YES];
344 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
345 This is bad, because some of the screen hacks want to delay for long
346 periods (like 5 seconds or a minute!) between frames, and running them
347 all at 60 FPS is no good.
349 So, we don't use setAnimationTimeInterval, and just let the framework call
350 us whenever. But, we only invoke the screen hack's "draw frame" method
351 when enough time has expired.
353 This means two extra calls to gettimeofday() per frame. For fast-cycling
354 screen savers, that might actually slow them down. Oh well.
356 #### Also, we do not run the draw callback faster than the system's
357 animationTimeInterval, so if any savers are pickier about timing
358 than that, this may slow them down too much. If that's a problem,
359 then we could call draw_cb in a loop here (with usleep) until the
360 next call would put us past animationTimeInterval... But a better
361 approach would probably be to just change the saver to not do that.
364 gettimeofday (&tv, 0);
365 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
366 if (now < next_frame_time) return;
368 [self prepareContext];
371 // We do this here instead of in setFrameSize so that all the
372 // Xlib drawing takes place under the animation timer.
373 [self resizeContext];
374 NSRect r = [self frame];
375 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
379 // Run any XtAppAddInput callbacks now.
380 // (Note that XtAppAddTimeOut callbacks have already been run by
381 // the Cocoa event loop.)
383 jwxyz_sources_run (display_sources_data (xdpy));
387 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
391 gettimeofday (&tv, 0);
392 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
393 next_frame_time = now + (delay / 1000000.0);
397 - (void)drawRect:(NSRect)rect
399 if (xwindow) // clear to the X window's bg color, not necessarily black.
400 XClearWindow (xdpy, xwindow);
402 [super drawRect:rect]; // early: black.
406 - (void) setFrameSize:(NSSize) newSize
408 [super setFrameSize:newSize];
409 if ([self isAnimating]) {
414 - (void) setFrame:(NSRect) newRect
416 [super setFrame:newRect];
417 if (xwindow) // inform Xlib that the window has changed.
418 jwxyz_window_resized (xdpy, xwindow);
422 +(BOOL) performGammaFade
427 - (BOOL) hasConfigureSheet
432 - (NSWindow *) configureSheet
434 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
435 NSString *file = [NSString stringWithCString:xsft->progclass
436 encoding:NSUTF8StringEncoding];
437 file = [file lowercaseString];
438 NSString *path = [bundle pathForResource:file ofType:@"xml"];
440 NSLog (@"%@.xml does not exist in the application bundle: %@/",
441 file, [bundle resourcePath]);
445 NSWindow *sheet = [[XScreenSaverConfigSheet alloc]
447 options:xsft->options
448 controller:[prefsReader userDefaultsController]];
450 // #### am I expected to retain this, or not? wtf.
451 // I thought not, but if I don't do this, we (sometimes) crash.
458 /* Announce our willingness to accept keyboard input.
460 - (BOOL)acceptsFirstResponder
466 /* Convert an NSEvent into an XEvent, and pass it along.
467 Returns YES if it was handled.
469 - (BOOL) doEvent: (NSEvent *) e
472 if (![self isPreview] || // no event handling if actually screen-saving!
473 ![self isAnimating] ||
478 memset (&xe, 0, sizeof(xe));
482 int flags = [e modifierFlags];
483 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
484 if (flags & NSShiftKeyMask) state |= ShiftMask;
485 if (flags & NSControlKeyMask) state |= ControlMask;
486 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
487 if (flags & NSCommandKeyMask) state |= Mod2Mask;
489 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
492 int y = [self frame].size.height - p.y;
500 xe.xbutton.state = state;
501 if ([e type] == NSScrollWheel)
502 xe.xbutton.button = ([e deltaY] > 0 ? Button4 : Button5);
504 xe.xbutton.button = [e buttonNumber] + 1;
509 xe.xmotion.state = state;
514 NSString *nss = [e characters];
515 const char *s = [nss cStringUsingEncoding:NSUTF8StringEncoding];
516 xe.xkey.keycode = (s && *s ? *s : 0);
517 xe.xkey.state = state;
525 [self prepareContext];
526 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
532 - (void) mouseDown: (NSEvent *) e
534 if (! [self doEvent:e type:ButtonPress])
538 - (void) mouseUp: (NSEvent *) e
540 if (! [self doEvent:e type:ButtonRelease])
544 - (void) otherMouseDown: (NSEvent *) e
546 if (! [self doEvent:e type:ButtonPress])
547 [super otherMouseDown:e];
550 - (void) otherMouseUp: (NSEvent *) e
552 if (! [self doEvent:e type:ButtonRelease])
553 [super otherMouseUp:e];
556 - (void) mouseMoved: (NSEvent *) e
558 if (! [self doEvent:e type:MotionNotify])
559 [super mouseMoved:e];
562 - (void) mouseDragged: (NSEvent *) e
564 if (! [self doEvent:e type:MotionNotify])
565 [super mouseDragged:e];
568 - (void) otherMouseDragged: (NSEvent *) e
570 if (! [self doEvent:e type:MotionNotify])
571 [super otherMouseDragged:e];
574 - (void) scrollWheel: (NSEvent *) e
576 if (! [self doEvent:e type:ButtonPress])
577 [super scrollWheel:e];
580 - (void) keyDown: (NSEvent *) e
582 if (! [self doEvent:e type:KeyPress])
586 - (void) keyUp: (NSEvent *) e
588 if (! [self doEvent:e type:KeyRelease])
595 /* Utility functions...
599 get_prefsReader (Display *dpy)
601 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
603 return [view prefsReader];
608 get_string_resource (Display *dpy, char *name, char *class)
610 return [get_prefsReader(dpy) getStringResource:name];
614 get_boolean_resource (Display *dpy, char *name, char *class)
616 return [get_prefsReader(dpy) getBooleanResource:name];
620 get_integer_resource (Display *dpy, char *name, char *class)
622 return [get_prefsReader(dpy) getIntegerResource:name];
626 get_float_resource (Display *dpy, char *name, char *class)
628 return [get_prefsReader(dpy) getFloatResource:name];