TweetFollow Us on Twitter

Apr 86 Tech Questions
Volume Number:2
Issue Number:4
Column Tag:Ask Professor Mac

Reader's Technical Questions

By Steve Brecher, Software Supply, 4618 E. Sixth St., Long Beach, CA. 90814

Steve only writes when someone sends him an interesting question to answer. Help us keep Steve busy by sending in your technical questions to Professor Mac. You may write to Steve directly at his software store listed above.

Desk Accessories and JIODone

Q. Jan Eugenides sent me a question about how the Control routine of a desk accessory should return: via an RTS instruction or via a JMP through JIODone? This turns out to be a somewhat complex issue; the following discussion is based on answers from my most trusted authority, the ROM, and insights provided by Lew Rollins and Jon Hueras via the CompuServe Mac Developers SIG.

A. Inside Macintosh says that the Control routine of a driver should return to the ROM's IODone routine -- to the address contained in the system global variable JIODone. When jumping through JIODone, register A1 must contain the address of the driver's Device Control Entry (DCE); otherwise, no registers need to be preserved by the Control routine. Generally, any queued request to a driver must return to IODone so that the system won't hang waiting for completion of the request.

If a driver doesn't need to be locked in the heap when it is not executing, it has the dRAMBased bit in the DCE set and the dNeedLock bit clear. Note that a driver is always locked before it is entered, so self-locking by a driver is superfluous. The dNeedLock bit specifies whether the driver needs to be locked when it is not executing. If dNeedLock is clear, IODone will unlock the driver and the DCE.

For queued I/O requests, IODone removes the completed request from the queue and dequeues the next pending request, if any, for execution. However, Desk Accessory (DA) Control requests are not queued (they have the noQueue bit set in the Conrol trap word) except for the case of the "goodbye kiss" (csCode = -1) call that is issued if the dNeedGoodbye bit is set in the driver header.

But... the IODone routine in the 64K ROM has a bug (fixed in the 128K ROM). In order to determine if the request was a queued one, it looks at the noQueue (,IMMED) bit in the trap word in the queue element pointed to by the dCtlQueue field of the DCE. Problem is, if the request was not a queued one, then it's not in the queue to be examined! Since DA Control calls (except for a "goodbye kiss") are not queued, IODone will be using a Nil pointer in the dCtlQueue field and will examine the word at offset ioTrap (offset 6) from address zero, i.e., address 00000006. As luck would have it, usually the noQueue bit happens to be set at that irrelevant location, and IODone doesnt't try to remove the nonexistent queue element - so the bug doesn't usually bite. But it's dicey for a DA to depend on that happenstance.

A further complication for some DAs is that their Control routines may be reentrant. Consider the case of a DA that has the dNeedTime bit set in its header because it wants to be called periodically. Suppose this DA's Control routine calls ModalDialog (possibly indirectly via SFGet/PutFile or Alert). ModalDialog calls SystemTask, which in turn may issue a periodic Control call to the DA. If the DA is always unlocked at Control exit (either explicitly by itself or by IODone), then the periodic call will at exit unlock the DA/DCE, and when ModalDialog returns the hapless DA will find itself "mysteriously" unlocked with perhaps disastrous results. If you share Jon Heuras's "sheer paranoia" about reentrancy, you can use his technique of clearing the dCtlEnable bit in the DCE at entrance to Control, and setting it again at exit -- this avoids reentrant Control calls.

Ok, so those are the facts -- what does it all mean? I would suggest the following approach. It assumes that the DA does not need itself nor its DCE to be locked while the DA is not executing; and it permits reentrance. There are few cases when a DA must be locked when not executing, and it is impolite to the host application to have unnecessarily-locked blocks in the heap.

Allocate a word -- or to be safe, a longword -- in the DAs private storage to be used as a Control entrance counter. Clear this counter in the Open routine. At entrance to the Control routine, increment the counter. Then the Control exit logic would look like this:

;A0 points to the request parameter block
;A1 points to the DCE
;the following 4 lines can be omitted if dNeedGoodbye is clear
;- otherwise we assume private storage has beed disposed of
 Btst #noQueueBit-8,ioTrap(A0) ;queued request?
 Beq.S  @0;no
 Move.L JIODone,A0 ;yes, it's "Goodbye"...
 Jmp  (A0);so return to IODone
@0 Move.L dCtlStorage(A1),A0;get storage handle
 Move.L (A0),A2  ;dereference
 SubQ.L #1,EntranceCount(A2);decrement counter
 Bne.S  @1;br if reentered
 _HUnlock ;unlock storage
 Move.L A1,A0  ;DCE pointer
 _RecoverHandle  ;DCE handle
 _HUnlock ;unlock DCE
 Lea  Driver,A0  ;addr of this DRVR
 _RecoverHandle  ;handle to ourself
 _HUnlock ;unlock ourself
@1 Rts  ;bypass IODone

Scamble Well, Please

Q. Dr. David T. Linker of Trondheim, Norway, writes, “I was encouraged by your comment on 'dumb' questions [I solicited them! --SB]. Why do Macintosh debuggers offer to scramble the heap? I use the heap ... but I can't think of a reason why I would want to scramble it. It seems that this would be undesireable, kind of like 'scramble variables.'"

A. "Rearrange" would be a more accurate word for the function offered by the debuggers. Scrambling the heap means to move relocateable blocks around; if your code at some point depends on the incorrect assumption that a given block is locked (won't move), then scrambling the heap may help to reveal that bug. After the scramble, the program won't find what it expects to find at what it thinks is the address of the block, and will probably behave differently than it would without the scrambling.

Such a buggy program may have seemed to be healthy only because, by luck, the block has not moved; but in some circumstances that testing has not yet encountered, the block will move, and the program will crash or otherwise misbehave. If your program can survive a heap scramble on any trap which might rearrange the heap, then you can have some confidence that it is not making incorrect assumptions about blocks being immobile.

Application Icon

Q. This and the following question were submitted by L. Tannenbaum of Long Beach, Calif. "How do I get the Finder to use the 'generic' icon for an application? What info do I include or exclude in the resource file?"

A. This is an unusual question - most often, people want to know how to give their application a custom icon. To get the generic "hand-on-diamond" icon on the Finder desktop, all you need do is specify APPL for the type of the application file. Begin your RMaker input file with:

Name Of My Application File
APPLMYAP

This examples uses MYAP as the signature of the application.

Moving Pictures

Q. How do I get a "PICT," say one I drew in MacPaint, into a resource file? I have RMaker (with no documentation) and REdit (not ResEdit).

A. Use RMaker to create a dummy PICT resource in the target resource file:

TYPE PICT = GNRL ;; define a "new" type via GNRL

,128 ;; resource ID

.I ;; integers follow

0 0 0 0 ;; dummy rectangle coordinates

In MacPaint, cut your picture and paste it into the Scrapbook. Now run REdit and open your resource file and the PICT resource type. You'll see PICT 128 represented by an icon. Open the Scrapbook, cut the picture from it, select the PICT 128 icon, and paste. (P.S. ResEdit is more powerful than REdit; ResEdit lets you create resources as well as modify them. ResEdit 1.0d5 is available on the MacTutor Utility disk and source code disk #6. ]

AutoWatch

It's a service to the user for an application to display a wristwatch cursor whenever the application is busy and not in the mood for user input. But it's something of a nuisance for the programmer to explicitly change the cursor before and after each time-consuming operation; besides, sometimes it's not easy to know a priori that an operation will be time-consuming.

I saw a message from Larry Rosenstein of Apple on one of the networks in which he outlined a scheme for automating changing of the cursor to a watch and back. The basic idea is to install both a vertical-blanking (VBL) interrupt task and a hook into GetNextEvent. If GetNextEvent has not been called for some pre-defined period of time, then the application is deemed to be "busy" and the cursor is changed to a watch; this is done by the VBL task.

The system's VBL interrupt handler checks each element in a VBL queue each 60th of a second, decrementing a counter value in the queue element. If the counter decrements to zero, a task associated with that VBL queue element is executed. Then -- unless the VBL task reinitialized the counter to a non-zero value -- the element is removed from the queue. For more information, see the Vertical Retrace Manager chapter of Inside Macintosh.

Suppose we want a watch cursor to be displayed whenever half a second passes with no calls to GetNextEvent. What we do is install a VBL task with a value of 30 (30/60 of a second) in the associated VBL queue element counter field. We also revector the GetNextEvent trap to a piece of code which restores the counter in the VBL queue element to 30 (and then does normal GetNextEvent processing). If GetNextEvent is called at least once every half second, the VBL task will never be executed, because the VBL queue element's counter will never get decremented to zero. But if half a second elapses with no calls to GetNextEvent, the VBL task will be executed.

When the VBL task is executed, it examines a global flag which indicates whether the cursor has already been changed to a watch. If the flag is false, the task saves the current cursor, changes the screen cursor to a watch, and sets the flag. Then it restores the counter field in the VBL queue element so that the element will not be dequeued.

The GetNextEvent hook code restores the VBL task's timer value, and, if the VBL task changed the cursor from a non-watch to a watch (indicated by the global flag set by the VBL task), restores the original cursor.

In simplest terms, every time GetNextEvent is called it says to the Vertical Retrace Manager, "Whoa! The application is not busy! Restart your countdown." The Vertical Retrace Manager, each 60th of a second, decrements the counter; if it has reached zero, that means GetNextEvent has not been called since the countdown last started, implying a busy application which needs a watch cursor: the VBL task is executed and changes the cursor.

There are a couple of minor complications. The GetNextEvent hook code cannot just do an InitCursor (which makes the cursor an arrow), because the pre-watch cursor may not have been an arrow. But neither can it blindly restore whatever cursor the VBL task saved. Consider this example: the application calls DIBadMount to initialize a disk. DIBadMount changes the cursor to a watch as it starts to format the disk. Shortly thereafter, the VBL task countdown reaches zero, and the VBL task saves the current cursor (a watch!) and sets the cursor to a watch - superfluous, but no harm done. When the disk format completes, DIBadMount does an InitCursor, and returns to the application. The application calls GetNextEvent. Now our GetNextEvent hook sees the flag set by the VBL task indicating that the VBL task saved the old cursor and changed the cursor to a watch; so GetNextEvent restores the old cursor. No good! We've restored to a watch when we don't want a watch. To avoid this, the GetNextEvent hook looks at the cursor saved by the VBL task; if it's a watch, it doesn't restore it.

Also, there are times when GetNextEvent is not called, but nonetheless it would be inappropriate to change the cursor to a watch: when the mouse is being tracked by, e.g., the Menu Manager or the Control Manager. If the user dawdles while he has a menu pulled down, we can't change the cursor just because GetNextEvent is not being called. So, the VBL task will not change the cursor if the mouse button is down.

The time period used to initialize the VBL counter is application-dependent. About half a second works pretty well with an application I recently implemented. If the period is too short, the cursor will too-often toggle to a watch and back in a distracting way. If the period is too long, then the user will not be promptly informed that the application is busy, and the change of the cursor to a watch will seem unrelated to the preceding user action which initiated the time-consuming process.

My implementation of AutoWatch in MDS Assembler is shown in Figure 1. This implementation will not work under Switcher.

Move Low

The 128K ROM has a new feature with regard to the loading of CODE segments. If a CODE resource does not have the resLocked attribute, then the Segment Loader will load that segment as high as possible in the heap. The idea is that up at or near the top of the heap, it will be "out of the way" and not contribute to heap fragmentation.

However, this can be a problem with CODE 1, the code segment which is loaded first and which is usually the main segment of the application. Upon loading, the Segment Loader will lock it, regardless of the resLocked attribute, as it does for all segments. It will remain locked until the applicaton unloads it - but most applications don't unload CODE 1. If CODE 1 does not have the resLocked attribute (which it will not if generated by, e.g., the Consulair Linker), it will be loaded high in the heap - the initial heap before any heap expansion has occurred. Since almost all applications expand the heap either explicitly or implicitly, the new Segment Loader feature is effectively a "load in middle" for CODE 1.

To get around this, I implemented a routine (Figure 2) which moves the calling segment to or near the bottom of the heap. It consists of two parts: the invoking code, and a subroutine which does the actual move. The invoking code puts the subroutine on the stack and calls it; the subroutine reallocates the calling segment's heap block low in the heap, displaces its return address by the distance the segment was moved, and returns to the caller, which cleans up the stack. I put this code right after a call to MaxApplZone at the beginning of my CODE 1 segment.

The reason the subroutine is executed from the stack is that the Memory Manager call which it uses to get space low in the heap, _ReservMem, may move the original segment which at that point is unlocked. If it does move, and _ReservMem were called from within the moved segment, then _ReservMem would return to the wrong place.

You may notice that after _ReservMem, the original heap block containing the segment has been released - it's now a free block. Nonetheless we copy the segment contents from it to the new location low in the heap. That's OK - nothing can happen to clobber the freed block between the _ReservMem and the _BlockMove.

; Figure 1 Autowatch routine
;  by Steve Brecher, MacTutor 1986
; 
 XDEF AutoWatch,ShowWatch
;
; Thanks to Larry Rosenstein for the idea.
;
; This code must be in a  locked segment (e.g., CODE 1).
;
;
; Procedure AutoWatch(TickValue: integer);
;
; If TickValue <> 0, install VBL task that will change cursor
;to watch after TickValue ticks since GetNextEvent
;was called, provided that mouse button is not down. 
;Old cursor is saved before cursor is changed to watch.
;Installs GetNextEvent hook that restores saved cursor
;and reinits VBL countdown.
; Calls _InitCursor.
;Call AutoWatch with TickValue<>0 just before entering
;     your event loop.
; If TickValue = 0,  remove VBL task and GetNextEvent hook. 
;Call before quitting application.
;
; Procedure ShowWatch;
;Unconditionally change cursor to watch. (Must
;have called AutoWatch with TickValue <> 0 at
;some time previous.)  Typically called right before
;exiting application, to cover time until Finder (or
;  whatever is next) comes up.
;
 IncludeMacTraps.D
 IncludeSysEqu.D
 IncludeQuickEqu.D

 IncludeMacros ;see MacTutor Jan. 1986 ie,
 ; StackFrame,  Arg, Result, Local, Return
 ; Pop ..., Push ...,  Assume
;
;  Global variables
;
GNEptr  DS.L1
GNEhookPtrDS.L 1
GNEreturn DS.L 1
VBLTicksDS1
watchHndl DS.L 1
VBLCountPtr DS.L 1
SavedCursor DS.B cursRec
isWatch DS.B1
;
; The VBL queue element.
;
VBLQElem:
 DC.L 0 ;link
 DCvType
 DC.L 0 ;task ptr
 DC0  ;countdown value
 DC0  ;phase
;
; The VBL task code.
;
VBLtask:
 Move.L CurrentA5,A2 ;application's A5 into A2
 Tst.B  MBState  ;mouse button down?
 Bpl.S  @1;yes, don't change cursor
 Bset #0,isWatch(A2)   ;already make it a watch?
 Bne.S  @1;yes
 Lea  TheCrsr,A0 ;no, save current cursor...
 Lea  SavedCursor(A2),A1  
 Assume CursRec&3 = 0
 MoveQ  #(CursRec/4)-1,D0
@0 Move.L (A0)+,(A1)+
 Dbra D0,@0
 Move.L watchHndl(A2),A0  ;change to watch...
 Push.L (A0)
 _SetCursor
@1 Lea  VBLQElem+vblCount,A0  ;re-init VBL count
 Move VBLticks(A2),(A0)
 Rts
;
; The GetNextEvent hook code.  It's moved to system heap,
; which is why we must use a global to store the VBL queue
;  element count field address -- the Lea instruction can't be 
; used in code that's moved relative to the target of the Lea.
;
GNEhook:
 Pop.L  GNEreturn(A5)
 Bclr #0,isWatch(A5) ;did we make it a watch?
 Beq.S  @1;no
 Move.L watchHndl(A5),A0  ;yes, but did VBL task         ;"change" watch 
to watch?  (maybe
 ; somebody made it a watch behind our back)
 Move.L (A0),A0
 Lea  SavedCursor(A5),A1
 MoveQ  #(CursRec/4)-1,D0
@0 CmpM.L (A0)+,(A1)+;compare cursor saved by
 ; VBL task to watch...
 Dbne D0,@0
 Beq.S  @1;don't restore if cursor is a watch
 Pea  SavedCursor(A5);ok, restore non-watch 
 _SetCursor
@1 Move.L GNEptr(A5),A0 ;addr of original GetNextEvent 
 Jsr  (A0);go do GetNextEvent
 Move.L VBLCountPtr(A5),A0 ;re-init VBL countdown...
 Move VBLticks(A5),(A0)
 Move.L GNEreturn(A5),A0
 Jmp  (A0);return to trap dispatcher
GNEhookLen     Equ *-GNEhook

 StackFrame NotLinked
 Arg    TickValue,word

AutoWatch:
 Move TickValue(SP),VBLTicks(A5) ;install or remove?
 Beq.S  Remove   ;remove
 _InitCursor
 SFIsWatch(A5)
 Lea  VBLQElem+vblCount,A0 ;store addr of VBL
 Move.L A0,VBLCountPtr(A5)   ; queue elem count field
 Move VBLticks(A5),(A0)   ; for use by GetNextEvent
 ; hook.  Init VBL countdown 
 ; value in queue element.
 MoveQ  #CursRec,D0;length of a cursor
 _ResrvMem;make space low in heap
 Push.L ;room for _GetResource result
 Move.L #'CURS',-(SP)
 Push #watchCursor
 _GetResource
 Pop.L  A0
 Move.L A0,watchHndl(A5)  ;save handle to watch 
 _HNoPurge;don't let it go away
 Lea  VBLQElem,A0
 Lea  VBLtask,A1
 Move.L A1,VBLaddr(A0)
 _VInstall;install the VBL task
 MoveQ  #GNEhookLen,D1  ;length of our hook code
 Move.L D1,D0
 _NewPtr,SYS;move hook code to system heap...
 Move.L A0,A1
 Move.L A1,GNEhookPtr(A5) ;save addr in system heap 
 ; for remove
 Lea  GNEhook,A0
 Move.L D1,D0
 _BlockMove
 Move #$170,D0   ;GetNextEvent trap#
 _GetTrapAddress
 Move.L A0,GNEptr(A5);save addr of original code 
 Move.L A1,A0    ;addr of our hook code
 Bra.S  SetGNE   ;revector GetNextEvent to our 
 ; hook
Remove:
 Lea  VBLQElem,A0
 _VRemove ;remove the VBL queue element
 Move.L GNEhookPtr(A5),A0
 _DisposPtr ;dispose of hook code in sys heap
 Move.L GNEptr(A5),A0;addr of original GetNextEvent 
SetGNE: Move#$170,D0
 _SetTrapAddress ;set GetNextEvent routine address
 Return

ShowWatch:
 Move.L watchHndl(A5),A0
 Push.L (A0)
 _SetCursor
 Rts

 End
Figure 2
; 
SegStart: ;first location in segment (past segment header)

 ;(any data or miscellaneous stuff)
;
;  MoveLo subroutine.  This subroutine is moved to the stack and executed 
from there.
;
;  Unlock the calling segment, reallocate it low in the heap, copy its 
contents,
;  lock the calling segment, and return to the calling segment.
; 
;  On entry, A0 = handle to calling segment.
;
MoveLo: Pop.L  D2;return address
 Sub.L  (A0),D2  ;D2.W = relative return address
 Push.B (A0);save Memory Mgr flags in master ptr
 Clr.B  (A0);make sure not purgeable, unlocked
 _GetHandleSize  ;get his size
 Move.L D0,D1  ;save size
 _ResrvMem;make space low in the heap 
 ; (may move caller)
 Push.L (A0);save addr of calling segment
 Move.L D1,D0  ;size
 _ReallocHandle  ;reallocate handle in low space
 Move.L A0,D0  ;save handle
 Move.L (A0),A1  ;dest addr
 Pop.L  A0;source addr for move
 Exg  D1,D0 ;put size in D0, saved handle in D1
 _BlockMove ;copy contents of segment
 Move.L D1,A0  ;handle
 Pop.B  (A0);restore flags in master pointer
 Jmp  (A1,D2)  ;return
MoveLoSizeEqu  *-MoveLo

 XDEF Start ;application entry point
Start:  Bsr MaxApplZone 
 ;see "Ask Prof. Mac" Aug. 1985 
; Move this CODE resource down in heap
;
 MoveQ  #MoveLoSize,D0  ;size of MoveLo routine
 Sub  D0,SP ;make room on stack for routine
 Lea  MoveLo,A0  ;source addr
 Move.L SP,A1    ;dest addr
 _BlockMove ;move MoveLo routine to stack
 Lea  SegStart-4,A0;addr of segment header
 _RecoverHandle  ;pass segment handle to MoveLo
 Jsr  (A1);call MoveLo on stack
 Add  #MoveLoSize,SP ;pop routine from stack
;
; The usual initiation rites:
;(call _MoreMasters here as many times as needed)
 MoveQ  #0,D0
 SubQ #1,D0 ;all-events mask
 _FlushEvents
 Pea  -4(A5)
 _InitGraf
 _InitFonts
 _InitWindows
 _TEInit
 Push0.L
 _InitDialogs

Notes from Volume 2 Number 5:

Ask Prof. Mac Correction for April

Steve Brecher

Please note an error on page 62 in the April 1986 issue of MacTutor (volume 2 number 4) in the example of using JIODone. After testing the "noQueueBit", the next instruction says "Beq.S @0". This is incorrect. It should be "Bne.S @0" instead. If this bit is set, then the branch will be taken and the rest of the code implemented, which is what we want for a "no queued" item. For queued items, the branch will not be taken, and we will exit through JIODone.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Drop to GIF 1.28 - Drag-and-drop creatio...
Drag to GIF means zero-click movie-to-GIF conversion. Select a folder to watch and every movie saved or moved into that folder will be converted to an animated GIF. Features Dragging - Drag a movie... Read more
BetterTouchTool 3.158 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more
NeoFinder 7.4.1 - Catalog your external...
NeoFinder (formerly CDFinder) rapidly organizes your data, either on external or internal disks, or any other volumes. It catalogs and manages all your data, so you stay in control of your data... Read more
Tunnelblick 3.8.0 - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
Tweetbot 3 for Twitter 3.3.1 - Popular T...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Monosnap 3.5.9 - Versatile screenshot ut...
Monosnap lets you capture screenshots, share files, and record video and .gifs. Features Capture Capture full screen, just part of the screen, or a selected window Make your crop area pixel... Read more
Monosnap 3.5.9 - Versatile screenshot ut...
Monosnap lets you capture screenshots, share files, and record video and .gifs. Features Capture Capture full screen, just part of the screen, or a selected window Make your crop area pixel... Read more
Tweetbot 3 for Twitter 3.3.1 - Popular T...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Tunnelblick 3.8.0 - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
Bookends 13.2.5 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more

Latest Forum Discussions

See All

Void Tyrant guide - Tips and tricks for...
Void Tyrant continues to get a lot of play in these parts. Probably because the game is just so deep and varied. The next stop on our guide series for Void Tyrant is class-specific guides. First up is the Knight, as it’s the first class anyone has... | Read more »
Summon beasts and battle evil in epic re...
Imagine a tale of conlict between factions of good and evil, where rogueish heroes summon beasts to aid them in them in warfare and courageously battle dragons over fields of scorched earth and brimstone - that's exactly the essence of epic fantasy... | Read more »
Upcoming visual novel Arranged shines a...
If you’re in the market for a new type of visual novel designed to inform and make you think deeply about its subject matter, then Arranged by Kabuk Games could be exactly what you’re looking for. It’s a wholly unique take on marital traditions in... | Read more »
TEPPEN guide - The three best decks in T...
TEPPEN’s unique take on the collectible card game genre is exciting. It’s just over a week old, but that isn’t stopping lots of folks from speculating about the long-term viability of the game, as well as changes and additions that will happen over... | Read more »
Intergalactic puzzler Silly Memory serve...
Recently released matching puzzler Silly Memory is helping its fans with their intergalactic journeys this month with some very special offers on in-app purchases. In case you missed it, Silly Memory is the debut title of French based indie... | Read more »
TEPPEN guide - Tips and tricks for new p...
TEPPEN is a wild game that nobody asked for, but I’m sure glad it exists. Who would’ve thought that a CCG featuring Capcom characters could be so cool and weird? In case you’re not completely sure what TEPPEN is, make sure to check out our review... | Read more »
Dr. Mario World guide - Other games that...
We now live in a post-Dr. Mario World world, and I gotta say, things don’t feel too different. Nintendo continues to squirt out bad games on phones, causing all but the most stalwart fans of mobile games to question why they even bother... | Read more »
Strategy RPG Brown Dust introduces its b...
Epic turn-based RPG Brown Dust is set to turn 500 days old next week, and to celebrate, Neowiz has just unveiled its biggest and most exciting update yet, offering a host of new rewards, increased gacha rates, and a brand new feature that will... | Read more »
Dr. Mario World is yet another disappoin...
As soon as I booted up Dr. Mario World, I knew I wasn’t going to have fun with it. Nintendo’s record on phones thus far has been pretty spotty, with things trending downward as of late. [Read more] | Read more »
Retro Space Shooter P.3 is now available...
Shoot-em-ups tend to be a dime a dozen on the App Store, but every so often you come across one gem that aims to shake up the genre in a unique way. Developer Devjgame’s P.3 is the latest game seeking to do so this, working as a love letter to the... | Read more »

Price Scanner via MacPrices.net

13″ 1.8GHz MacBook Air on sale again for only...
Amazon has the 2017 13″ 1.8GHz/128GB MacBook Air on sale today for only $749.99 shipped. That’s $250 off Apple’s original MSRP for this model and the cheapest MacBook Air available from any Apple... Read more
Apple’s $1489 clearance price on refurbished...
Apple has Certified Refurbished 2018 13″ 2.3GHz 4-Core Touch Bar MacBook Pros available starting at $1489. Apple’s one-year warranty is included, shipping is free, and each MacBook has a new outer... Read more
New 2019 13″ 2.4GHz 4-Core MacBook Pros on sa...
Apple resellers B&H Photo and Amazon are offering the new 2019 13″ 2.4GHz 4-Core Touch Bar MacBook Pros for $150 off Apple’s MSRP. These are the same MacBook Pros sold by Apple in its retail and... Read more
B&H drops prices another $50 on clearance...
B&H Photo has dropped prices on clearance 2018 13″ MacBook Airs by a further $50 with models now available for $250 off Apple’s original MSRP. Overnight shipping, or expedited shipping, is free... Read more
Find the best sales & lowest prices on Ap...
Our Apple award-winning price trackers are the best place to look for the best sales and lowest prices on Apple gear. Scan our price trackers for the latest information on sales, bundles, and... Read more
Apple has clearance 2018 13″ MacBook Airs now...
Apple has Certified Refurbished 2018 13″ MacBook Airs available starting at only $849. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is shipped free. The... Read more
Save $400 on the 8-Core iMac Pro today at Ama...
Amazon has the base 8-core iMac Pro on sale today for $4599 including free shipping. Their price is $400 off Apple’s MSRP, and it’s the currently lowest price available for an iMac Pro. For the... Read more
Flash sale! New 11″ 1TB WiFi iPad Pros for th...
Amazon has the 11″ 1TB WiFi iPad Pro on sale today for only $1199.99 including free shipping. Their price is $350 off Apple’s MSRP for this model, and it’s the lowest price ever for a 1TB 11″ iPad... Read more
Weekend Deal: 2018 13″ MacBook Airs starting...
B&H Photo has clearance 2018 13″ MacBook Airs available starting at only $999 with all models now available for $200 off Apple’s original MSRP. Overnight shipping, or expedited shipping, is free... Read more
Apple has clearance 10.5″ iPad Pros available...
Apple has Certified Refurbished 2017 10.5″ iPad Pros available starting at $469. An Apple one-year warranty is included with each iPad, outer shells are new, and shipping is free: – 64GB 10″ iPad Pro... Read more

Jobs Board

Best Buy *Apple* Computing Master - Best Bu...
**711495BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 000882-Waterfront-Store **Job Description:** The Read more
Best Buy *Apple* Computing Master - Best Bu...
**711597BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000873-Colma CA-Store **Job Description:** **What does a Read more
Best Buy *Apple* Computing Master - Best Bu...
**711346BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 001095-Chesterfield-Store **Job Description:** Read more
Best Buy *Apple* Computing Master - Best Bu...
**704899BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000135-Pleasant Hill-Store **Job Description:** **What does Read more
Best Buy *Apple* Computing Master - Best Bu...
**707083BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000045-Rockford-Store **Job Description:** **What does a Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.