/* xscreensaver, Copyright (c) 2006, 2007 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ /* This is a subclass of Apple's ScreenSaverView that knows how to run xscreensaver programs without X11 via the dark magic of the "jwxyz" library. In xscreensaver terminology, this is the replacement for the "screenhack.c" module. */ #import "XScreenSaverView.h" #import "XScreenSaverConfigSheet.h" #import "screenhackI.h" #import "xlockmoreI.h" #import "jwxyz-timers.h" extern struct xscreensaver_function_table *xscreensaver_function_table; /* Global variables used by the screen savers */ const char *progname; const char *progclass; int mono_p = 0; @implementation XScreenSaverView - (struct xscreensaver_function_table *) findFunctionTable { NSBundle *nsb = [NSBundle bundleForClass:[self class]]; NSAssert1 (nsb, @"no bundle for class %@", [self class]); NSString *path = [nsb bundlePath]; NSString *name = [[[path lastPathComponent] stringByDeletingPathExtension] lowercaseString]; NSString *suffix = @"_xscreensaver_function_table"; NSString *table_name = [name stringByAppendingString:suffix]; CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef) path, kCFURLPOSIXPathStyle, true); CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url); CFRelease (url); NSAssert1 (cfb, @"no CFBundle for \"%@\"", path); void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name); NSAssert2 (addr, @"no symbol \"%@\" in bundle %@", table_name, path); // NSLog (@"%@ = 0x%08X", table_name, (unsigned long) addr); return (struct xscreensaver_function_table *) addr; } // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle // to $PATH for the benefit of savers that include helper shell scripts. // - (void) setShellPath { NSBundle *nsb = [NSBundle bundleForClass:[self class]]; NSAssert1 (nsb, @"no bundle for class %@", [self class]); NSString *nsdir = [nsb resourcePath]; NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]); const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding]; const char *opath = getenv ("PATH"); if (!opath) opath = "/bin"; // $PATH is unset when running under Shark! char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30); strcpy (npath, "PATH="); strcat (npath, dir); strcat (npath, ":"); strcat (npath, opath); if (putenv (npath)) { perror ("putenv"); abort(); } free (npath); } // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts // (e.g., "xscreensaver-text") know how to look up resources. // - (void) setResourcesEnv:(NSString *) name { NSBundle *nsb = [NSBundle bundleForClass:[self class]]; NSAssert1 (nsb, @"no bundle for class %@", [self class]); const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding]; char *env = (char *) malloc (strlen (s) + 40); strcpy (env, "XSCREENSAVER_CLASSPATH="); strcat (env, s); if (putenv (env)) { perror ("putenv"); abort(); } free (env); } static void add_default_options (const XrmOptionDescRec *opts, const char * const *defs, XrmOptionDescRec **opts_ret, const char ***defs_ret) { /* These aren't "real" command-line options (there are no actual command-line options in the Cocoa version); but this is the somewhat kludgey way that the and tags in the ../hacks/config/*.xml files communicate with the preferences database. */ static const XrmOptionDescRec default_options [] = { { "-text-mode", ".textMode", XrmoptionSepArg, 0 }, { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 }, { "-text-file", ".textFile", XrmoptionSepArg, 0 }, { "-text-url", ".textURL", XrmoptionSepArg, 0 }, { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" }, { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"}, { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" }, { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"}, { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 }, { 0, 0, 0, 0 } }; static const char *default_defaults [] = { ".textMode: date", // ".textLiteral: ", // ".textFile: ", // ".textURL: ", ".grabDesktopImages: yes", ".chooseRandomImages: no", ".imageDirectory: ~/Pictures", 0 }; int count = 0, i, j; for (i = 0; default_options[i].option; i++) count++; for (i = 0; opts[i].option; i++) count++; XrmOptionDescRec *opts2 = (XrmOptionDescRec *) calloc (count + 1, sizeof (*opts2)); i = 0; j = 0; while (default_options[j].option) { opts2[i] = default_options[j]; i++, j++; } j = 0; while (opts[j].option) { opts2[i] = opts[j]; i++, j++; } *opts_ret = opts2; /* now the defaults */ count = 0; for (i = 0; default_defaults[i]; i++) count++; for (i = 0; defs[i]; i++) count++; const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2)); i = 0; j = 0; while (default_defaults[j]) { defs2[i] = default_defaults[j]; i++, j++; } j = 0; while (defs[j]) { defs2[i] = defs[j]; i++, j++; } *defs_ret = defs2; } - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview { if (! (self = [super initWithFrame:frame isPreview:isPreview])) return 0; xsft = [self findFunctionTable]; [self setShellPath]; setup_p = YES; if (xsft->setup_cb) xsft->setup_cb (xsft, xsft->setup_arg); /* The plist files for these preferences show up in $HOME/Library/Preferences/ByHost/ in a file named like "org.jwz.xscreensaver...plist" */ NSString *name = [NSString stringWithCString:xsft->progclass encoding:NSUTF8StringEncoding]; name = [@"org.jwz.xscreensaver." stringByAppendingString:name]; [self setResourcesEnv:name]; XrmOptionDescRec *opts = 0; const char **defs = 0; add_default_options (xsft->options, xsft->defaults, &opts, &defs); prefsReader = [[PrefsReader alloc] initWithName:name xrmKeys:opts defaults:defs]; free (defs); // free (opts); // bah, we need these! #### leak! xsft->options = opts; progname = progclass = xsft->progclass; next_frame_time = 0; return self; } - (void) dealloc { NSAssert(![self isAnimating], @"still animating"); NSAssert(!xdata, @"xdata not yet freed"); if (xdpy) jwxyz_free_display (xdpy); [prefsReader release]; [super dealloc]; } - (PrefsReader *) prefsReader { return prefsReader; } - (void) startAnimation { NSAssert(![self isAnimating], @"already animating"); NSAssert(!initted_p && !xdata, @"already initialized"); [super startAnimation]; /* We can't draw on the window from this method, so we actually do the initialization of the screen saver (xsft->init_cb) in the first call to animateOneFrame() instead. */ } - (void)stopAnimation { NSAssert([self isAnimating], @"not animating"); if (initted_p) { [self lockFocus]; // in case something tries to draw from here [self prepareContext]; xsft->free_cb (xdpy, xwindow, xdata); [self unlockFocus]; // setup_p = NO; // #### wait, do we need this? initted_p = NO; xdata = 0; } [super stopAnimation]; } /* Hook for the XScreenSaverGLView subclass */ - (void) prepareContext { } /* Hook for the XScreenSaverGLView subclass */ - (void) resizeContext { } - (void) animateOneFrame { if (!initted_p) { if (! xdpy) { xdpy = jwxyz_make_display (self); xwindow = XRootWindow (xdpy, 0); } if (!setup_p) { setup_p = YES; if (xsft->setup_cb) xsft->setup_cb (xsft, xsft->setup_arg); } initted_p = YES; resized_p = NO; NSAssert(!xdata, @"xdata already initialized"); # undef ya_rand_init ya_rand_init (0); XSetWindowBackground (xdpy, xwindow, get_pixel_resource (xdpy, 0, "background", "Background")); XClearWindow (xdpy, xwindow); [[self window] setAcceptsMouseMovedEvents:YES]; /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz drawing primitives will run on the GPU instead of the CPU. It seems like it might make things worse rather than better, though... Plus it makes us binary-incompatible with 10.4. # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 [[self window] setPreferredBackingLocation: NSWindowBackingLocationVideoMemory]; # endif */ /* Kludge: even though the init_cb functions are declared to take 2 args, actually call them with 3, for the benefit of xlockmore_init() and xlockmore_setup(). */ void *(*init_cb) (Display *, Window, void *) = (void *(*) (Display *, Window, void *)) xsft->init_cb; xdata = init_cb (xdpy, xwindow, xsft->setup_arg); } /* I don't understand why we have to do this *every frame*, but we do, or else the cursor comes back on. */ if (![self isPreview]) [NSCursor setHiddenUntilMouseMoves:YES]; /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing. This is bad, because some of the screen hacks want to delay for long periods (like 5 seconds or a minute!) between frames, and running them all at 60 FPS is no good. So, we don't use setAnimationTimeInterval, and just let the framework call us whenever. But, we only invoke the screen hack's "draw frame" method when enough time has expired. This means two extra calls to gettimeofday() per frame. For fast-cycling screen savers, that might actually slow them down. Oh well. #### Also, we do not run the draw callback faster than the system's animationTimeInterval, so if any savers are pickier about timing than that, this may slow them down too much. If that's a problem, then we could call draw_cb in a loop here (with usleep) until the next call would put us past animationTimeInterval... But a better approach would probably be to just change the saver to not do that. */ struct timeval tv; gettimeofday (&tv, 0); double now = tv.tv_sec + (tv.tv_usec / 1000000.0); if (now < next_frame_time) return; [self prepareContext]; if (resized_p) { // We do this here instead of in setFrameSize so that all the // Xlib drawing takes place under the animation timer. [self resizeContext]; NSRect r = [self frame]; xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height); resized_p = NO; } // Run any XtAppAddInput callbacks now. // (Note that XtAppAddTimeOut callbacks have already been run by // the Cocoa event loop.) // jwxyz_sources_run (display_sources_data (xdpy)); // And finally: // unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata); XSync (xdpy, 0); gettimeofday (&tv, 0); now = tv.tv_sec + (tv.tv_usec / 1000000.0); next_frame_time = now + (delay / 1000000.0); } - (void)drawRect:(NSRect)rect { if (xwindow) // clear to the X window's bg color, not necessarily black. XClearWindow (xdpy, xwindow); else [super drawRect:rect]; // early: black. } - (void) setFrameSize:(NSSize) newSize { [super setFrameSize:newSize]; if ([self isAnimating]) { resized_p = YES; } } - (void) setFrame:(NSRect) newRect { [super setFrame:newRect]; if (xwindow) // inform Xlib that the window has changed. jwxyz_window_resized (xdpy, xwindow); } +(BOOL) performGammaFade { return YES; } - (BOOL) hasConfigureSheet { return YES; } - (NSWindow *) configureSheet { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSString *file = [NSString stringWithCString:xsft->progclass encoding:NSUTF8StringEncoding]; file = [file lowercaseString]; NSString *path = [bundle pathForResource:file ofType:@"xml"]; if (!path) { NSLog (@"%@.xml does not exist in the application bundle: %@/", file, [bundle resourcePath]); return nil; } NSWindow *sheet = [[XScreenSaverConfigSheet alloc] initWithXMLFile:path options:xsft->options controller:[prefsReader userDefaultsController]]; // #### am I expected to retain this, or not? wtf. // I thought not, but if I don't do this, we (sometimes) crash. [sheet retain]; return sheet; } /* Announce our willingness to accept keyboard input. */ - (BOOL)acceptsFirstResponder { return YES; } /* Convert an NSEvent into an XEvent, and pass it along. Returns YES if it was handled. */ - (BOOL) doEvent: (NSEvent *) e type: (int) type { if (![self isPreview] || // no event handling if actually screen-saving! ![self isAnimating] || !initted_p) return NO; XEvent xe; memset (&xe, 0, sizeof(xe)); int state = 0; int flags = [e modifierFlags]; if (flags & NSAlphaShiftKeyMask) state |= LockMask; if (flags & NSShiftKeyMask) state |= ShiftMask; if (flags & NSControlKeyMask) state |= ControlMask; if (flags & NSAlternateKeyMask) state |= Mod1Mask; if (flags & NSCommandKeyMask) state |= Mod2Mask; NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow] toView:self]; int x = p.x; int y = [self frame].size.height - p.y; xe.xany.type = type; switch (type) { case ButtonPress: case ButtonRelease: xe.xbutton.x = x; xe.xbutton.y = y; xe.xbutton.state = state; if ([e type] == NSScrollWheel) xe.xbutton.button = ([e deltaY] > 0 ? Button4 : [e deltaY] < 0 ? Button5 : [e deltaX] > 0 ? Button6 : [e deltaX] < 0 ? Button7 : 0); else xe.xbutton.button = [e buttonNumber] + 1; break; case MotionNotify: xe.xmotion.x = x; xe.xmotion.y = y; xe.xmotion.state = state; break; case KeyPress: case KeyRelease: { NSString *nss = [e characters]; const char *s = [nss cStringUsingEncoding:NSUTF8StringEncoding]; xe.xkey.keycode = (s && *s ? *s : 0); xe.xkey.state = state; break; } default: abort(); } [self lockFocus]; [self prepareContext]; BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe); [self unlockFocus]; return result; } - (void) mouseDown: (NSEvent *) e { if (! [self doEvent:e type:ButtonPress]) [super mouseDown:e]; } - (void) mouseUp: (NSEvent *) e { if (! [self doEvent:e type:ButtonRelease]) [super mouseUp:e]; } - (void) otherMouseDown: (NSEvent *) e { if (! [self doEvent:e type:ButtonPress]) [super otherMouseDown:e]; } - (void) otherMouseUp: (NSEvent *) e { if (! [self doEvent:e type:ButtonRelease]) [super otherMouseUp:e]; } - (void) mouseMoved: (NSEvent *) e { if (! [self doEvent:e type:MotionNotify]) [super mouseMoved:e]; } - (void) mouseDragged: (NSEvent *) e { if (! [self doEvent:e type:MotionNotify]) [super mouseDragged:e]; } - (void) otherMouseDragged: (NSEvent *) e { if (! [self doEvent:e type:MotionNotify]) [super otherMouseDragged:e]; } - (void) scrollWheel: (NSEvent *) e { if (! [self doEvent:e type:ButtonPress]) [super scrollWheel:e]; } - (void) keyDown: (NSEvent *) e { if (! [self doEvent:e type:KeyPress]) [super keyDown:e]; } - (void) keyUp: (NSEvent *) e { if (! [self doEvent:e type:KeyRelease]) [super keyUp:e]; } @end /* Utility functions... */ static PrefsReader * get_prefsReader (Display *dpy) { XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0)); if (!view) abort(); return [view prefsReader]; } char * get_string_resource (Display *dpy, char *name, char *class) { return [get_prefsReader(dpy) getStringResource:name]; } Bool get_boolean_resource (Display *dpy, char *name, char *class) { return [get_prefsReader(dpy) getBooleanResource:name]; } int get_integer_resource (Display *dpy, char *name, char *class) { return [get_prefsReader(dpy) getIntegerResource:name]; } double get_float_resource (Display *dpy, char *name, char *class) { return [get_prefsReader(dpy) getFloatResource:name]; }