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);
84 // free (npath); // Oops, don't free this! putenv() does not copy it!
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 },
129 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
130 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
133 static const char *default_defaults [] = {
139 ".grabDesktopImages: yes",
140 ".chooseRandomImages: no",
141 ".imageDirectory: ~/Pictures",
146 for (i = 0; default_options[i].option; i++)
148 for (i = 0; opts[i].option; i++)
151 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
152 calloc (count + 1, sizeof (*opts2));
156 while (default_options[j].option) {
157 opts2[i] = default_options[j];
161 while (opts[j].option) {
172 for (i = 0; default_defaults[i]; i++)
174 for (i = 0; defs[i]; i++)
177 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
181 while (default_defaults[j]) {
182 defs2[i] = default_defaults[j];
195 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
197 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
200 xsft = [self findFunctionTable];
205 xsft->setup_cb (xsft, xsft->setup_arg);
207 /* The plist files for these preferences show up in
208 $HOME/Library/Preferences/ByHost/ in a file named like
209 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
211 NSString *name = [NSString stringWithCString:xsft->progclass
212 encoding:NSUTF8StringEncoding];
213 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
214 [self setResourcesEnv:name];
217 XrmOptionDescRec *opts = 0;
218 const char **defs = 0;
219 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
220 prefsReader = [[PrefsReader alloc]
221 initWithName:name xrmKeys:opts defaults:defs];
223 // free (opts); // bah, we need these! #### leak!
224 xsft->options = opts;
226 progname = progclass = xsft->progclass;
235 NSAssert(![self isAnimating], @"still animating");
236 NSAssert(!xdata, @"xdata not yet freed");
238 jwxyz_free_display (xdpy);
239 [prefsReader release];
243 - (PrefsReader *) prefsReader
249 - (void) startAnimation
251 NSAssert(![self isAnimating], @"already animating");
252 NSAssert(!initted_p && !xdata, @"already initialized");
253 [super startAnimation];
254 /* We can't draw on the window from this method, so we actually do the
255 initialization of the screen saver (xsft->init_cb) in the first call
256 to animateOneFrame() instead.
260 - (void)stopAnimation
262 NSAssert([self isAnimating], @"not animating");
266 [self lockFocus]; // in case something tries to draw from here
267 [self prepareContext];
269 /* I considered just not even calling the free callback at all...
270 But webcollage-cocoa needs it, to kill the inferior webcollage
271 processes (since the screen saver framework never generates a
272 SIGPIPE for them...) Instead, I turned off the free call in
273 xlockmore.c, which is where all of the bogus calls are anyway.
275 xsft->free_cb (xdpy, xwindow, xdata);
278 // setup_p = NO; // #### wait, do we need this?
283 [super stopAnimation];
287 /* Hook for the XScreenSaverGLView subclass
289 - (void) prepareContext
293 /* Hook for the XScreenSaverGLView subclass
295 - (void) resizeContext
301 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
303 fps_compute (fpst, 0);
308 - (void) animateOneFrame
313 xdpy = jwxyz_make_display (self);
314 xwindow = XRootWindow (xdpy, 0);
320 xsft->setup_cb (xsft, xsft->setup_arg);
324 NSAssert(!xdata, @"xdata already initialized");
329 XSetWindowBackground (xdpy, xwindow,
330 get_pixel_resource (xdpy, 0,
331 "background", "Background"));
332 XClearWindow (xdpy, xwindow);
334 [[self window] setAcceptsMouseMovedEvents:YES];
336 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
337 drawing primitives will run on the GPU instead of the CPU.
338 It seems like it might make things worse rather than better,
339 though... Plus it makes us binary-incompatible with 10.4.
341 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
342 [[self window] setPreferredBackingLocation:
343 NSWindowBackingLocationVideoMemory];
347 /* Kludge: even though the init_cb functions are declared to take 2 args,
348 actually call them with 3, for the benefit of xlockmore_init() and
351 void *(*init_cb) (Display *, Window, void *) =
352 (void *(*) (Display *, Window, void *)) xsft->init_cb;
354 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
356 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
357 fpst = fps_init (xdpy, xwindow);
358 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
362 /* I don't understand why we have to do this *every frame*, but we do,
363 or else the cursor comes back on.
365 if (![self isPreview])
366 [NSCursor setHiddenUntilMouseMoves:YES];
371 /* This is just a guess, but the -fps code wants to know how long
372 we were sleeping between frames.
374 unsigned long usecs = 1000000 * [self animationTimeInterval];
375 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
376 fps_slept (fpst, usecs);
380 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
381 This is bad, because some of the screen hacks want to delay for long
382 periods (like 5 seconds or a minute!) between frames, and running them
383 all at 60 FPS is no good.
385 So, we don't use setAnimationTimeInterval, and just let the framework call
386 us whenever. But, we only invoke the screen hack's "draw frame" method
387 when enough time has expired.
389 This means two extra calls to gettimeofday() per frame. For fast-cycling
390 screen savers, that might actually slow them down. Oh well.
392 #### Also, we do not run the draw callback faster than the system's
393 animationTimeInterval, so if any savers are pickier about timing
394 than that, this may slow them down too much. If that's a problem,
395 then we could call draw_cb in a loop here (with usleep) until the
396 next call would put us past animationTimeInterval... But a better
397 approach would probably be to just change the saver to not do that.
400 gettimeofday (&tv, 0);
401 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
402 if (now < next_frame_time) return;
404 [self prepareContext];
407 // We do this here instead of in setFrameSize so that all the
408 // Xlib drawing takes place under the animation timer.
409 [self resizeContext];
410 NSRect r = [self frame];
411 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
415 // Run any XtAppAddInput callbacks now.
416 // (Note that XtAppAddTimeOut callbacks have already been run by
417 // the Cocoa event loop.)
419 jwxyz_sources_run (display_sources_data (xdpy));
423 NSDisableScreenUpdates();
424 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
425 if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
427 NSEnableScreenUpdates();
429 gettimeofday (&tv, 0);
430 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
431 next_frame_time = now + (delay / 1000000.0);
435 - (void)drawRect:(NSRect)rect
437 if (xwindow) // clear to the X window's bg color, not necessarily black.
438 XClearWindow (xdpy, xwindow);
440 [super drawRect:rect]; // early: black.
444 - (void) setFrameSize:(NSSize) newSize
446 [super setFrameSize:newSize];
447 if ([self isAnimating]) {
452 - (void) setFrame:(NSRect) newRect
454 [super setFrame:newRect];
455 if (xwindow) // inform Xlib that the window has changed.
456 jwxyz_window_resized (xdpy, xwindow);
460 +(BOOL) performGammaFade
465 - (BOOL) hasConfigureSheet
470 - (NSWindow *) configureSheet
472 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
473 NSString *file = [NSString stringWithCString:xsft->progclass
474 encoding:NSUTF8StringEncoding];
475 file = [file lowercaseString];
476 NSString *path = [bundle pathForResource:file ofType:@"xml"];
478 NSLog (@"%@.xml does not exist in the application bundle: %@/",
479 file, [bundle resourcePath]);
483 NSWindow *sheet = [[XScreenSaverConfigSheet alloc]
485 options:xsft->options
486 controller:[prefsReader userDefaultsController]];
488 // #### am I expected to retain this, or not? wtf.
489 // I thought not, but if I don't do this, we (sometimes) crash.
496 /* Announce our willingness to accept keyboard input.
498 - (BOOL)acceptsFirstResponder
504 /* Convert an NSEvent into an XEvent, and pass it along.
505 Returns YES if it was handled.
507 - (BOOL) doEvent: (NSEvent *) e
510 if (![self isPreview] || // no event handling if actually screen-saving!
511 ![self isAnimating] ||
516 memset (&xe, 0, sizeof(xe));
520 int flags = [e modifierFlags];
521 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
522 if (flags & NSShiftKeyMask) state |= ShiftMask;
523 if (flags & NSControlKeyMask) state |= ControlMask;
524 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
525 if (flags & NSCommandKeyMask) state |= Mod2Mask;
527 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
530 int y = [self frame].size.height - p.y;
538 xe.xbutton.state = state;
539 if ([e type] == NSScrollWheel)
540 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
541 [e deltaY] < 0 ? Button5 :
542 [e deltaX] > 0 ? Button6 :
543 [e deltaX] < 0 ? Button7 :
546 xe.xbutton.button = [e buttonNumber] + 1;
551 xe.xmotion.state = state;
556 NSString *nss = [e characters];
557 const char *s = [nss cStringUsingEncoding:NSISOLatin1StringEncoding];
558 xe.xkey.keycode = (s && *s ? *s : 0);
559 xe.xkey.state = state;
567 [self prepareContext];
568 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
574 - (void) mouseDown: (NSEvent *) e
576 if (! [self doEvent:e type:ButtonPress])
580 - (void) mouseUp: (NSEvent *) e
582 if (! [self doEvent:e type:ButtonRelease])
586 - (void) otherMouseDown: (NSEvent *) e
588 if (! [self doEvent:e type:ButtonPress])
589 [super otherMouseDown:e];
592 - (void) otherMouseUp: (NSEvent *) e
594 if (! [self doEvent:e type:ButtonRelease])
595 [super otherMouseUp:e];
598 - (void) mouseMoved: (NSEvent *) e
600 if (! [self doEvent:e type:MotionNotify])
601 [super mouseMoved:e];
604 - (void) mouseDragged: (NSEvent *) e
606 if (! [self doEvent:e type:MotionNotify])
607 [super mouseDragged:e];
610 - (void) otherMouseDragged: (NSEvent *) e
612 if (! [self doEvent:e type:MotionNotify])
613 [super otherMouseDragged:e];
616 - (void) scrollWheel: (NSEvent *) e
618 if (! [self doEvent:e type:ButtonPress])
619 [super scrollWheel:e];
622 - (void) keyDown: (NSEvent *) e
624 if (! [self doEvent:e type:KeyPress])
628 - (void) keyUp: (NSEvent *) e
630 if (! [self doEvent:e type:KeyRelease])
637 /* Utility functions...
641 get_prefsReader (Display *dpy)
643 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
645 return [view prefsReader];
650 get_string_resource (Display *dpy, char *name, char *class)
652 return [get_prefsReader(dpy) getStringResource:name];
656 get_boolean_resource (Display *dpy, char *name, char *class)
658 return [get_prefsReader(dpy) getBooleanResource:name];
662 get_integer_resource (Display *dpy, char *name, char *class)
664 return [get_prefsReader(dpy) getIntegerResource:name];
668 get_float_resource (Display *dpy, char *name, char *class)
670 return [get_prefsReader(dpy) getFloatResource:name];