1 /* xscreensaver, Copyright (c) 2006-2009 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);
85 /* Don't free (npath) -- MacOS's putenv() does not copy it. */
89 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
90 // (e.g., "xscreensaver-text") know how to look up resources.
92 - (void) setResourcesEnv:(NSString *) name
94 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
95 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
97 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
98 char *env = (char *) malloc (strlen (s) + 40);
99 strcpy (env, "XSCREENSAVER_CLASSPATH=");
105 /* Don't free (env) -- MacOS's putenv() does not copy it. */
110 add_default_options (const XrmOptionDescRec *opts,
111 const char * const *defs,
112 XrmOptionDescRec **opts_ret,
113 const char ***defs_ret)
115 /* These aren't "real" command-line options (there are no actual command-line
116 options in the Cocoa version); but this is the somewhat kludgey way that
117 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
118 ../hacks/config/*.xml files communicate with the preferences database.
120 static const XrmOptionDescRec default_options [] = {
121 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
122 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
123 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
124 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
125 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
126 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
127 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
128 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
129 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
130 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
131 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
134 static const char *default_defaults [] = {
140 ".grabDesktopImages: yes",
141 ".chooseRandomImages: no",
142 ".imageDirectory: ~/Pictures",
147 for (i = 0; default_options[i].option; i++)
149 for (i = 0; opts[i].option; i++)
152 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
153 calloc (count + 1, sizeof (*opts2));
157 while (default_options[j].option) {
158 opts2[i] = default_options[j];
162 while (opts[j].option) {
173 for (i = 0; default_defaults[i]; i++)
175 for (i = 0; defs[i]; i++)
178 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
182 while (default_defaults[j]) {
183 defs2[i] = default_defaults[j];
196 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
198 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
201 xsft = [self findFunctionTable];
206 xsft->setup_cb (xsft, xsft->setup_arg);
208 /* The plist files for these preferences show up in
209 $HOME/Library/Preferences/ByHost/ in a file named like
210 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
212 NSString *name = [NSString stringWithCString:xsft->progclass
213 encoding:NSUTF8StringEncoding];
214 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
215 [self setResourcesEnv:name];
218 XrmOptionDescRec *opts = 0;
219 const char **defs = 0;
220 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
221 prefsReader = [[PrefsReader alloc]
222 initWithName:name xrmKeys:opts defaults:defs];
224 // free (opts); // bah, we need these! #### leak!
225 xsft->options = opts;
227 progname = progclass = xsft->progclass;
236 NSAssert(![self isAnimating], @"still animating");
237 NSAssert(!xdata, @"xdata not yet freed");
239 jwxyz_free_display (xdpy);
240 [prefsReader release];
244 - (PrefsReader *) prefsReader
250 - (void) startAnimation
252 NSAssert(![self isAnimating], @"already animating");
253 NSAssert(!initted_p && !xdata, @"already initialized");
254 [super startAnimation];
255 /* We can't draw on the window from this method, so we actually do the
256 initialization of the screen saver (xsft->init_cb) in the first call
257 to animateOneFrame() instead.
261 - (void)stopAnimation
263 NSAssert([self isAnimating], @"not animating");
267 [self lockFocus]; // in case something tries to draw from here
268 [self prepareContext];
270 /* I considered just not even calling the free callback at all...
271 But webcollage-cocoa needs it, to kill the inferior webcollage
272 processes (since the screen saver framework never generates a
273 SIGPIPE for them...) Instead, I turned off the free call in
274 xlockmore.c, which is where all of the bogus calls are anyway.
276 xsft->free_cb (xdpy, xwindow, xdata);
279 // setup_p = NO; // #### wait, do we need this?
284 [super stopAnimation];
288 /* Hook for the XScreenSaverGLView subclass
290 - (void) prepareContext
294 /* Hook for the XScreenSaverGLView subclass
296 - (void) resizeContext
302 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
304 fps_compute (fpst, 0);
309 - (void) animateOneFrame
314 xdpy = jwxyz_make_display (self);
315 xwindow = XRootWindow (xdpy, 0);
321 xsft->setup_cb (xsft, xsft->setup_arg);
325 NSAssert(!xdata, @"xdata already initialized");
330 XSetWindowBackground (xdpy, xwindow,
331 get_pixel_resource (xdpy, 0,
332 "background", "Background"));
333 XClearWindow (xdpy, xwindow);
335 [[self window] setAcceptsMouseMovedEvents:YES];
337 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
338 drawing primitives will run on the GPU instead of the CPU.
339 It seems like it might make things worse rather than better,
340 though... Plus it makes us binary-incompatible with 10.4.
342 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
343 [[self window] setPreferredBackingLocation:
344 NSWindowBackingLocationVideoMemory];
348 /* Kludge: even though the init_cb functions are declared to take 2 args,
349 actually call them with 3, for the benefit of xlockmore_init() and
352 void *(*init_cb) (Display *, Window, void *) =
353 (void *(*) (Display *, Window, void *)) xsft->init_cb;
355 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
357 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
358 fpst = fps_init (xdpy, xwindow);
359 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
363 /* I don't understand why we have to do this *every frame*, but we do,
364 or else the cursor comes back on.
366 if (![self isPreview])
367 [NSCursor setHiddenUntilMouseMoves:YES];
372 /* This is just a guess, but the -fps code wants to know how long
373 we were sleeping between frames.
375 unsigned long usecs = 1000000 * [self animationTimeInterval];
376 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
377 fps_slept (fpst, usecs);
381 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
382 This is bad, because some of the screen hacks want to delay for long
383 periods (like 5 seconds or a minute!) between frames, and running them
384 all at 60 FPS is no good.
386 So, we don't use setAnimationTimeInterval, and just let the framework call
387 us whenever. But, we only invoke the screen hack's "draw frame" method
388 when enough time has expired.
390 This means two extra calls to gettimeofday() per frame. For fast-cycling
391 screen savers, that might actually slow them down. Oh well.
393 #### Also, we do not run the draw callback faster than the system's
394 animationTimeInterval, so if any savers are pickier about timing
395 than that, this may slow them down too much. If that's a problem,
396 then we could call draw_cb in a loop here (with usleep) until the
397 next call would put us past animationTimeInterval... But a better
398 approach would probably be to just change the saver to not do that.
401 gettimeofday (&tv, 0);
402 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
403 if (now < next_frame_time) return;
405 [self prepareContext];
408 // We do this here instead of in setFrameSize so that all the
409 // Xlib drawing takes place under the animation timer.
410 [self resizeContext];
411 NSRect r = [self frame];
412 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
416 // Run any XtAppAddInput callbacks now.
417 // (Note that XtAppAddTimeOut callbacks have already been run by
418 // the Cocoa event loop.)
420 jwxyz_sources_run (display_sources_data (xdpy));
424 NSDisableScreenUpdates();
425 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
426 if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
428 NSEnableScreenUpdates();
430 gettimeofday (&tv, 0);
431 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
432 next_frame_time = now + (delay / 1000000.0);
436 - (void)drawRect:(NSRect)rect
438 if (xwindow) // clear to the X window's bg color, not necessarily black.
439 XClearWindow (xdpy, xwindow);
441 [super drawRect:rect]; // early: black.
445 - (void) setFrameSize:(NSSize) newSize
447 [super setFrameSize:newSize];
448 if ([self isAnimating]) {
453 - (void) setFrame:(NSRect) newRect
455 [super setFrame:newRect];
456 if (xwindow) // inform Xlib that the window has changed.
457 jwxyz_window_resized (xdpy, xwindow);
461 +(BOOL) performGammaFade
466 - (BOOL) hasConfigureSheet
471 - (NSWindow *) configureSheet
473 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
474 NSString *file = [NSString stringWithCString:xsft->progclass
475 encoding:NSUTF8StringEncoding];
476 file = [file lowercaseString];
477 NSString *path = [bundle pathForResource:file ofType:@"xml"];
479 NSLog (@"%@.xml does not exist in the application bundle: %@/",
480 file, [bundle resourcePath]);
484 NSWindow *sheet = [[XScreenSaverConfigSheet alloc]
486 options:xsft->options
487 controller:[prefsReader userDefaultsController]];
489 // #### am I expected to retain this, or not? wtf.
490 // I thought not, but if I don't do this, we (sometimes) crash.
497 /* Announce our willingness to accept keyboard input.
499 - (BOOL)acceptsFirstResponder
505 /* Convert an NSEvent into an XEvent, and pass it along.
506 Returns YES if it was handled.
508 - (BOOL) doEvent: (NSEvent *) e
511 if (![self isPreview] || // no event handling if actually screen-saving!
512 ![self isAnimating] ||
517 memset (&xe, 0, sizeof(xe));
521 int flags = [e modifierFlags];
522 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
523 if (flags & NSShiftKeyMask) state |= ShiftMask;
524 if (flags & NSControlKeyMask) state |= ControlMask;
525 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
526 if (flags & NSCommandKeyMask) state |= Mod2Mask;
528 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
531 int y = [self frame].size.height - p.y;
539 xe.xbutton.state = state;
540 if ([e type] == NSScrollWheel)
541 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
542 [e deltaY] < 0 ? Button5 :
543 [e deltaX] > 0 ? Button6 :
544 [e deltaX] < 0 ? Button7 :
547 xe.xbutton.button = [e buttonNumber] + 1;
552 xe.xmotion.state = state;
557 NSString *nss = [e characters];
558 const char *s = [nss cStringUsingEncoding:NSISOLatin1StringEncoding];
559 xe.xkey.keycode = (s && *s ? *s : 0);
560 xe.xkey.state = state;
568 [self prepareContext];
569 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
575 - (void) mouseDown: (NSEvent *) e
577 if (! [self doEvent:e type:ButtonPress])
581 - (void) mouseUp: (NSEvent *) e
583 if (! [self doEvent:e type:ButtonRelease])
587 - (void) otherMouseDown: (NSEvent *) e
589 if (! [self doEvent:e type:ButtonPress])
590 [super otherMouseDown:e];
593 - (void) otherMouseUp: (NSEvent *) e
595 if (! [self doEvent:e type:ButtonRelease])
596 [super otherMouseUp:e];
599 - (void) mouseMoved: (NSEvent *) e
601 if (! [self doEvent:e type:MotionNotify])
602 [super mouseMoved:e];
605 - (void) mouseDragged: (NSEvent *) e
607 if (! [self doEvent:e type:MotionNotify])
608 [super mouseDragged:e];
611 - (void) otherMouseDragged: (NSEvent *) e
613 if (! [self doEvent:e type:MotionNotify])
614 [super otherMouseDragged:e];
617 - (void) scrollWheel: (NSEvent *) e
619 if (! [self doEvent:e type:ButtonPress])
620 [super scrollWheel:e];
623 - (void) keyDown: (NSEvent *) e
625 if (! [self doEvent:e type:KeyPress])
629 - (void) keyUp: (NSEvent *) e
631 if (! [self doEvent:e type:KeyRelease])
638 /* Utility functions...
642 get_prefsReader (Display *dpy)
644 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
646 return [view prefsReader];
651 get_string_resource (Display *dpy, char *name, char *class)
653 return [get_prefsReader(dpy) getStringResource:name];
657 get_boolean_resource (Display *dpy, char *name, char *class)
659 return [get_prefsReader(dpy) getBooleanResource:name];
663 get_integer_resource (Display *dpy, char *name, char *class)
665 return [get_prefsReader(dpy) getIntegerResource:name];
669 get_float_resource (Display *dpy, char *name, char *class)
671 return [get_prefsReader(dpy) getFloatResource:name];