home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1998 / MacHack 1998.toast / The Hacks! / Spotlight Hack / source / filter.c < prev    next >
Encoding:
Text File  |  1998-06-21  |  17.5 KB  |  439 lines  |  [TEXT/CWIE]

  1. // File "filter.c" -
  2.  
  3. #include <A4Stuff.h>
  4. #include "filter.h"
  5. #include "main.h"
  6. #include "layers.h"
  7.  
  8. // ***********************************************************************************
  9. // ***********************************************************************************
  10.  
  11. /*
  12.     The Not-So-Simple FAT jGNEFilter 
  13.         Original Code by Matt Slot (fprefect@umich.edu), 6/2/95 
  14.             with help and criticism (lots!) from Ed Wynne (arwyn@umich.edu).
  15.     
  16.     Quick Intro
  17.         Since jGNEFilters are nasty things in 68k, and pretty much impossible 
  18.         in PPC, writing simple and cross-compiling handler code is also. To 
  19.         facilitate use of this, I have written some interface routines to hide
  20.         some of the complexity from the application programmer.
  21.         
  22.     /    Ptr InstallEventFilter(FilterHelperUPP helperProc, Ptr helperData);
  23.     |        Installs a jGNEFilter which can properly call a callback from within
  24.     |        the application. The callback handles incoming events wither either 
  25.     |        68k or PPC code, and gets passed the data pointer you send from here.
  26.     |        Install() returns a pointer the jGNEFilter (which has been loaded from
  27.     |        a resource) installed in the system heap -- or NIL to indicate an error.
  28.  
  29.     /    Ptr ReleaseEventFilter(Ptr filterProc);
  30.     |        Pass in the pointer to the jGNEFilter, and this routine will disable 
  31.     |        the handling and try to dispose of its storage in the System Heap.
  32.     |        You *must* do this if your helper function is going to disappear when
  33.     |        the application closes. 
  34.     
  35.     /    Code Resource 'jGNE', #128: the jGNEFilter
  36.     |        Since there is no way to get the same routine to compile on both 68K
  37.     |        and PPC, I have compiled the jGNEFilter into an executable resource.
  38.     |        The real code of this resource has been compiled from the additional
  39.     |        source file you can find in the project folder. Note that the code simply  
  40.     |        calls the ProcPtr for the Helper function blindly... whether 68k or PPC. 
  41.     |        See below for more details as to how the filter is loaded, unloaded,
  42.     |        and called.
  43.  
  44.     /    void EventFilterHelper(EventRecord *theEvent, Ptr helperData);
  45.     |        This callback is the workhorse of the event filter. Once installed, this  
  46.     |        routine sees every event that gets harvested and has an opportunity to
  47.     |        modify the record before the front application gets to see it. Ideally
  48.     |        this function can do the necessary work itself or pass off the event info
  49.     |        to the home application.
  50.     |        WARNING: For 68k code, the routine will be called from the current app's
  51.     |        context (A5/Globals, Rsrc File, HeapZone). PPC code will have a valid RTOC
  52.     |        (Globals access) but not Rsrc File, HeapZone, etc.
  53.     
  54.  
  55.     How this all works:
  56.         
  57.         Since I didn't want to write the jGNEFilter in C (OK, I couldn't figure out
  58.         a good way), the code is carried around in a resource. Originally, I had 
  59.         saved the routine as a constant string that was StuffHex()'ed into the System 
  60.         Heap pointer... but I got so many questions and comments I decided to do it 
  61.         the normal way.
  62.         
  63.         The jGNEFilter keeps 3 pieces of data inline: the next filter in the chain,
  64.         a pointer to the helper routine, and some extra data to pass to the helper.
  65.         Most importantly, we may not be able to remove the filter from the calling
  66.         chain...  the architecture just doesn't permit it! If we are able to safely
  67.         pull the filter out, we do. Otherwise the next best solution is to keep a flag,
  68.         that we can clear when we want to disable the functionality -- in fact, we
  69.         set or clear the pointer to the Helper Proc as the flag.
  70.         
  71.         Finally, the helper function is the meat of our jGNEFilter; it does the work
  72.         of the active filter. In the case of a 68K helper, it is accessed via a 
  73.         simple ProcPtr. In the case of a PPC helper, the installer sets up a valid
  74.         RoutineDescriptor (in the System Heap with the jGNEFilter) to invoke a 
  75.         MixedMode switch between the 68K caller (filter) and PPC routine (helper).
  76.         Again, when releasing the filter the handler disposes the descriptor and 
  77.         clear the inline ProcPtr/flag, since the helper function will probably be
  78.         disappearing when the application quits.
  79.         
  80.         If you are picking out events to handle within your app, my suggestion is to
  81.         keep a secondary Queue of events in the System Heap -- remember, you must 
  82.         allocate new EventRecords (since the current event belongs to the calling
  83.         app) into the System Heap (you need a heap that both the current process and
  84.         your own process can access). Given these events, your main event loop can 
  85.         suck out clicks or keydowns for dispatching internally and safely within your
  86.         own context.
  87.         
  88.         Also, Text Service windows don't receive Activate or Update events... you 
  89.         must check for those manually within your own event loop and handle them.
  90.     
  91. */
  92.  
  93. // ***********************************************************************************
  94. // ***********************************************************************************
  95.  
  96. Ptr InstallEventFilter(FilterHelperUPP helperProc, Ptr helperData) {
  97.     Handle filterRsrc;
  98.     Ptr filterProc, data;
  99.     
  100.     // Create a duplicate function in the System Heap (so its *alway* there) and
  101.     //   copy the data across. Note: even though it is technically a function, we
  102.     //   can still treat it as data safely until it has been installed and invoked.
  103.     filterRsrc = Get1Resource(kJGNEFilterResType, kJGNEFilterResID);
  104.     filterProc = NewPtrSys(GetHandleSize(filterRsrc));
  105.     // If either is nil, there is no need to free.. we are quitting anyway
  106.     if (! filterRsrc || ! filterProc) return(0); 
  107.     BlockMove(*filterRsrc, filterProc, GetHandleSize(filterRsrc));
  108.     ReleaseResource(filterRsrc);
  109.     
  110.     // Get and install the current filter as the next filter in the chain.
  111.     data = (Ptr) LMGetGNEFilter();
  112.     BlockMove(&data, filterProc + kNextFilterOffset, sizeof(data));
  113.  
  114.     // Get and install the Helper function to do the real work (and as a flag to
  115.     //   indicate we are in business and accepting events). Remember that if we
  116.     //   generating PPC code, it is necessary to establish a Routine Descriptor.
  117.     SetZone(SystemZone());
  118.     data = (Ptr) NewFilterHelperProc(helperProc);
  119.     if (! data) return(0);
  120.     BlockMove(&data, filterProc + kEventHelperOffset, sizeof(data));
  121.     SetZone(ApplicationZone());
  122.  
  123.     // If the caller wants to pass data to the jGNEFilter Helper function. This
  124.     //   pointer (or handle if desired) *must* be allocated in the System Heap
  125.     //   if you don't plan on releasing the Filter before quitting. If you plan
  126.     //   on releasing the filter, then either the App or Sys heap will suffice.
  127.     data = helperData;
  128.     BlockMove(&helperData, filterProc + kEventHelperDataOffset, sizeof(data));
  129.     
  130.     // Install us, we are ready to do some work!
  131.     LMSetGNEFilter((GNEFilterUPP) filterProc);
  132.     
  133.     return(filterProc);
  134.     }
  135.  
  136. // ***********************************************************************************
  137. // ***********************************************************************************
  138.  
  139. Ptr ReleaseEventFilter(Ptr filterProc) {
  140.     Ptr data;
  141.     
  142.     if (! filterProc) return(0);
  143.  
  144.     // Clear the Helper location as an indicator that we have closed up shop. The
  145.     //   filter itself may lingers in the System Heap until shutdown unless we can
  146.     //   find a way to extract it from the chain (see below). On the other hand, the 
  147.     //   filter has been written so that if the Helper function pointer is NIL, the 
  148.     //   filter will do nothing at all. Let's zero it out for that (hopeful) case.
  149.     BlockMove(filterProc + kEventHelperOffset, &data, sizeof(data));
  150.     if (data) DisposeRoutineDescriptor((UniversalProcPtr) data);
  151.     data = 0;
  152.     BlockMove(&data, filterProc + kEventHelperOffset, sizeof(data));
  153.     
  154.  
  155.     // If the installed filterProc is the first one in the chain, then we should
  156.     //    be able remove it and replace it with the next one (the one we would
  157.     //    normally jump to). If we can dispose the filterProc buffer, then we can
  158.     //    can recover those 50 bytes that remain in the System Heap.
  159.     // Thanks to HoverBar's Guy Fullerton (hedgeboy@realm.net) for the suggestion.
  160.     if (filterProc == (Ptr) LMGetGNEFilter()) {
  161.         // Remove our filterProc from the chain.
  162.         BlockMove(filterProc + kNextFilterOffset, &data, sizeof(data));
  163.         LMSetGNEFilter((GNEFilterUPP) data);
  164.         
  165.         BlockMove(filterProc + kEventHelperDataOffset, &data, sizeof(data));
  166.         DisposePtr(filterProc);
  167.         }
  168.       else {
  169.         // Grab the data that was passed when initialized or as set in the Helper
  170.         //   function. The caller can then deallocate it if desired or necessary.
  171.         
  172.         BlockMove(filterProc + kEventHelperDataOffset, &data, sizeof(data));
  173.         }
  174.     
  175.     return(data);
  176.     }
  177.  
  178. // ***********************************************************************************
  179. // ***********************************************************************************
  180.  
  181. #pragma mark -
  182.  
  183. #define modKeysDown(modifiers) ((modifiers & glob.prefs.activateModifiers) == glob.prefs.activateModifiers)
  184.  
  185. void EventFilterHelper(EventRecord *event, Ptr helperData) {
  186.     // This only does something in 68K code. We now have access to globals,
  187.     //   which PPC get for free from CFM; however, we won't have access to 
  188.     //   our application's Resource file/chain or HeapZone. Be careful!
  189.     long saveA4 = SetA4((long) helperData);
  190.     WindowPeek i;
  191.     Boolean modsDown = modKeysDown(event->modifiers);
  192.  
  193.     /*
  194.     This switch statement eliminates two redraw problems. One is that if we do anything with
  195.     the spotlight on an updateEvt, it is possible to invalidate the updateEvt and so some
  196.     little areas do not get drawn correctly. mouseUp events are also ignored because the 
  197.     event->where point can be quite far from where the mouse currently is (because it's set to
  198.     where the *mouseUp* occurred, and sometimes the event doesn't come through until a bit after
  199.     it happens). So, when mouseUp events are not ignored, sometimes the spotlight can "jump" back
  200.     to where the mouseUp happened, and then back to where the cursor currently is.
  201.     
  202.     In addition, it eliminates a bizarre problem where sometimes when a high level event came
  203.     through, the modifiers field would not correctly represent the true state of the modifiers.
  204.     What kept happening was, whenever I launched MW Debug, the spotlight would immeditely 
  205.     activate. This proved incredibly annoying because it switched to the finder and sent all
  206.     other apps to the background, and it happened sometimes when stepping through code, too. So
  207.     you think you're debugging your program, then poof, you're in the finder. :) Anyway, I have
  208.     no idea why the modifiers field should be invalid -- IM:Toolbox Essentials mentions nothing
  209.     about this -- so perhaps it's something in IM:IAC, something undocumented, a bug in MW 
  210.     Debug...whatever the reason, this fixes it.
  211.     */
  212.     
  213.     switch (event->what) {
  214.         case updateEvt:
  215.         case mouseUp:
  216.         case kHighLevelEvent:
  217.             goto end;
  218.             break;
  219.     }
  220.     
  221.     if (glob.active) {
  222.         if (modsDown) ShowSpotlight(event->where, false);
  223.         else {
  224.             for (i = (WindowPeek)LMGetWindowList(); i != nil; i = i->nextWindow) {
  225.                 if (i->visible) RestoreWindow(i);
  226.             }
  227.             CalcVisBehind(LMGetWindowList(), glob.spotlightRgn);
  228.             PaintBehind(LMGetWindowList(), glob.spotlightRgn);
  229.  
  230.             glob.active = false;
  231.             MoveSpotlightOffscreen();
  232.         }
  233.     }
  234.     else if (glob.activating) {
  235.         ProcessSerialNumber psn;
  236.         Boolean inTheFinder;
  237.         
  238.         GetCurrentProcess(&psn);
  239.         SameProcess(&psn, &glob.finderPSN, &inTheFinder);
  240.         if (inTheFinder) {
  241.             glob.activating = false;
  242.             StartSpotlight(event->where);
  243.         }
  244.     }
  245.     else if (modsDown) {    
  246.         // don't show the spotlight if there is a dialog open
  247.         WindowPtr window = FrontWindow();
  248.         if (window != nil && window == glob.lastModalDialog) goto end;
  249.     
  250.         if (!(glob.activating = HideAllButFinder())) StartSpotlight(event->where);
  251.     }
  252.  
  253. end:
  254.     
  255.     if (glob.active) {
  256.         event->modifiers &= ~glob.prefs.disableModifiers;
  257.     }
  258.  
  259.     // Restore the (68K) context before leaving
  260.     SetA4(saveA4);
  261. }
  262.  
  263. void StartSpotlight(Point where) {
  264.     glob.active = true;
  265.     ShowSpotlight(where, false);
  266. }
  267.  
  268. /*
  269. ShowSpotlight displays the spotlight centered on a point passed to it. It determines which
  270. windows need modifying and updating.
  271.  
  272. Why, you ask, do I loop through the entire window list *twice*? Well, it has to do with how the
  273. WDEF calculates where to draw the close box, grow box, windowshade box, etc. In the past, these
  274. two loops were combined. The result was that by the end of the loop, some windows had the
  275. spotlight region cut out of them. Then, PaintBehind() was called to redraw areas which had been
  276. vacated by the spotlight. If a titlebar was involved, the WDEF would be called to draw it. The
  277. WDEF draws the close box relative to the leftmost part of the window, and the grow box relative
  278. to the rightmost part of the window. If the spotlight completely cut off one side of the titlebar 
  279. -- that is, if it did not divide the titlebar into two smaller pieces -- then what the WDEF saw 
  280. as the "leftmost" or "rightmost" part of the window would not be the right place to draw the
  281. close box and grow box, and so, in summary, it would look messed up. :-) The way I have it now,
  282. when PaintBehind() is called, all windows are in their "normal" state, not their "spotlighted"
  283. state, so everything works correctly.
  284. */
  285.  
  286. /*
  287. void ShowSpotlight(Point where, Boolean override) {
  288.     if (where.h != glob.lightCenter.h || where.v != glob.lightCenter.v) {
  289.         WindowPeek i;
  290.         Boolean anyWindows = false;
  291.         Boolean anyOldWindows = false;
  292.  
  293.         CopyRgn(glob.spotlightRgn, glob.oldlightRgn);
  294.         OffsetRgn(glob.spotlightRgn, where.h - glob.lightCenter.h, where.v - glob.lightCenter.v);        
  295.         DiffRgn(glob.spotlightRgn, glob.oldlightRgn, glob.newlightRgn);
  296.         DiffRgn(glob.oldlightRgn, glob.spotlightRgn, glob.oldlightRgn);
  297.  
  298.         for (i = (WindowPeek)LMGetWindowList(); i != nil; i = i->nextWindow) {
  299.             if (i->visible) {
  300.                 RestoreWindow(i);
  301.                 
  302.                 SectRgn(i->strucRgn, glob.oldlightRgn, glob.scratchRgn);
  303.                 if (!EmptyRgn(glob.scratchRgn)) anyOldWindows = true;
  304.             }
  305.         }
  306.         
  307.         if (anyOldWindows) {
  308.             CalcVisBehind(LMGetWindowList(), glob.oldlightRgn);
  309.             PaintBehind(LMGetWindowList(), glob.oldlightRgn);
  310.         }
  311.  
  312.         for (i = (WindowPeek)LMGetWindowList(); i != nil; i = i->nextWindow) {
  313.             if (i->visible) {
  314.                 SectRgn(i->strucRgn, glob.newlightRgn, glob.scratchRgn);
  315.                 if (!EmptyRgn(glob.scratchRgn)) {
  316.                     DiffRgn(i->strucRgn, glob.spotlightRgn, i->strucRgn);
  317.                     DiffRgn(i->contRgn, glob.spotlightRgn, i->contRgn);
  318.                     anyWindows = true;
  319.                 }
  320.             }
  321.         }
  322.         
  323.         if (anyWindows) {
  324.             CalcVisBehind(LMGetWindowList(), glob.newlightRgn);
  325.             PaintOne(nil, glob.newlightRgn);
  326.         }
  327.         else if (override) PaintOne(nil, glob.newlightRgn);
  328.         
  329.         glob.lightCenter = where;
  330.     }
  331. }
  332. */
  333.  
  334. void ShowSpotlight(Point where, Boolean override) {
  335.     LayerPtr layer;
  336.     Boolean anyWindowsAtAll = false;
  337.     
  338.     if (where.h == glob.lightCenter.h && where.v == glob.lightCenter.v) return;
  339.     
  340.     CopyRgn(glob.spotlightRgn, glob.oldlightRgn);
  341.     OffsetRgn(glob.spotlightRgn, where.h - glob.lightCenter.h, where.v - glob.lightCenter.v);        
  342.     DiffRgn(glob.spotlightRgn, glob.oldlightRgn, glob.newlightRgn);
  343.     DiffRgn(glob.oldlightRgn, glob.spotlightRgn, glob.oldlightRgn);    
  344.     
  345.     for (layer = CurrentLayer(); layer != nil; layer = GetNextLayer(layer)) {
  346.         WindowPeek i;
  347.         WindowPtr windowList = ((LayerPeek)layer)->windowList;
  348.         Boolean anyWindows = false;
  349.         Boolean anyOldWindows = false;
  350.     
  351.         if (!LayerVisible(layer)) continue;
  352.     
  353.         for (i = (WindowPeek)windowList; i != nil; i = i->nextWindow) {
  354.             if (!i->visible) continue;
  355.  
  356.             RestoreWindow(i);
  357.             
  358.             SectRgn(i->strucRgn, glob.oldlightRgn, glob.scratchRgn);
  359.             if (!EmptyRgn(glob.scratchRgn)) anyOldWindows = true;
  360.         }
  361.         
  362.         if (anyOldWindows) {
  363.             CalcVisBehind(windowList, glob.oldlightRgn);
  364.             PaintBehind(windowList, glob.oldlightRgn);
  365.         }
  366.  
  367.         for (i = (WindowPeek)windowList; i != nil; i = i->nextWindow) {
  368.             if (!i->visible) continue;
  369.  
  370.             SectRgn(i->strucRgn, glob.newlightRgn, glob.scratchRgn);
  371.             if (!EmptyRgn(glob.scratchRgn)) {
  372.                 DiffRgn(i->strucRgn, glob.spotlightRgn, i->strucRgn);
  373.                 DiffRgn(i->contRgn, glob.spotlightRgn, i->contRgn);
  374.                 anyWindows = anyWindowsAtAll = true;
  375.             }
  376.         }
  377.         
  378.         if (anyWindows) {
  379.             CalcVisBehind(windowList, glob.newlightRgn);
  380.             PaintOne(nil, glob.newlightRgn);
  381.         }
  382.         
  383.     }
  384.     
  385.     //if (anyWindowsAtAll) PaintOne(nil, glob.newlightRgn);
  386.     //else if (override) PaintOne(nil, glob.newlightRgn);
  387.  
  388.     glob.lightCenter = where;
  389. }
  390.  
  391. /*
  392. RestoreWindow tells to WDEF to recalculate the strucRgn and contRgn and so eliminates the hole 
  393. created by the spotlight. Do not try to simplify it -- it *will* crash unless windowDefProc is
  394. locked and window is the current port.
  395.  
  396. I used to restore the previous port, but this slowed things down by a fair amount. So, then I
  397. tried getting the port, restoring all the windows, and *then* restoring the port -- only doing
  398. it once, instead of once for every window. This ended up messing things up, so by restoring
  399. the windows the "right" port must have changed. For whatever reason, it seems to work better
  400. if I *don't* restore the port.
  401. */
  402.  
  403. void RestoreWindow(WindowPeek window) {
  404.     char state = HGetState(window->windowDefProc);
  405.     HLock(window->windowDefProc);
  406.  
  407.     SetPort((WindowPtr)window);
  408.     CallWindowDefProc((WindowDefUPP)*window->windowDefProc, GetWVariant((WindowPtr)window), 
  409.         (WindowPtr)window, kWindowMsgCalculateShape, nil);
  410.  
  411.     HSetState(window->windowDefProc, state);
  412. }
  413.  
  414. Boolean HideAllButFinder(void) {
  415.     ProcessSerialNumber psn;
  416.     LayerPtr layer;
  417.     Boolean activating = false, currentProcess;
  418.     
  419.     GetCurrentProcess(&psn);
  420.     
  421.     for (layer = CurrentLayer(); layer != nil; layer = GetNextLayer(layer)) {
  422.         LayerInfoPtr info = GetLayerInfo(layer);
  423.         if (info->creator == 'MACS' && glob.prefs.switchToFinder) {
  424.             glob.finderPSN = info->layerPSN;
  425.             SetFrontProcess(&glob.finderPSN);
  426.             SameProcess(&glob.finderPSN, &psn, &activating);
  427.             activating = !activating;
  428.         }
  429.         else if (glob.prefs.hideApps) {
  430.             SameProcess(&info->layerPSN, &psn, ¤tProcess);
  431.             if (!glob.prefs.switchToFinder && currentProcess) continue;
  432.         
  433.             HideLayer(layer);
  434.             currentProcess = false;
  435.         }
  436.     }
  437.     
  438.     return activating;
  439. }