/* Altabber, n. He who performs an `Alt-Tab' action. ** ** Version 1.5 (1.4.99) İ Mfc. ** ** Compile with DICE: ** dcc -mi -mRR -pi Altabber.c NewReadArgs.c ** ** The "fixed-keys" version of Altabber (without argument/tooltypes parsing) ** can be obtained with: ** dcc -mi -mRR -DFIXEDKEYS -pi Altabber.c ** ** To enable debugging output, simply add "-DDEBUG" ** */ #include "Altabber.h" #ifndef FIXEDKEYS #include #endif /// Defines /* Messages we will receive from the Cx filter: */ #define WINTAB 1 /* Cycle thru windows */ #define SCRTAB 2 /* Cycle thru screens */ #define UPSTROKE 8 /* Select this item */ /* Max string lengths: */ #define MAX_TITLE_LEN 80 #define MAX_HOTKEY_LEN 40 /* Some macros: */ #define CURRENTLY_ACTIVE_SCR IntuitionBase->ActiveScreen #define CURRENTLY_ACTIVE_WIN IntuitionBase->ActiveWindow #define FIRST_AVAILABLE_SCR IntuitionBase->FirstScreen #define FIRST_AVAILABLE_WIN IntuitionBase->ActiveScreen->FirstWindow /* Pretty obvious :-) */ #define JAM1 0 #define JAM2 1 /* This is a clearly impossible hotkey */ #define DUMMY_KEY "Ctrl LAmiga RAmiga NumPad *" /* A couple of largely used macros: */ #define xalloc(s) AllocVec(s, 0) #define xfree(a) FreeVec(a) #ifdef DEBUG #define D(x) x #else #define D(x) #endif #define TEMPLATE "WQ=WIN_QUAL/K,WK=WIN_KEY/K," \ "SQ=SCR_QUAL/K,SK=SCR_KEY/K," \ "CX_PRIORITY/N/K," \ "COLOR1/N/K,COLOR2/N/K,"\ "AA=AUTOACTIVATE/S" /* Not yet fully functional! */ /// /// Globals /* The options (either CLI parameters or icon ToolTypes) */ /* are stored in the Opt struct. Some defaults are given here: */ const int def_pri = -5; const int def_color1 = 1; const int def_color2 = 2; struct { STRPTR win_qual, win_key; STRPTR scr_qual, scr_key; int *pri; int *color1, *color2; int aa; /* Not yet fully functional! */ } Opt = { "LAmiga", "Tab", "LAmiga", "Shift Tab", &def_pri, &def_color1, &def_color2, 0 /* Not yet fully functional! */ }; /* These are extern since we let the compiler care for */ /* opening library, replying WB, etc. */ extern struct IntuitionBase *IntuitionBase; extern struct WBStartup *_WBMsg; /* Has our window been opened? */ int Win_open = 0; int Scr_open = 0; /* Each item (either scr or win) is stored in a struct */ /* like this. Multiple records are then linked together */ /* in a single chain. */ struct record { void *id; unsigned char title[MAX_TITLE_LEN]; int len; int width; struct record *next; }; struct record *First_item; /* Head of the chain */ struct record *Special_item; /* Some magic stuff usad by the OMM */ struct record *Displayed_item; /* The currently displayed item */ struct record *Active_item; /* The item which was active when the user */ /* pressed the hotkey the first time */ /* Gfx Globals: all the global variable which are related to gfx */ /* (window, rastport, fonts, etc.) are placed in a single global struct */ struct { struct Window *win; struct RastPort *rp; struct TextFont *font; int font_height; int font_baseline; int max_width; int top_corner, left_corner; } GG = {NULL}; /* OMM (Olivier's Memory Method) */ void *Last_active_win = NULL; void *Last_active_scr = NULL; struct MsgPort *mp; /* the port where we will receive all the msgs */ ULONG Sigbits; /* signals we will wait for */ /* Every Cx must have an Object called a broker: */ CxObj *Broker; /* The special filters used to know when the user releases the qualifier key. */ /* They can't be "normal" hotkeys as they imply "upstroke", which the Cx */ /* interface doen't recognize. */ /* magic class code cmask qual qmask synonyms*/ IX scr_ix = { IX_VERSION, IECLASS_RAWKEY, 0, 0xffff, 0, 0, 0 }; IX win_ix = { IX_VERSION, IECLASS_RAWKEY, 0, 0xffff, 0, 0, 0 }; /// /// Prototypes int open_window(void); void close_window(void); void display_next(void); int create_scrlist(void); int create_winlist(void); void raise_scr(void); void raise_win(void); void dispose_list(void); void handle(void); int copy_title(STRPTR, STRPTR); int qualcode(STRPTR); CxObj *AttachFilter(STRPTR, ULONG); int ShowError(STRPTR); /// #define BROKERVERSION "Altabber 1.5 İ1999 Mfc." static const STRPTR version = "$VER: Altabber 1.5 (1.4.99)"; /// main() /* ŻŻŻŻŻŻ */ #ifdef _DCC __stkargs #endif void _main() { int error = 0; #ifndef FIXEDKEYS /* Read the arguments/tooltypes */ struct NewRDArgs nrda = { TEMPLATE, NULL, NULL, (LONG *) &Opt, -1, TRUE, /* other fields = NULL */ }; error = NewReadArgs(_WBMsg, &nrda); #endif if (error == 0) { unsigned char win_hotkey[MAX_HOTKEY_LEN]; unsigned char scr_hotkey[MAX_HOTKEY_LEN]; #ifdef DEBUG Printf("win_qual=%s\n", Opt.win_qual); Printf("win_key=%s\n", Opt.win_key); Printf("scr_qual=%s\n", Opt.scr_qual); Printf("scr_key=%s\n", Opt.scr_key); Printf("pri=%ld\n", *Opt.pri); Printf("color1,2=%ld,%ld\n", *Opt.color1, *Opt.color2); Printf("aa=%ld\n", Opt.aa); #endif /* Try to interpret the options and to build an adequate */ /* IX filter for every qualifier */ win_ix.ix_Code = IECODE_UP_PREFIX | qualcode(Opt.win_qual); scr_ix.ix_Code = IECODE_UP_PREFIX | qualcode(Opt.scr_qual); /* Build the win hotkey as the union of the "_QUAL" option */ /* and the "_KEY" option */ strcpy(win_hotkey, Opt.win_qual); strcat(win_hotkey, " "); strcat(win_hotkey, Opt.win_key); /* Build the scr hotkey as the union of the "_QUAL" option */ /* and the "_KEY" option */ strcpy(scr_hotkey, Opt.scr_qual); strcat(scr_hotkey, " "); strcat(scr_hotkey, Opt.scr_key); #ifdef DEBUG Printf("win_hotkey=|%s|\n", win_hotkey); Printf("scr_hotkey=|%s|\n", scr_hotkey); Printf("win_ix.ix_Code=%lx\n", win_ix.ix_Code); Printf("scr_ix.ix_Code=%lx\n", scr_ix.ix_Code); #endif if (mp=CreateMsgPort()) { /* Create the broker */ struct NewBroker nb = { NB_VERSION, "Altabber", BROKERVERSION, "Windoze-like Alt-Tab function", NBU_UNIQUE | NBU_NOTIFY, 0, *Opt.pri, 0, 0 }; nb.nb_Port = mp; Sigbits = SIGBREAKF_CTRL_C | (1L << mp->mp_SigBit); if (Broker=CxBroker(&nb, &error)) { /* Now we start to attach things to the broker. */ /* First of all the two filters which intercept */ /* the Amiga+Tab and Amiga+Shift+Tab strokes */ if (AttachFilter(win_hotkey, WINTAB) && AttachFilter(scr_hotkey, SCRTAB)) { /* Add the special filter which intercept */ /* the release of the Amiga key. To do this, */ /* first build a normal filetr with a dummy hotkey, */ /* then replace the dummy hotkey with the actual IX */ CxObj *x = AttachFilter(DUMMY_KEY, UPSTROKE); if (x) { SetFilterIX(x, &win_ix); /* Do the same with the scr upstroke filter, */ /* but only if it's different from the win one */ if (scr_ix.ix_Code != win_ix.ix_Code) { x = AttachFilter(DUMMY_KEY, UPSTROKE); if (x) SetFilterIX(x, &scr_ix); } if (x) { /* If everithing's ok, activate the broker */ ActivateCxObj(Broker, 1); handle(); } else error = ShowError("couldn't create 2nd upstroke filter."); } else error = ShowError("Couldn't create upstroke filter."); } else error = ShowError("couldn't create filters."); /* Delete the broker (all attached objects get freed as well) */ DeleteCxObjAll(Broker); /* Empty the msg queue by replying to all pending msgs */ { struct Message *msg; while (msg=GetMsg(mp)) ReplyMsg(msg); } } else { if (error == CBERR_DUP) /* Not a real error: the prg was started twice */ /* so the second instance tells the first one to quit */ error = 0; else error = ShowError("couldn't create broker."); } DeleteMsgPort(mp); } else error = ShowError("couldn't create message port."); #ifndef FIXEDKEYS NewFreeArgs(&nrda); #endif } else error = ShowError("invalid arguments."); _exit(error); #ifdef _DCC /* Dummy reference (never executed) which forces Dice to import */ /* the WBMsg handling routines. */ void _waitwbmsg(void); _waitwbmsg(); #endif } /// /// AttachFilter() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ ** This routine adds a filter to an existing broker. ** Each filter is triggered by the event ginven in the "key" string. ** The filter in turn has attached a Translator (which "eats" the event ** so that it doesn't propagate to lower-priority Cx's) an a Sender ** (which sends a "event" msg to the msg port "mp"). ** ** It returns the address of the created filter. */ CxObj *AttachFilter(STRPTR key, ULONG event) { CxObj *filter, *sender, *translator; if (filter=CxFilter(key)) { AttachCxObj(Broker, filter); if (sender=CxSender(mp, event)) { AttachCxObj(filter, sender); if (translator=CxTranslate(NULL)) { AttachCxObj(filter, translator); if (!CxObjError(filter)) return filter; } } } return NULL; } /// /// handle() /* ŻŻŻŻŻŻŻŻ ** This is the kernel of the entire program. It basically consists ** of a classic Wait-GetMsg-ReplyMsg loop where the appropriate ** routines are called according to the type of message received. */ void handle(void) { int error = 0; /* Main loop: the routines inside will set error to 1 */ /* if the main loop is to be quitted. */ while (!error) { CxMsg *msg; ULONG signal = Wait(Sigbits); /* We received a signal: either one or more msgs have arrived */ /* or someone has sent us a CTRL-C */ while (msg = (CxMsg*) GetMsg(mp)) { /* We received a msg, so keep its type & ID */ /* and then reply to it */ ULONG id = CxMsgID(msg); ULONG type = CxMsgType(msg); ReplyMsg((struct Message *) msg); /* Take the appropriate action */ /* Note that this part could be more optimized */ switch (type) { case CXM_IEVENT: /* One of the hotkeys... */ switch(id) { case SCRTAB: if (Win_open) { close_window(); dispose_list(); } if (!Scr_open) { if (create_scrlist()) Scr_open = open_window(); } if (Scr_open) { display_next(); } break; case WINTAB: if (Scr_open) { close_window(); dispose_list(); } if (!Win_open) { if (create_winlist()) Win_open = open_window(); } if (Win_open) { display_next(); } break; case UPSTROKE: /* If alright, raise the selected item */ /* then close the window */ if (Scr_open) { raise_scr(); close_window(); dispose_list(); } if (Win_open) { raise_win(); close_window(); dispose_list(); } break; } break; case CXM_COMMAND: /* Standard CX stuff... */ switch (id) { case CXCMD_DISABLE: ActivateCxObj(Broker, 0); break; case CXCMD_ENABLE: ActivateCxObj(Broker, 1); break; case CXCMD_KILL: case CXCMD_UNIQUE: /* Quit... */ error = 1; break; } break; } } /* If we received a CTRL-C, quit */ if (signal & SIGBREAKF_CTRL_C) error = 1; } /* Before quitting, close open windows and free allocated resources */ if (Win_open || Scr_open) { close_window(); dispose_list(); } } /// /// create_winlist() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ ** Examine the list of open windows and build a chain of "items" ** or "records". The first win in the chain must be the Special One ** according to the OMM (Olivier's Memory Method). The last win ** should be the currently active one. ** Windows whithout printable title are skipped. */ int create_winlist(void) { Active_item = First_item = Special_item = Displayed_item = NULL; /* Examine all the wins, starting with the currently active one */ struct Window *aw = CURRENTLY_ACTIVE_WIN; struct Window *w = aw; do { /* Has printable title? */ STRPTR title = w->Title; if ( title && (*title != '\0') ) { /* Yes: allocate space for a new record and fill it */ struct record *item = xalloc(sizeof(struct record)); if (item == NULL) break; item->len = copy_title(item->title, title); item->id = w; /* Is this win the Special One according to OMM? */ if (w == Last_active_win && w != aw) { /* Yes: keep it apart */ Special_item = item; } else { /* No: add it to the head of the chain */ item->next = First_item; First_item = item; } /* Is this win the currently active one? */ if (w == aw) Active_item = item; } /* Examine the next win. If we reach the end of the list */ /* restart from the first win of this screen */ w = w->NextWindow; if (w == NULL) w = FIRST_AVAILABLE_WIN; /* Stop when we cycled thru all available wins and we came back to */ /* the firstly examined one, which happens to be the currently active one */ } while (w != aw); /* If a Special Win was found, add it to the head of the chain, so that */ /* it will appear first (making Olivier happy) */ if (Special_item) { Special_item->next = First_item; First_item = Special_item; } /* Now, if we found only one win and that win is already selected */ /* (the currently active one) then Altabber is obviously of no use, */ /* so don't even open the window: discard the chain and return an error */ if (First_item == Active_item) { dispose_list(); return 0; /* means: couldn't correctly initiate the wins chain */ } return 1; /* Ok */ } /// /// create_scrlist() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ ** Examine the list of open screens and build a chain of "items" ** or "records". The first scr in the chain must be the Special One ** according to the OMM (Olivier's Memory Method). The last scr ** should be the currently active one. ** ** Please see the comments in the create_winlist() function. */ int create_scrlist(void) { Active_item = First_item = Special_item = Displayed_item = NULL; struct Screen *as = CURRENTLY_ACTIVE_SCR; struct Screen *s = as; do { STRPTR title = s->Title; /* Or DefaultTitle ?!? */ /* Screen are never skipped, regardless of their title */ //if ( title && (*title != '\0') ) { struct record *item = xalloc(sizeof(struct record)); if (item == NULL) break; /* If the scr has no printable title, a default one */ /* is provided */ if ( title && (*title != '\0') ) item->len = copy_title(item->title, title); else item->len = copy_title(item->title, ""); item->id = s; if (s == Last_active_scr && s != as) { Special_item = item; } else { item->next = First_item; First_item = item; } if (s == as) Active_item = item; } s = s->NextScreen; if (s == NULL) s = FIRST_AVAILABLE_SCR; } while (s != as); if (Special_item) { Special_item->next = First_item; First_item = Special_item; } if (First_item == Active_item) { dispose_list(); return 0; } return 1; /* Ok */ } /// /// open_window() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻ */ int open_window(void) { struct Screen *as = IntuitionBase->ActiveScreen; struct RastPort *tmp_rp = &as->RastPort; /* Get the screen font's height */ GG.font_height = as->Font->ta_YSize; /* Try to determine the max window width required */ /* by the titles in the chain */ GG.max_width = 0; struct record *item = First_item; while (item) { int width = TextLength(tmp_rp, item->title, item->len); item->width = width; if (width > GG.max_width) GG.max_width = width; item = item->next; } /* Leave a margin of twice the font's height on both sides */ /* and above and below the print-area */ int winw = GG.max_width + 4 * GG.font_height; int winh = 5 * GG.font_height; /* Open the window on the currently active screen */ /* with the calculated size and without any title */ struct Window *win = OpenWindowTags(NULL, WA_Left, (as->Width - winw)/2, WA_Top, (as->Height - winh)/2, WA_Width, winw, WA_Height, winh, WA_CustomScreen,(ULONG) as, TAG_END, 0 ); if (win == NULL) return 0; /* Fill in the Global Gfx variables */ GG.win = win; GG.rp = win->RPort; /* These are the coordinates of the print-area */ /* inside the window */ GG.left_corner = 2 * GG.font_height; GG.top_corner = 2 * GG.font_height; /* Set the same font as the screen's one */ GG.font = OpenFont(as->Font); if (GG.font) { SetFont(GG.rp, GG.font); } GG.font_baseline = GG.font->tf_Baseline; /* Be sure we are writing in JAM1 mode */ SetABPenDrMd(GG.rp, 1, 0, JAM1); return 1; /* Ok */ } /// /// display_next() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ */ void display_next(void) { /* Select the next item */ if (Displayed_item) Displayed_item = Displayed_item->next; /* If at the end of the chain (or if called for the first time) */ /* restart from the head of the chain */ if (Displayed_item == NULL) Displayed_item = First_item; /* Clear the print-area */ EraseRect(GG.rp, GG.left_corner, GG.top_corner, GG.left_corner + GG.max_width, GG.top_corner + GG.font_height ); /* Center the title in the print-area */ Move(GG.rp, GG.left_corner + (GG.max_width - Displayed_item->width)/2, GG.top_corner + GG.font_baseline ); /* Use color2 (white) if we are showing the currently active win/scr, */ /* color1 (black) otherwise */ if (Displayed_item == Active_item) SetAPen(GG.rp, *Opt.color2); else SetAPen(GG.rp, *Opt.color1); Text(GG.rp, Displayed_item->title, Displayed_item->len); } /// /// close_window() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ ** Close the window and the font (if opened), ** then free all the items of the chain. */ void close_window(void) { if (GG.font) { CloseFont(GG.font); GG.font = NULL; } if (GG.win) { CloseWindow(GG.win); GG.win = NULL; } Scr_open = Win_open = 0; } /// /// dispose_list() /* ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ ** This routine frees all items of the chain. */ void dispose_list(void) { struct record *item = First_item; while(item) { struct record *next = item->next; xfree(item); item = next; } } /// /// raise_win() /* ŻŻŻŻŻŻŻŻŻŻŻ ** Bring the selected win to the foreground, but only if it's not ** the currently active one. */ void raise_win(void) { if (Displayed_item && Displayed_item != Active_item) { Last_active_win = CURRENTLY_ACTIVE_WIN; /* OMM! */ WindowToFront(Displayed_item->id); ActivateWindow(Displayed_item->id); } } /// /// raise_scr() /* ŻŻŻŻŻŻŻŻŻŻŻ ** Bring the selected scr to the foreground, but only if it's not ** the currently active one. If the AUTOACTIVE flag is on, then ** activate the first window of the newly raised screen. */ void raise_scr(void) { if (Displayed_item && Displayed_item != Active_item) { Last_active_scr = CURRENTLY_ACTIVE_SCR; /* OMM! */ ScreenToFront(Displayed_item->id); if (Opt.aa) /* Not yet fully functional! */ ActivateWindow(FIRST_AVAILABLE_WIN); } } /// /// copy_title() /* ŻŻŻŻŻŻŻŻŻŻŻŻ ** Same as strcpy(), but ignores multiple spaces and copies ** only up to MAX_TITLE_LEN-1 chars (including trailing '\0'). ** ** Returns the number of chars copied, same as strlen(dst). */ int copy_title(STRPTR dst, STRPTR src) { int i = 0; while ( *src && i < MAX_TITLE_LEN-1 ) { if (*src == ' ') { while (*src == ' ') ++src; if (i) dst[i++] = ' '; } else { dst[i++] = *(src++); } } dst[i] = '\0'; return i; } /// /// ShowError() /* ŻŻŻŻŻŻŻŻŻŻŻ ** Prints an error message in a requester. */ int ShowError(STRPTR err) { struct EasyStruct errmsg = { sizeof(struct EasyStruct), 0, "Altabber", "An error has occurred:\n%s\nQuitting.", "Ok" }; EasyRequest(NULL, &errmsg, NULL, err); return 10; } /// /// qualcode() /* ŻŻŻŻŻŻŻŻŻŻ ** Every "qualifier" key has a raw code associated, as any other ** "normal" key. Unfortunately there's no way to access to these keys ** as normal keys using the standard "input expression" method. ** So to get the correct raw code we must work it out manually */ int qualcode(STRPTR key) { if (!stricmp(key, "ctrl")) return 0x63; if (!stricmp(key, "control")) return 0x63; if (!stricmp(key, "lalt")) return 0x64; if (!stricmp(key, "ralt")) return 0x65; if (!stricmp(key, "lamiga")) return 0x66; if (!stricmp(key, "ramiga")) return 0x67; if (!stricmp(key, "lcommand")) return 0x66; if (!stricmp(key, "rcommand")) return 0x67; if (!stricmp(key, "lshift")) return 0x60; if (!stricmp(key, "rshift")) return 0x61; if (!stricmp(key, "caps")) return 0x62; if (!stricmp(key, "capslock")) return 0x62; return 0; } ///