/** Start walking the window stack. */ void StartWindowStackWalk(void) { /* Get an image of the window stack. * Here we get the Window IDs rather than client pointers so * clients can be added/removed without disrupting the stack walk. */ ClientNode *np; int layer; int count; /* If we are already walking the stack, just return. */ if(windowStack != NULL) { return; } /* First determine how much space to allocate for windows. */ count = 0; for(layer = LAST_LAYER; layer >= FIRST_LAYER; layer--) { for(np = nodes[layer]; np; np = np->next) { if(ShouldFocus(np, 1)) { ++count; } } } /* If there were no windows to walk, don't even start. */ if(count == 0) { return; } /* Allocate space for the windows. */ windowStack = Allocate(sizeof(Window) * count); /* Copy windows into the array. */ windowStackSize = 0; for(layer = LAST_LAYER; layer >= FIRST_LAYER; layer--) { for(np = nodes[layer]; np; np = np->next) { if(ShouldFocus(np, 1)) { windowStack[windowStackSize++] = np->window; } } } Assert(windowStackSize == count); windowStackCurrent = 0; JXGrabKeyboard(display, rootWindow, False, GrabModeAsync, GrabModeAsync, CurrentTime); RaiseTrays(); walkingWindows = 1; wasMinimized = 0; }
/** Minimize all clients in a group. */ void MinimizeGroup(const TaskEntry *tp) { ClientEntry *cp; for(cp = tp->clients; cp; cp = cp->next) { if(ShouldFocus(cp->client, 1)) { MinimizeClient(cp->client, 0); } } }
/** Move to the next window in the window stack. */ void WalkWindowStack(char forward) { ClientNode *np; if(windowStack != NULL) { int x; if(wasMinimized) { np = FindClientByWindow(windowStack[windowStackCurrent]); if(np) { MinimizeClient(np, 1); } } /* Loop until we either raise a window or go through them all. */ for(x = 0; x < windowStackSize; x++) { /* Move to the next/previous window (wrap if needed). */ if(forward) { windowStackCurrent = (windowStackCurrent + 1) % windowStackSize; } else { if(windowStackCurrent == 0) { windowStackCurrent = windowStackSize; } windowStackCurrent -= 1; } /* Look up the window. */ np = FindClientByWindow(windowStack[windowStackCurrent]); /* Skip this window if it no longer exists or is currently in * a state that doesn't allow focus. */ if(np == NULL || !ShouldFocus(np, 1)) { continue; } /* Show the window. * Only when the walk completes do we update the stacking order. */ RestackClients(); if(np->state.status & STAT_MINIMIZED) { RestoreClient(np, 1); wasMinimized = 1; } else { wasMinimized = 0; } JXRaiseWindow(display, np->parent ? np->parent : np->window); FocusClient(np); break; } } }
/** Determine if there is anything to show for the specified entry. */ char ShouldShowEntry(const TaskEntry *tp) { const ClientEntry *cp; for(cp = tp->clients; cp; cp = cp->next) { if(ShouldFocus(cp->client, 0)) { return 1; } } return 0; }
/** Determine if we should attempt to focus an entry. */ char ShouldFocusEntry(const TaskEntry *tp) { const ClientEntry *cp; for(cp = tp->clients; cp; cp = cp->next) { if(cp->client->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) { if(ShouldFocus(cp->client, 1)) { return 1; } } } return 0; }
/** Run a menu action. */ void RunTaskBarCommand(MenuAction *action, unsigned button) { ClientEntry *cp; if(action->type & MA_GROUP_MASK) { TaskEntry *tp = action->context; for(cp = tp->clients; cp; cp = cp->next) { if(!ShouldFocus(cp->client, 0)) { continue; } switch(action->type & ~MA_GROUP_MASK) { case MA_SENDTO: SetClientDesktop(cp->client, action->value); break; case MA_CLOSE: DeleteClient(cp->client); break; case MA_RESTORE: RestoreClient(cp->client, 0); break; case MA_MINIMIZE: MinimizeClient(cp->client, 0); break; default: break; } } } else if(action->type == MA_EXECUTE) { if(button == Button3) { Window w; int x, y; GetMousePosition(&x, &y, &w); ShowWindowMenu(action->context, x, y, 0); } else { ClientNode *np = action->context; RestoreClient(np, 1); FocusClient(np); MoveMouse(np->window, np->width / 2, np->height / 2); } } else { RunWindowCommand(action, button); } }
/** Focus the previous client in the task bar. */ void FocusPrevious(void) { TaskEntry *tp; /* Find the current entry. */ for(tp = taskEntries; tp; tp = tp->next) { ClientEntry *cp; for(cp = tp->clients; cp; cp = cp->next) { if(cp->client->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) { if(ShouldFocus(cp->client, 1)) { if(cp->client->state.status & STAT_ACTIVE) { cp = cp->next; goto ClientFound; } } } } } ClientFound: /* Move to the previous group. */ if(tp) { do { tp = tp->prev; } while(tp && !ShouldFocusEntry(tp)); } if(!tp) { /* Wrap around; start at the end. */ for(tp = taskEntriesTail; tp; tp = tp->prev) { if(ShouldFocusEntry(tp)) { break; } } } /* Focus the group if one exists. */ if(tp) { FocusGroup(tp); } }
/** Move to the next window in the window stack. */ void WalkWindowStack(int forward) { ClientNode *np; int x; if(windowStack != NULL) { /* Loop until we either raise a window or go through them all. */ for(x = 0; x < windowStackSize; x++) { /* Move to the next/previous window (wrap if needed). */ if(forward) { windowStackCurrent = (windowStackCurrent + 1) % windowStackSize; } else { if(windowStackCurrent == 0) { windowStackCurrent = windowStackSize; } --windowStackCurrent; } /* Look up the window. */ np = FindClientByWindow(windowStack[windowStackCurrent]); /* Skip this window if it no longer exists or is currently in * a state that doesn't allow focus. */ if(np == NULL || !ShouldFocus(np)) { continue; } /* Focus the window. We only raise the client when the * stack walk completes. */ FocusClient(np); break; } } }
/** Draw a specific task bar. */ void Render(const TaskBarType *bp) { TaskEntry *tp; char *displayName; ButtonNode button; int x, y; if(JUNLIKELY(shouldExit)) { return; } ClearTrayDrawable(bp->cp); if(!taskEntries) { UpdateSpecificTray(bp->cp->tray, bp->cp); return; } ResetButton(&button, bp->cp->pixmap); button.border = settings.trayDecorations == DECO_MOTIF; button.font = FONT_TASKLIST; button.height = bp->itemHeight; button.width = bp->itemWidth; button.text = NULL; x = 0; y = 0; for(tp = taskEntries; tp; tp = tp->next) { if(!ShouldShowEntry(tp)) { continue; } /* Check for an active or urgent window and count clients. */ ClientEntry *cp; unsigned clientCount = 0; button.type = BUTTON_TASK; for(cp = tp->clients; cp; cp = cp->next) { if(ShouldFocus(cp->client, 0)) { const char flash = (cp->client->state.status & STAT_FLASH) != 0; const char active = (cp->client->state.status & STAT_ACTIVE) && IsClientOnCurrentDesktop(cp->client); if(flash || active) { if(button.type == BUTTON_TASK) { button.type = BUTTON_TASK_ACTIVE; } else { button.type = BUTTON_TASK; } } clientCount += 1; } } button.x = x; button.y = y; if(!tp->clients->client->icon) { button.icon = GetDefaultIcon(); } else { button.icon = tp->clients->client->icon; } displayName = NULL; if(tp->clients->client->className && settings.groupTasks) { if(clientCount != 1) { const size_t len = strlen(tp->clients->client->className) + 16; displayName = Allocate(len); snprintf(displayName, len, "%s (%u)", tp->clients->client->className, clientCount); button.text = displayName; } else { button.text = tp->clients->client->className; } } else { button.text = tp->clients->client->name; } DrawButton(&button); if(displayName) { Release(displayName); } if(bp->layout == LAYOUT_HORIZONTAL) { x += bp->itemWidth; } else { y += bp->itemHeight; } } UpdateSpecificTray(bp->cp->tray, bp->cp); }
/** Show the menu associated with a task list item. */ void ShowClientList(TaskBarType *bar, TaskEntry *tp) { Menu *menu; MenuItem *item; ClientEntry *cp; const ScreenType *sp; int x, y; Window w; if(settings.groupTasks) { menu = Allocate(sizeof(Menu)); menu->itemHeight = 0; menu->items = NULL; menu->label = NULL; item = CreateMenuItem(MENU_ITEM_NORMAL); item->name = CopyString(_("Close")); item->action.type = MA_CLOSE | MA_GROUP_MASK; item->action.context = tp; item->next = menu->items; menu->items = item; item = CreateMenuItem(MENU_ITEM_NORMAL); item->name = CopyString(_("Minimize")); item->action.type = MA_MINIMIZE | MA_GROUP_MASK; item->action.context = tp; item->next = menu->items; menu->items = item; item = CreateMenuItem(MENU_ITEM_NORMAL); item->name = CopyString(_("Restore")); item->action.type = MA_RESTORE | MA_GROUP_MASK; item->action.context = tp; item->next = menu->items; menu->items = item; item = CreateMenuItem(MENU_ITEM_SUBMENU); item->name = CopyString(_("Send To")); item->action.type = MA_SENDTO_MENU | MA_GROUP_MASK; item->action.context = tp; item->next = menu->items; menu->items = item; /* Load the separator and group actions. */ item = CreateMenuItem(MENU_ITEM_SEPARATOR); item->next = menu->items; menu->items = item; /* Load the clients into the menu. */ for(cp = tp->clients; cp; cp = cp->next) { if(!ShouldFocus(cp->client, 0)) { continue; } item = CreateMenuItem(MENU_ITEM_NORMAL); if(cp->client->state.status & STAT_MINIMIZED) { size_t len = 0; if(cp->client->name) { len = strlen(cp->client->name); } item->name = Allocate(len + 3); item->name[0] = '['; memcpy(&item->name[1], cp->client->name, len); item->name[len + 1] = ']'; item->name[len + 2] = 0; } else { item->name = CopyString(cp->client->name); } item->icon = cp->client->icon ? cp->client->icon : GetDefaultIcon(); item->action.type = MA_EXECUTE; item->action.context = cp->client; item->next = menu->items; menu->items = item; } } else { /* Not grouping clients. */ menu = CreateWindowMenu(tp->clients->client); } /* Initialize and position the menu. */ InitializeMenu(menu); sp = GetCurrentScreen(bar->cp->screenx, bar->cp->screeny); GetMousePosition(&x, &y, &w); if(bar->layout == LAYOUT_HORIZONTAL) { if(bar->cp->screeny + bar->cp->height / 2 < sp->y + sp->height / 2) { /* Bottom of the screen: menus go up. */ y = bar->cp->screeny + bar->cp->height; } else { /* Top of the screen: menus go down. */ y = bar->cp->screeny - menu->height; } x -= menu->width / 2; x = Max(x, sp->x); } else { if(bar->cp->screenx + bar->cp->width / 2 < sp->x + sp->width / 2) { /* Left side: menus go right. */ x = bar->cp->screenx + bar->cp->width; } else { /* Right side: menus go left. */ x = bar->cp->screenx - menu->width; } y -= menu->height / 2; y = Max(y, sp->y); } ShowMenu(menu, RunTaskBarCommand, x, y, 0); DestroyMenu(menu); }
/** Raise all clients in a group and focus the top-most. */ void FocusGroup(const TaskEntry *tp) { const char *className = tp->clients->client->className; ClientNode **toRestore; const ClientEntry *cp; unsigned restoreCount; int i; char shouldSwitch; /* If there is no class name, then there will only be one client. */ if(!className || !settings.groupTasks) { if(!(tp->clients->client->state.status & STAT_STICKY)) { ChangeDesktop(tp->clients->client->state.desktop); } RestoreClient(tp->clients->client, 1); FocusClient(tp->clients->client); return; } /* If there is a client in the group on this desktop, * then we remain on the same desktop. */ shouldSwitch = 1; for(cp = tp->clients; cp; cp = cp->next) { if(IsClientOnCurrentDesktop(cp->client)) { shouldSwitch = 0; break; } } /* Switch to the desktop of the top-most client in the group. */ if(shouldSwitch) { for(i = 0; i < LAYER_COUNT; i++) { ClientNode *np; for(np = nodes[i]; np; np = np->next) { if(np->className && !strcmp(np->className, className)) { if(ShouldFocus(np, 0)) { if(!(np->state.status & STAT_STICKY)) { ChangeDesktop(np->state.desktop); } break; } } } } } /* Build up the list of clients to restore in correct order. */ toRestore = AllocateStack(sizeof(ClientNode*) * clientCount); restoreCount = 0; for(i = 0; i < LAYER_COUNT; i++) { ClientNode *np; for(np = nodes[i]; np; np = np->next) { if(!ShouldFocus(np, 1)) { continue; } if(np->className && !strcmp(np->className, className)) { toRestore[restoreCount] = np; restoreCount += 1; } } } Assert(restoreCount <= clientCount); for(i = restoreCount - 1; i >= 0; i--) { RestoreClient(toRestore[i], 1); } for(i = 0; i < restoreCount; i++) { if(toRestore[i]->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) { FocusClient(toRestore[i]); break; } } ReleaseStack(toRestore); }
/** Process a task list button event. */ void ProcessTaskButtonEvent(TrayComponentType *cp, int x, int y, int mask) { TaskBarType *bar = (TaskBarType*)cp->object; TaskEntry *entry = GetEntry(bar, x, y); if(entry) { ClientEntry *cp; ClientNode *focused = NULL; char onTop = 0; char hasActive = 0; switch(mask) { case Button1: /* Raise or minimize items in this group. */ for(cp = entry->clients; cp; cp = cp->next) { int layer; char foundTop = 0; if(cp->client->state.status & STAT_MINIMIZED) { continue; } else if(!ShouldFocus(cp->client, 0)) { continue; } for(layer = LAST_LAYER; layer >= FIRST_LAYER; layer--) { ClientNode *np; for(np = nodes[layer]; np; np = np->next) { if(np->state.status & STAT_MINIMIZED) { continue; } else if(!ShouldFocus(np, 0)) { continue; } if(np == cp->client) { const char isActive = (np->state.status & STAT_ACTIVE) && IsClientOnCurrentDesktop(np); onTop = onTop || !foundTop; if(isActive) { focused = np; } if(!(cp->client->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS)) || isActive) { hasActive = 1; } } if(hasActive && onTop) { goto FoundActiveAndTop; } foundTop = 1; } } } FoundActiveAndTop: if(hasActive && onTop) { ClientNode *nextClient = NULL; int i; /* Try to find a client on a different desktop. */ for(i = 0; i < settings.desktopCount - 1; i++) { const int target = (currentDesktop + i + 1) % settings.desktopCount; for(cp = entry->clients; cp; cp = cp->next) { ClientNode *np = cp->client; if(!ShouldFocus(np, 0)) { continue; } else if(np->state.status & STAT_STICKY) { continue; } else if(np->state.desktop == target) { if(!nextClient || np->state.status & STAT_ACTIVE) { nextClient = np; } } } if(nextClient) { break; } } /* Focus the next client or minimize the current group. */ if(nextClient) { ChangeDesktop(nextClient->state.desktop); RestoreClient(nextClient, 1); } else { MinimizeGroup(entry); } } else { FocusGroup(entry); if(focused) { FocusClient(focused); } } break; case Button3: ShowClientList(bar, entry); break; case Button4: FocusPrevious(); break; case Button5: FocusNext(); break; default: break; } } }