/* File: DeskMgrPatches.c Contains: Routines that patch the Desk Manager Written by: Erich Ringewald Copyright: © 1986-1991, 1993 by Apple Computer, Inc., all rights reserved. Change History (most recent first): 7/21/93 joe Backed out . <19> 11/17/91 YK Modify code for the Text Services Manager in c_systemevent to support apps which set SEvtEnb flag off. <18> 11/1/91 DTY Use the system override map in FILE_NOT_IN_SYSTEM_MAP instead of SYSMAPHANDLE. <17> 10/15/91 YK Added code for the Text Services Manager in c_systemevent. <16> 3/7/91 DFH stb, #84365: Fixed CoreLaunchDeskAccessory to deal with file spec ptr == nil and driver name ptr == nil. Was calling OpenDeskAcc ROM with nil name pointer. <15> 3/7/91 DFH stb, #84195: Tuned sleep limiters to have a floor driver delay. Greedy drivers were zeroing out the sleep period, causing the front application to hog the CPU from the other applications. <14> 3/5/91 DFH dnf, WS#DFH-910305a: Fixed LaunchDeskAccessory so that it can be called when the DA itself is the current process. <13> 1/28/91 DFH JSM,#81425: Removed inhibit_DAs mechanism that used to prevent SystemEvent and SystemTask call-throughs while in MenuSelect. There was no need for it, and it caused problems for drivers during prolonged SystemMenu (e.g. Help menu's ModalDialog). <12> 1/21/91 DFH (KSM) Process Menu is now called Application Menu. <10> 1/8/91 DFH (stb) Fixed LimitFrontSleepForDriversSake and LimitBackSleepForDriversSake to work correctly when dCtlDelay is 0. <9> 1/8/91 DFH (ewa) Fixed CoreLaunchDeskAccessory() bug where retval was not being set. <8> 12/18/90 DFH Changed SystemEvent patch to give high-level debugger key even if that debugger is pCurrentProcess. <7> 12/17/90 DFH Rename CheckForCutCopy to CheckForCutCopyMenuKey. <6> 12/14/90 DFH Added SynchIdleTime call in c_systemtask. <5> 12/10/90 DFH Compiler bug workaround in c_sysedit. Use SCRAPCOUNT += 1 instead of SCRAPCOUNT++. <4> 12/5/90 DFH Driver scheduling now affects application sleep time, not vice versa. <3> 11/30/90 DFH Fixed PEntryFromFileSpec call in CoreLaunchDeskAccessory to pass pDASpec->daFileSpec instead of &fileSpec. This bug prevented LaunchDeskAccessory from bringing forward an already open DA. <0> 9/2/86 ELR New Today. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Glue.h" #include "Lomem.h" #include "Data.h" #include "SysMisc.h" #include "Patches.h" #include "Puppet.h" #include "OSDispatchPrivate.h" /* Some function prototypes that should be in (yet another) header file */ Boolean OptionKeyIsDown(void); Handle GetMapHdlFromRefNum(short, Handle); pascal OSErr CoreLaunchDeskAccessory(DASpecBlock *, short *, Boolean); AppleItemHdl StringToAppleItemHdl(StringPtr); void SendAppleMenuSelectMsg(AppleItemHdl); PEntryPtr PEntryFromApplicationMenuItem(unsigned short); void AdjustApplicationMenu(PEntryPtr); void GiveDriversTime(void); OSErr GetDrvrResourceAndInfo(StringPtr, Handle *, short *, Str255 *); Boolean imLayerKeyDown(EventRecord *, Boolean); /* Function prototypes internal to this file */ void GiveLocalDriversTime(void); /* The macro below is used because Suitcase sticks maps below the system */ #define FILE_NOT_IN_SYSTEM_MAP(refNum) (GetMapHdlFromRefNum(refNum, GetOverrideMap(SYSMAPHANDLE)) == nil) /* Used as floor value when adjusting application sleep time based on driver needs. * Smaller values cause the application to sleep less, which robs time from the * other applications. Larger values may impinge on real driver requirements. * NOTE: The initial value of this (7 Mar 91) is just an educated guess. */ #define MIN_DRIVER_DELAY (5) /* GetDrvrResourceAndInfo. Utility to get information about DA in current chain. * Assumes a5 == PROCESSMGRGLOBALS */ OSErr GetDrvrResourceAndInfo(StringPtr pDAName, Handle *phRsrc, short *theID, Str255 *theName) { register Handle drvrHdl; /* Locate the DRVR resource */ drvrHdl = (pDAName != nil) ? CALL_FNC_PTR(Getnamedresource_ptr,patchtraps[GETNAMEDRESOURCE].oldtrap,('DRVR', pDAName)) : Get1IndResource('DRVR', 1); /* Determine the file and resource refnums */ if (drvrHdl != nil) { ROMMAPINSERT = (-1); GetResInfo(drvrHdl, theID, nil, theName); } *phRsrc = drvrHdl; return(RESERR); } /* CheckUnitTableEntry. See if the UNITTABLE entry that this driver would occupy * is acceptable. For now, just checks whether entry is already in use. */ OSErr CheckUnitTableEntry(short resourceID) { register DCtlPtr dCtlPtr; DCtlHandle dCtlHdl; OSErr result; result = noErr; dCtlHdl = (DCtlHandle) UNITTABLE[resourceID]; if ( (dCtlHdl != nil) && ((dCtlPtr = *dCtlHdl) != nil) && ((dCtlPtr->dCtlFlags & DOpened) != 0) ) result = portInUse; return(result); } pascal OSErr CoreLaunchDeskAccessory(DASpecBlock *daSpecAddr, short *pDrvrNum, Boolean fromOpenDeskAcc) { register DASpecBlock *pDASpec; register StringPtr pDAName; register OSErr retval; short daFile; short daResourceNum; Handle drvrHdl; Boolean alreadyLaunched; unsigned long olda5; FSSpec fileSpec; FCBPBRec fcbInfo; LaunchResults dahLaunchResults; Str255 rsrcName; pascal void (*oldtrap)(); #define daOwnerPtr (dahLaunchResults.pNewProcess) retval = noErr; olda5 = ProcessMgrA5Setup(); daOwnerPtr = nil; pDASpec = daSpecAddr; pDAName = pDASpec->daDrvrName; daResourceNum = 0; /* daFileSpec == nil means that the resource MUST be in the existing resource chain. * Locate the resource to determine the full file specification. Allow user to * open the DA in the current layer if the option key is down. */ if (pDASpec->daFileSpec == nil) { /* Locate the DRVR resource */ if ((retval = GetDrvrResourceAndInfo(pDAName, &drvrHdl, &daResourceNum, &rsrcName)) != noErr) goto ErrorRecovery; /* Get file spec of resource file */ if ((daFile = HomeResFile(drvrHdl)) == 0) daFile = SYSMAP; fcbInfo.ioFCBIndx = 0; fcbInfo.ioRefNum = daFile; fcbInfo.ioNamePtr = &fileSpec.name; fcbInfo.ioCompletion = nil; if ((retval = PBGetFCBInfo(&fcbInfo, SyncHFS)) != noErr) goto ErrorRecovery; fileSpec.vRefNum = fcbInfo.ioFCBVRefNum; fileSpec.parID = fcbInfo.ioFCBParID; pDASpec->daFileSpec = &fileSpec; /* See whether current process should get it */ if ( (FILE_NOT_IN_SYSTEM_MAP(daFile) || OptionKeyIsDown() ) && ((pCurrentProcess->p_taskmode & modeDeskAccessory) == 0) ) { if (pCurrentProcess->p_layer == nil) { retval = appIsDaemon; goto ErrorRecovery; } else daOwnerPtr = pCurrentProcess; } } /* See if handler already launched. Must do this with a possibly nil pDAName, * since we can't look in the file to find the name if the file is busy! */ if (daOwnerPtr == nil) daOwnerPtr = PEntryFromFileSpec(pDASpec->daFileSpec, pDAName); if ((alreadyLaunched = (daOwnerPtr != nil)) == false) { /* Try to launch the DA Handler (implicitly setting daOwnerPtr) */ StandardLaunch(nil, 0, modeDeskAccessory+FILE_FLAG_MULTILAUNCH, nil, &dahLaunchResults, pDASpec); if ((retval = dahLaunchResults.LaunchError) != noErr) goto ErrorRecovery; /* Nuke dad if opening the DA was just a knee-jerk reaction by the * application, since the relationship is accidental. */ if (fromOpenDeskAcc) daOwnerPtr->p_dadProcess = nil; } /* If DA belongs to someone else: bring it forward. If it's brand new, make it sleep * until it can come forward (avoids flash). * NOTE: Assumes that DA is not using this call to bring itself forward (since we'll just * _OpenDeskAcc). OK, tho. DA could use SetFrontProcess instead. */ if (daOwnerPtr != pCurrentProcess) { if ((SetFrontProcess(&daOwnerPtr->p_serialNumber) == noErr) && (alreadyLaunched == false)) PutOnSleepQueue(daOwnerPtr, MAXVALUE); } /* Owner is current process: load the DA! * NOTE: Must set the p_daResource before call through, in case the call through * results in a context switch (I've seen DAs put up StandardFile here!). */ else { if ((coercionState == CS_DONE) || (coercionState == CS_ENTERING)) { short callThroughResult; if (((pCurrentProcess->p_taskmode & modeDeskAccessory) != 0) && (daResourceNum != 0)) (*daOwnerPtr->p_pcb)->p_daResource = daResourceNum; oldtrap = patchtraps[OPENDESKACC].oldtrap; callThroughResult = CALL_FNC_PTR(Opendeskacc_ptr,oldtrap,((pDAName != nil) ? pDAName : &rsrcName)); if (pDrvrNum != nil) *pDrvrNum = callThroughResult; } else { dbmsg("Bad time to call LaunchDeskAccessory/_OpenDeskAcc"); } } ErrorRecovery: A5Restore(olda5); return (retval); #undef daOwnerPtr } /* c_opendeskacc. Our patch to OpenDeskAcc. This trap used to mean "open * desk accessory". Now it means "I don't recognize the Apple menu item the * user selected, what do you make of it?". What we make of it is one of * about a half dozen things that we stick in the Apple menu: a phantom item to * aid scrap coercion, a process name to switch to, a command, our about box, * a speciality item added by an application, or (last but not least) a DA. */ pascal short c_opendeskacc(StringPtr stringPtrDAName) { short retval; unsigned long olda5; AppleItemHdl hAppleItem; olda5 = ProcessMgrA5Setup(); /* In the middle of scrap coercion? Handle this state from here. Install a fake * DA window. Set coercionState so app does the proper activation (i.e. if there * is already a window, expect a deactivate on it before an activate on ours). */ retval = FAKE_OPENDA_RETVAL; if ((hAppleItem = StringToAppleItemHdl(stringPtrDAName)) != nil) { SendAppleMenuSelectMsg(hAppleItem); } /* Item is nothing we know as special. Try to open as a desk accessory. */ else { DASpecBlock daSpec; daSpec.daFileSpec = nil; daSpec.daDrvrName = stringPtrDAName; if (CoreLaunchDeskAccessory(&daSpec, &retval, true) != noErr) SysBeep(20); } A5Restore(olda5); return retval; } /* c_LaunchDeskAccessory. Cause the specified DA to be launched and/or frontmost. * If the file name is not specified, assume that DA is in the current chain. If * the file name is specified, but the resource name is not, use the first DRVR * resource in the file. */ pascal OSErr c_LaunchDeskAccessory(FSSpec *pFileSpec, StringPtr pDAName) { DASpecBlock daSpec; daSpec.daFileSpec = pFileSpec; daSpec.daDrvrName = pDAName; return(CoreLaunchDeskAccessory(&daSpec, nil, false)); } /* c_sysedit. Patch to increment SCRAPCOUNT if the fake window is on top. */ pascal short c_sysedit(short editCmd) { unsigned long olda5; WindowPeek windowPeek; pascal void (*oldtrap)(); short retval; /* Locate top window */ windowPeek = WINDOWLIST; /* Check the windowKind field to see if it's mine */ if ((windowPeek != nil) && (windowPeek->windowKind == SCRAP_COERCION_FAKE_DA_WINDOW_ID)) { /* If cut or copy bump the scrapcount. Pretend we accepted it * in any case. */ CheckForCutCopySysEditCmd(editCmd, &SCRAPCOUNT); retval = wTrue; } else { olda5 = ProcessMgrA5Setup(); /* Check if we came in here for a cut or copy */ CheckForCutCopySysEditCmd(editCmd, &cutCopyCount); oldtrap = patchtraps[SYSEDIT].oldtrap; A5Restore(olda5); retval = CALL_FNC_PTR(Sysedit_ptr,oldtrap,(editCmd)); } return retval; } /* c_systemmenu. Our chance to have real menus! */ pascal void c_systemmenu(long menuResult) { PEntryPtr pProc; LayerPeek pNextLayer; Boolean hideFront; unsigned long olda5; pascal void (*oldtrap)(); olda5 = ProcessMgrA5Setup(); oldtrap = patchtraps[SYSTEMMENU].oldtrap; /* Was it our menu (versus a DA or other system menu)? */ if (MENU_ID_OF_SELECTION(menuResult) != kApplicationMenuID) { A5Restore(olda5); CALL_FNC_PTR(Systemmenu_ptr,oldtrap,(menuResult)); return; } /* Our menu was selected from. Do the right thing. */ pProc = nil; switch (ITEM_ID_OF_SELECTION(menuResult)) { case HIDE_APP_ITEM: hideFront = true; if ((pNextLayer = LGetNextLayer(GetLayer(), true)) != nil) pProc = LayerOwner(pNextLayer); break; case HIDE_OTHERS_ITEM: case SHOW_ALL_ITEM: ShowHideOthers(ITEM_ID_OF_SELECTION(menuResult) == SHOW_ALL_ITEM); AdjustApplicationMenu(nil); break; /* Process name was selected */ default: { /* Sample option key promptly */ hideFront = OptionKeyIsDown(); /* Initiate foreground switch to the selected process */ if ((pProc = PEntryFromApplicationMenuItem(ITEM_ID_OF_SELECTION(menuResult))) == pFrontProcess) pProc = nil; break; } }; /* Handle foreground switch and optional hiding */ if ((pProc != nil) && (SetFrontProcess(&pProc->p_serialNumber) == noErr) && hideFront) HideLayer(GetFrontAppLayer(), false); /* Disemblazon the menu */ HiliteMenu(0); A5Restore(olda5); } /* c_systemclick. Yet another step in scrap coercion. */ pascal void c_systemclick(EventRecord *evtPtr, WindowPeek *windowPtr) { unsigned long olda5; pascal void (*oldtrap)(); olda5 = ProcessMgrA5Setup(); /* Coercion state CS_CLICK_IN_WINDOW said mouse down in fake window, and * sets state to CS_TAKE_DOWN_WINDOW. App responds by calling _SystemClick, * so now take down fake window to simulate a _CloseDeskAcc. Next call(s) * to _WaitNextEvent, _GetNextEvent, or _EventAvail should return the activate * events for the (new) top window. */ if (coercionState == CS_TAKE_DOWN_WINDOW) { TakeDownFakeWindow(); } else { oldtrap = patchtraps[SYSTEMCLICK].oldtrap; CALL_FNC_PTR(Systemclick_ptr,oldtrap,(evtPtr,windowPtr)); } A5Restore(olda5); } #define IS_UPDATE_OR_ACTIVATE_EVENT(eCode) ( (eCode == updateEvt) || (eCode == activateEvt) ) /* c_systemevent. If its a key or any event with our window in the message field, * bump SCRAPCOUNT and return true. * NOTE: Like much of the desk mgr, we don't understand completely why we can't * can't call the old routine inside of _MenuSelect. There is no apparent reason * why we should, except to pass on an update event that would invalidate the menu * saved-behind bits (but this should never happen). Right now we always pass thru * disk-insert events since we need to always get them mounted. */ pascal short c_systemevent(EventRecord *theevent) { unsigned long olda5; WindowPeek windowPeek; LayerPtr pLayer; short eventCode; Boolean isCmdKeyDown; short dontPassThru; pascal void (*oldtrap)(); short retval; eventCode = theevent->what; isCmdKeyDown = ((eventCode == keyDown) && ((theevent->modifiers & cmdKey) != 0)); /* Get the window we're going to deal with, based on the event handling table in ROM _SystemEvent */ windowPeek = IS_UPDATE_OR_ACTIVATE_EVENT(eventCode) ? (WindowPeek)theevent->message : (WindowPeek) FrontWindow(); /* If it is our fake DA window, swallow the events, possibly looking for cut or copies */ if (windowPeek != nil && windowPeek->windowKind == SCRAP_COERCION_FAKE_DA_WINDOW_ID) { if (isCmdKeyDown) CheckForCutCopyMenuKey((char)theevent->message, &SCRAPCOUNT); /* Have to return false for pseudoevents */ return (theevent->what != app4Evt); } olda5 = ProcessMgrA5SimpleSetup(); oldtrap = patchtraps[SYSTEMEVENT].oldtrap; dontPassThru = wFalse; retval = wFalse; if ( (coercionState == CS_DONE) && imLayerKeyDown( theevent, true) ) { theevent->what = nullEvent; dontPassThru = wTrue; retval = wTrue; } dontGetFakeKey = 0; if (isCmdKeyDown && coercionState == CS_DONE) CheckForCutCopyMenuKey((char)theevent->message, &cutCopyCount); /* Is the event an update to a window in another app's layer? If so, we need to * return wTrue (so app doesn't try to handle event for window it doesn't own), AND * not call through (preventing SystemEvent from trying to give the update to a DA * that is currently switched out. */ if (eventCode == updateEvt) { if ( ((pLayer = FindAppLayer(windowPeek)) != nil) && (LayerOwner(pLayer) != pCurrentProcess) ) { dontPassThru = wTrue; retval = wTrue; } } /* No? Check for the debugger key */ else if ( (pDebugProcess != nil) && (theevent->what == keyDown) && (*((char *) &theevent->message + 2) == debugControlKeyCode) && ((theevent->modifiers & (cmdKey | optionKey)) == (cmdKey | optionKey)) ) { /* Saw the debug key from a "safe" level. Reset counter because we * succeeded getting from PostEvent to GNE to SystemEvent. Make a * system error that the debugger will interpret as "user hit the * debugger key". This SysError will return! */ debugKeyTryCount = -1; SysError(enterDebugger); dontPassThru = wTrue; } A5SimpleRestore(olda5); if ((dontPassThru == wFalse) || theevent->what == diskEvt) retval = CALL_FNC_PTR(Systemevent_ptr,oldtrap,(theevent)); return retval; } #ifdef WRITENOW_FIX /* ********** NOTE: The following 2 routines are done for WriteNow to work. ********* */ /* OverlaySystemEvent. Set a5 to CURRENTA5, then call app's _SystemEvent. * NOTE: This is a hack for WriteNow that should be taken out when it is rev'ed * (or at least check for 'VERS'). */ pascal short OverlaySystemEvent(EventRecord *theevent) { unsigned long olda5; pascal void (*apptrap)(); short retval; /* Get app's patch routine addr */ olda5 = ProcessMgrA5SimpleSetup(); (ProcPtr)apptrap = (*pCurrentProcess->p_pcb)->appSystemEventPatch; A5SimpleRestore(olda5); /* Call it using CURRENTA5, but SystemEvent caller's graphics pointer */ olda5 = CurrentA5Setup(); retval = CALL_FNC_PTR(Systemevent_ptr,apptrap,(theevent)); A5Restore(olda5); return retval; } /* SetOverlaySystemEvent. Set up SystemEvent patch address in current process, and * return the address of our overlay. * NOTE: Assumes a5 == PROCESSMGRGLOBALS. */ ProcPtr SetOverlaySystemEvent(ProcPtr apptrap) { (*pCurrentProcess->p_pcb)->appSystemEventPatch = apptrap; return ((ProcPtr) OverlaySystemEvent); } /* **************************** */ #endif WRITENOW_FIX /* LimitFrontSleepForDriversSake. Calculate a desired maximum sleep time based on * how soon the next driver needs to be called (drivers are called via SystemTask which * is called by old apps and by event_common). * NOTE: Assumes that pCurrentProcess == pFrontProcess, since drivers installed in * the UNITTABLE but local to the current (non-FG) process are irrelevant. Furthermore, * this must be called *before* the process has actually started to sleep, since we don't * monkey around with the sleep queue. * NOTE: Best called after SystemTask, since SystemTask may satisfy current need and * advance dCtlDelay. */ void LimitFrontSleepForDriversSake(void) { register unsigned long nextCall, minSoFar; register DCtlPtr dCtlPtr; DCtlHandle *pDCtlHdl; short loopCount; /* Shortcut if process is not planning on sleeping anyway */ if (pCurrentProcess->p_wakeuptime == 0) return; /* DA Handler should not sleep in the foreground, since it has a driver that * probably needs cursor calls. */ if ((pCurrentProcess->p_taskmode & modeDeskAccessory) != 0) minSoFar = 0; else { minSoFar = 0xFFFFFFFF; pDCtlHdl = UNITTABLE; loopCount = UNITNTRYCNT; while ((--loopCount >= 0) && (minSoFar != 0)) { if ( (*pDCtlHdl != nil) && ((dCtlPtr = **pDCtlHdl) != nil) && WantsTime(dCtlPtr) ) { /* Check when driver should be called, handling dCtlDelay < MIN_DRIVER_DELAY * and instances where we're late giving the driver time. Also checks * for wraparound (in which case minSoFar should be min'd with 0xFFFFFFFF, * a NOP we can avoid!). */ if ((nextCall = (unsigned long) dCtlPtr->dCtlCurTicks + dCtlPtr->dCtlDelay) >= (unsigned long) dCtlPtr->dCtlCurTicks) { if (nextCall <= TICKS) nextCall = (dCtlPtr->dCtlDelay < MIN_DRIVER_DELAY) ? TICKS + MIN_DRIVER_DELAY : 0; if (nextCall < minSoFar) minSoFar = nextCall; } } pDCtlHdl++; } } /* Limit sleep time to calculated value. */ if (pCurrentProcess->p_wakeuptime > minSoFar) pCurrentProcess->p_wakeuptime = minSoFar; } /* LimitBackSleepForDriversSake. Calculate a desired maximum sleep time based on * how soon the next driver needs to be called (drivers are called via SystemTask which * is called by old apps and by event_common). * NOTE: This must be called *before* the process has actually started to sleep, since * we don't monkey around with the sleep queue. * NOTE: Best called after SystemTask, since SystemTask may satisfy current need and * advance dCtlDelay. */ void LimitBackSleepForDriversSake(void) { register DCtlPtr dCtlPtr; register unsigned long nextCall, minSoFar; DCEDescHdl ppDCEDesc; DCEDescPtr pDCEDesc; unsigned short cDCE; /* Shortcut if process is not planning on sleeping anyway */ if (pCurrentProcess->p_wakeuptime == 0) return; /* Get handle to DCE switch table, and find out how many local drivers there are. * Leave now if there are nonesuch. */ if ((cDCE = GetHandleSize((ppDCEDesc = (*(pCurrentProcess->p_pcb))->dces))) == 0) return; /* Loop through the DCE switch table. */ pDCEDesc = *ppDCEDesc; cDCE /= sizeof(**ppDCEDesc); minSoFar = 0xFFFFFFFF; do { if ( ((dCtlPtr = *((DCtlHandle) UNITTABLE[pDCEDesc->d_unit])) == nil) || (WantsTime(dCtlPtr) == false) ) continue; /* Check when driver should be called, handling dCtlDelay < MIN_DRIVER_DELAY * and instances where we're late giving the driver time. Also checks * for wraparound (in which case minSoFar should be min'd with 0xFFFFFFFF, * a NOP we can avoid!). */ if ((nextCall = (unsigned long) dCtlPtr->dCtlCurTicks + dCtlPtr->dCtlDelay) >= (unsigned long) dCtlPtr->dCtlCurTicks) { if (nextCall <= TICKS) nextCall = (dCtlPtr->dCtlDelay < MIN_DRIVER_DELAY) ? TICKS + MIN_DRIVER_DELAY : 0; if (nextCall < minSoFar) minSoFar = nextCall; } /* Advance to next descriptor (let the counter catch us going off the end) */ pDCEDesc += 1; } while ((--cDCE > 0) && (minSoFar != 0)); /* Limit sleep time to calculated value. */ if (pCurrentProcess->p_wakeuptime > minSoFar) pCurrentProcess->p_wakeuptime = minSoFar; } /* GiveLocalDriversTime. Give accRun calls to drivers local to the current process. * NOTE: It only gives time to drivers that were switched out of the unit table * when this guy was switched out, so if this guy opened one after the switch, he'll * have to wait for the next go-around to get the guy some time. * NOTE: Routine optimized for case where task has no local drivers. */ void GiveLocalDriversTime(void) { register DCtlPtr dCtlPtr; DCEDescHdl ppDCEDesc; DCEDescPtr pDCEDesc; unsigned short cDCE; unsigned long tickDelay; CntrlParam ctlPB; /* Get handle to DCE switch table, and find out how many local drivers there are. * Leave now if there are nonesuch. */ if ((cDCE = GetHandleSize((ppDCEDesc = (*(pCurrentProcess->p_pcb))->dces))) == 0) return; /* Lock ppDCEDesc, because a control call could conceivably cause movement in * the Process Mgr heap, most likely by causing sys heap growth. */ HLock(ppDCEDesc); /* Loop through the DCE switch table. Calling each driver noted therein. * NOTE: dCtlPtr could come directly from *ppDCEDesc->d_handle, but then we lose * the guarantee that it is actually in the unit table. A misbehaving app might * pull the guy out of the unit table, but leave him intact between the time the * app was switched in, and the time SystemTask was called. */ pDCEDesc = *ppDCEDesc; cDCE /= sizeof(**ppDCEDesc); do { if ( ((dCtlPtr = *((DCtlHandle) UNITTABLE[pDCEDesc->d_unit])) == nil) || (WantsTime(dCtlPtr) == false) ) continue; tickDelay = dCtlPtr->dCtlDelay; if (tickDelay == 0 || (unsigned long)dCtlPtr->dCtlCurTicks + tickDelay < TICKS) { /* Update him to show that he got time */ dCtlPtr->dCtlCurTicks = TICKS; /* Give him some time */ ctlPB.ioCRefNum = dCtlPtr->dCtlRefNum; ctlPB.csCode = accRun; ControlImmed(&ctlPB); /* Result can be positive since this is an immediate call */ assert(ctlPB.ioResult >= 0); } /* Advance to next descriptor (let the counter catch us going off the end) */ pDCEDesc += 1; } while (--cDCE > 0); HUnlock(ppDCEDesc); } /* c_systemtask. A patch for SystemTask. Do nothing unless the calling process and * coercion are in a generic state, and the fake DA window is not up (can the window * be even be up when coercionState == CS_DONE?). We can call the original routine * only if the current app is the frontmost, since the cursor handling and notification * manager rely on the WINDOWLIST to contain the active window. If the current app is * not frontmost, we do the remaining part of the original routine. Currently, this * only means giving control calls to the needs-time drivers. * NOTE: We disallow switching so we don't get screwed up by DA's that make event calls * (we could end up back here, and call them again, resulting in an infinite loop). * NOTE: We bracket the call with BeginSystemMode/EndSystemMode so that files opened * by permanent system drivers are not closed when an app quits. Unfortunately, this * is not quite bombproof because some "permanent drivers" (like QuickMail) are really * DAs that make event calls, which gives another app the chance to die. ExitToShell * necessarily resets the SystemModeLevel to 0. A file opened later in the same * control call would be tracked. This shouldn't screw up legitimate users of system * mode, tho, because of the rule that it not be in effect over an event call. * NOTE: Have to set up CURRENTA5 because of case where a DA might bring up an * alert. The problem is that if the alert causes any kind of event for one * of the underlying app's dialog windows, which might have a user item * installed and the user item might expect A5 == CURRENTA5. */ pascal void c_systemtask(void) { register PEntryPtr pCurPEntry; pascal void (*oldtrap)(); unsigned long olda5; /* Give the system/toolbox some time */ SynchIdleTime(); olda5 = ProcessMgrA5Setup(); pCurPEntry = pCurrentProcess; /* In FG: do old trap if not in the middle of menu activity or switching */ if (pCurPEntry->p_state == PRRUN) { if ((coercionState == CS_DONE) && (WINDOWLIST == nil || WINDOWLIST->windowKind != SCRAP_COERCION_FAKE_DA_WINDOW_ID)) { (void) BeginSystemMode(); oldtrap = patchtraps[SYSTEMTASK].oldtrap; A5Restore(CURRENTA5); CALL_FNC_PTR(Systemtask_ptr, oldtrap, ()); A5Restore(PROCESSMGRGLOBALS); (void) EndSystemMode(); } } /* In BG: restrict activity to control calls for current drivers. */ else if (pCurPEntry->p_state == PRBACKRUN) GiveLocalDriversTime(); A5Restore(olda5); }