/** * * Beryl tile plugin * * tile.c * * Copyright (c) 2006 Atie H. * Copyright (c) 2006 Michal Fojtik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * TODO * - change vertical and horizontal tiling to similar behavior as Left * - fix bugs * - make vertical and horizontal maximization be saved when tiling * **/ #include #include #include #include #include #include #include #include #define GET_TILE_DISPLAY(d) ((TileDisplay *) (d)->privates[displayPrivateIndex].ptr) #define TILE_DISPLAY(d) TileDisplay *td = GET_TILE_DISPLAY (d) #define GET_TILE_SCREEN(s, td) ((TileScreen *) (s)->privates[(td)->screenPrivateIndex].ptr) #define TILE_SCREEN(s) TileScreen *ts = GET_TILE_SCREEN (s, GET_TILE_DISPLAY (s->display)) #define GET_TILE_WINDOW(w, ts) ((TileWindow *) (w)->privates[(ts)->windowPrivateIndex].ptr) #define TILE_WINDOW(w) TileWindow *tw = GET_TILE_WINDOW (w, GET_TILE_SCREEN (w->screen, GET_TILE_DISPLAY (w->screen->display))) #define THIS_VIEWPORT(s) (ts->viewports[(s)->x]) #define TILE_DISPLAY_OPTION_VERTICALLY 0 #define TILE_DISPLAY_OPTION_HORIZONTALLY 1 #define TILE_DISPLAY_OPTION_TILE 2 #define TILE_DISPLAY_OPTION_CASCADE 3 #define TILE_DISPLAY_OPTION_RESTORE 4 #define TILE_DISPLAY_OPTION_TOGGLE 5 #define TILE_DISPLAY_OPTION_EXCLUDE_LIST 6 #define TILE_DISPLAY_OPTION_JOIN 7 #define TILE_DISPLAY_OPTION_DELTA 8 #define TILE_DISPLAY_OPTION_LEFT_OCCUPANCY 9 #define TILE_DISPLAY_OPTION_ANIMATE 10 #define TILE_DISPLAY_OPTION_ANIMATION_DURATION 11 #define TILE_DISPLAY_OPTION_ANIMATION_TYPE 12 #define TILE_DISPLAY_OPTION_TOGGLE_TYPE 13 #define TILE_DISPLAY_OPTION_NUM 14 #define TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_KEY "q" #define TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_MOD CompSuperMask|ShiftMask #define TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_KEY "w" #define TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_MOD CompSuperMask|ShiftMask #define TILE_TILE_DISPLAY_OPTION_INITIATE_KEY "a" #define TILE_TILE_DISPLAY_OPTION_INITIATE_MOD CompSuperMask|ShiftMask #define TILE_CASCADE_DISPLAY_OPTION_INITIATE_KEY "s" #define TILE_CASCADE_DISPLAY_OPTION_INITIATE_MOD CompSuperMask|ShiftMask #define TILE_RESTORE_DISPLAY_OPTION_INITIATE_KEY "z" #define TILE_RESTORE_DISPLAY_OPTION_INITIATE_MOD CompSuperMask|ShiftMask #define TILE_TOGGLE_DISPLAY_OPTION_INITIATE_KEY "x" #define TILE_TOGGLE_DISPLAY_OPTION_INITIATE_MOD CompSuperMask|ShiftMask #define TILE_DEFAULT_JOIN FALSE #define TILE_DEFAULT_ANIMATE FALSE #define TILE_DEFAULT_ANIMATION fade #define TILE_DEFAULT_TOGGLE_TYPE tile #define TILE_DELTA_DEFAULT 35 #define TILE_DELTA_MIN 0 #define TILE_DELTA_MAX 250 #define TILE_LEFT_OCCUPANCY_DEFAULT 60 #define TILE_LEFT_OCCUPANCY_MIN 20 #define TILE_LEFT_OCCUPANCY_MAX 80 #define TILE_ANIMATION_DURATION_DEFAULT 1000 #define TILE_ANIMATION_DURATION_MIN 200 #define TILE_ANIMATION_DURATION_MAX 5000 // Minimal width and height under which the window cant be resized #define MINIMUM_WIDTH 25 #define MINIMUM_HEIGHT 10 #define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption)) static int displayPrivateIndex = 0; #define NUM_ANIMATIONS 5 static char *animationTypeString[] = { N_("Filled outline"), N_("Slide"), N_("Zoom"), N_("Drop from top"), N_("Fade") }; typedef enum { outline = 0, slide, zoom, drop, fade } animationType; // number of tile types for dropdown, without 'none' #define TILE_TYPE_NUM 5 static char *tileTypeString[] = { N_("Tile"), N_("Left"), N_("Tile Vertically"), N_("Tile Horizontally"), N_("Cascade") }; typedef enum { tile = 0, left, vert, horz, cascade, none } tileType; typedef struct _TileDisplay { tileType currentToggleType; // toggle tiling type animationType currentAnimationType; int animationDuration; // duration of animation int screenPrivateIndex; CompOption opt[TILE_DISPLAY_OPTION_NUM]; } TileDisplay; typedef struct _TileViewport { tileType currentTileType; // which kind of tiling is applied to windows on viewport int tiledCount; // number of windows CompWindow *firstTiled; // pointer to first tiled window } TileViewport; typedef struct _TileScreen { TileViewport *viewports; Bool isResizing; // whether there any windows on screen being resized int oneDuration; // duration of animation for one window int msResizing; // number of ms elapsed from start of resizing animation PaintWindowProc paintWindow; WindowResizeNotifyProc windowResizeNotify; PreparePaintScreenProc preparePaintScreen; DonePaintScreenProc donePaintScreen; PaintScreenProc paintScreen; int decoWidth, decoHeight; // decoration width and height int windowPrivateIndex; } TileScreen; typedef struct _TileWindow { int isOtherAnimationAtom; // atom to check whether animation plugin is animating the window CompWindow *next; // next window for tiling CompWindow *prev; // previous window for tiling Bool resizedAlready; // whether the animation already resized the window // coords used in animation, before last resize int previousX; int previousY; int previousWidth; int previousHeight; // coords used when configuring window in middle of animation int futureX; int futureY; int futureWidth; int futureHeight; Bool isResizing; // tells if the window is being resized GLushort outlineColor[3]; int prevState; // coords before whole tiling int originalX; int originalY; int originalWidth; int originalHeight; } TileWindow; static Bool placeWindow(CompWindow *w, int x, int y, int width, int height); static Bool isTileWindow(CompWindow *w); static Bool setWindowFutureSize(CompWindow *w); int current = 0; // window painting function, draws animation static Bool tilePaintWindow(CompWindow * w, const WindowPaintAttrib * attrib, Region region, unsigned int mask) { CompScreen *s = w->screen; Bool status; TILE_SCREEN(s); TILE_WINDOW(w); TILE_DISPLAY(s->display); if(tw->isResizing) mask |= PAINT_WINDOW_NO_CORE_INSTANCE_MASK; if(tw->isResizing && td->currentAnimationType != outline) // on window texture animation { WindowPaintAttrib sAttrib = *attrib; glPushMatrix(); glLoadIdentity(); prepareXCoords(s, s->currentOutputDev, -DEFAULT_Z_CAMERA); switch(td->currentAnimationType) { /* Drop animation */ case drop: glRotatef(100.0f/td->animationDuration*ts->msResizing - 100, 0,0,1); (*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK); break; /* Zoom animation */ case zoom: glTranslatef(0,0, -1 + ts->msResizing/(float)td->animationDuration); (*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK); break; /* Slide animation */ case slide: if(ts->msResizing < 0.75*td->animationDuration) sAttrib.opacity = OPAQUE / 2; else sAttrib.opacity = OPAQUE/2 + OPAQUE/2*(ts->msResizing - 0.75*td->animationDuration)/(0.25*td->animationDuration); if(ts->msResizing > current*ts->oneDuration) // windows that have animation finished already { (*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK); } else if(ts->msResizing > (current-1)*ts->oneDuration && ts->msResizing < current*ts->oneDuration) // animation in progress { int thisDur; // ms spent animating this window for(thisDur = ts->msResizing;thisDur > ts->oneDuration;thisDur -= ts->oneDuration) ; if(current%2) glTranslatef(-s->width + s->width * (float)thisDur/ts->oneDuration, 0, 0); else glTranslatef(s->width - s->width * (float)thisDur/ts->oneDuration, 0, 0); (*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK); } break; /* Outline animation */ case outline: break; /* Fade animation */ case fade: // first half of the animation, fade out if(ts->msResizing < 0.40f*td->animationDuration) { sAttrib.opacity = OPAQUE - OPAQUE*ts->msResizing/(0.40f*td->animationDuration); (*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK); } else if(ts->msResizing > 0.40f*td->animationDuration && !tw->resizedAlready) // resize window right after first half setWindowFutureSize(w); else if(ts->msResizing > 0.60f*td->animationDuration) // second half of animation, fade in { sAttrib.opacity = OPAQUE*(ts->msResizing - 0.60f*td->animationDuration)/(0.40f*td->animationDuration); (*s->drawWindow) (w, &sAttrib, region, mask | PAINT_WINDOW_TRANSFORMED_MASK); } break; } current -= 1; glPopMatrix(); } else // paint window as always { UNWRAP(ts, s, paintWindow); status = (*s->paintWindow) (w, attrib, region, mask); WRAP(ts, s, paintWindow, tilePaintWindow); } return status; } static void tilePreparePaintScreen(CompScreen * s, int msSinceLastPaint) { TILE_SCREEN(s); TILE_DISPLAY(s->display); // this probably shouldnt be here... td->animationDuration = td->opt[TILE_DISPLAY_OPTION_ANIMATION_DURATION].value.i; current = THIS_VIEWPORT(s).tiledCount; // add spent time if(ts->isResizing) ts->msResizing += msSinceLastPaint; // Check if the animation hasnt finished yet if(ts->isResizing && ts->msResizing > td->animationDuration) { CompWindow *w = THIS_VIEWPORT(s).firstTiled; while(w) { TILE_WINDOW(w); if(tw->isResizing) tw->isResizing = FALSE; w = tw->next; } ts->isResizing = FALSE; ts->msResizing = 0; } UNWRAP(ts, s, preparePaintScreen); (*s->preparePaintScreen) (s, msSinceLastPaint); WRAP(ts, s, preparePaintScreen, tilePreparePaintScreen); } static void tileDonePaintScreen(CompScreen * s) { TILE_SCREEN(s); // hope this is ok if(ts->isResizing) damageScreen(s); UNWRAP(ts, s, donePaintScreen); (*s->donePaintScreen) (s); WRAP(ts, s, donePaintScreen, tileDonePaintScreen); } static Bool tilePaintScreen(CompScreen * s, const ScreenPaintAttrib * sa, Region region, int output, unsigned int mask) { Bool status; TILE_SCREEN(s); TILE_DISPLAY(s->display); if(ts->isResizing) mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK; UNWRAP(ts, s, paintScreen); status = (*s->paintScreen) (s, sa, region, output, mask); WRAP(ts, s, paintScreen, tilePaintScreen); // Check if animation is enabled, there is resizing on screen and only outline should be drawn if(!td->opt[TILE_DISPLAY_OPTION_ANIMATE].value.b || !ts->isResizing || td->currentAnimationType != outline) return status; glPushMatrix(); glLoadIdentity(); prepareXCoords(s, output, -DEFAULT_Z_CAMERA); glLineWidth(4.0f); CompWindow *w = THIS_VIEWPORT(s).firstTiled; while(w && isTileWindow(w)) { TILE_WINDOW(w); if(tw->isResizing) { // Coordinate = start + speed * elapsedTime // Coordinate = start + (target - start)/interval * elapsedTime // Draw outline int x = ((float)w->attrib.x - (float)tw->previousX)/td->animationDuration * ts->msResizing + tw->previousX; x -= w->input.left; // decoration int y = ((float)w->attrib.y - (float)tw->previousY)/td->animationDuration * ts->msResizing + tw->previousY; y -= w->input.top; // decoration int width = ((float)w->attrib.width - (float)tw->previousWidth)/td->animationDuration * ts->msResizing + tw->previousWidth; width += w->input.left + w->input.right; // decoration int height = ((float)w->attrib.height - (float)tw->previousHeight)/td->animationDuration * ts->msResizing + tw->previousHeight; height += w->input.top + w->input.bottom; //decoration glColor3us(tw->outlineColor[0]*0.66, tw->outlineColor[1]*0.66, tw->outlineColor[2]*0.66); glRecti(x, y + height, x + width, y); glColor3usv(tw->outlineColor); glBegin(GL_LINE_LOOP); glVertex3f(x, y, 0.0f); glVertex3f(x + width, y, 0.0f); glVertex3f(x + width, y + height, 0.0f); glVertex3f(x, y + height, 0.0f); glEnd(); } w = tw->next; } glPopMatrix(); glColor4usv(defaultColor); glLineWidth(1.0f); return status; } // Resize notify used when windows are tiled horizontally or vertically static void tileResizeNotify(CompWindow * w, int dx, int dy, int dwidth, int dheight, Bool preview) { TILE_SCREEN(w->screen); TILE_WINDOW(w); TILE_DISPLAY(w->screen->display); UNWRAP(ts, w->screen, windowResizeNotify); (*w->screen->windowResizeNotify) (w, dx, dy, dwidth, dheight, preview); WRAP(ts, w->screen, windowResizeNotify, tileResizeNotify); if(!tw->resizedAlready) { tw->resizedAlready = True; // window is resized now return; } // Dont do anything if joining is disabled or windows are being resized if(preview || !td->opt[TILE_DISPLAY_OPTION_JOIN].value.b || ts->isResizing) return; if(THIS_VIEWPORT(w->screen).currentTileType == vert) { if(tw->prev) { placeWindow(tw->prev, tw->prev->attrib.x, tw->prev->attrib.y, w->attrib.x - tw->prev->attrib.x - w->input.left - tw->prev->input.right, tw->prev->height); } if(tw->next) { int currentX = w->attrib.x + w->width + w->input.right + tw->next->input.left; placeWindow(tw->next, currentX, tw->next->attrib.y, tw->next->width + tw->next->attrib.x - currentX, tw->next->height); } } else if(THIS_VIEWPORT(w->screen).currentTileType == horz) { if(tw->prev) { placeWindow(tw->prev, tw->prev->attrib.x, tw->prev->attrib.y, tw->prev->width, w->attrib.y - tw->prev->attrib.y - w->input.top - tw->prev->input.bottom); } if(tw->next) { int currentY = w->attrib.y + w->height + w->input.bottom + tw->next->input.top; placeWindow(tw->next, tw->next->attrib.x, currentY, tw->next->width, tw->next->height + tw->next->attrib.y - currentY); } } else if(THIS_VIEWPORT(w->screen).currentTileType == left) { if(!tw->next && tw->prev && dwidth) // last window - on the left { CompWindow *temp = THIS_VIEWPORT(w->screen).firstTiled; while(temp) { TILE_WINDOW(temp); if(!tw->next) break; XRectangle workArea; screenGetOutputDevWorkArea(w->screen, screenGetCurrentOutputDev(w->screen), &workArea); int currentX = workArea.x + w->serverX + w->serverWidth + w->input.right + temp->input.left; placeWindow(temp, currentX, temp->attrib.y, workArea.width - currentX - w->input.right, temp->attrib.height); temp = tw->next; } } else if(tw->next) // windows on the right { XRectangle workArea; screenGetOutputDevWorkArea(w->screen, screenGetCurrentOutputDev(w->screen), &workArea); CompWindow *temp = THIS_VIEWPORT(w->screen).firstTiled; while(temp) { TILE_WINDOW(temp); if(!tw->next) // left window, last window { placeWindow(temp, workArea.x + temp->input.left, temp->attrib.y, w->serverX - w->input.left - temp->input.left - temp->input.right - workArea.x, temp->attrib.height); break; } if(w->id != temp->id) { int x = temp->attrib.x; int y = temp->attrib.y; int width = temp->attrib.width; int height = temp->attrib.height; TileWindow * otw = GET_TILE_WINDOW(w, ts); // tilewindow from the resized window, tw is from temp if(otw->prev && (temp->id == otw->prev->id)) height = w->serverY - temp->attrib.y - w->input.top - temp->input.bottom; else if(otw->next && (temp->id == otw->next->id)) y = w->serverY + w->serverHeight + w->input.bottom + temp->input.top; x = w->serverX; width = workArea.width + workArea.x - w->serverX - w->input.right; placeWindow(temp, x, y, width, height); } temp = tw->next; } } } } static Bool tileInitScreen(CompPlugin * p, CompScreen * s) { TILE_DISPLAY(s->display); TileScreen *ts = (TileScreen *) calloc(1, sizeof(TileScreen)); ts->windowPrivateIndex = allocateWindowPrivateIndex(s); if (ts->windowPrivateIndex < 0) { free(ts); return FALSE; } srand(time(0)); s->privates[td->screenPrivateIndex].ptr = ts; ts->decoWidth = 0; ts->decoHeight = 0; ts->isResizing = FALSE; ts->msResizing = 0; ts->oneDuration = 0; ts->viewports = calloc(s->hsize, sizeof(TileViewport)); int i; for(i=0;ihsize;i++) ts->viewports[i].currentTileType = none; // Wrap plugin functions WRAP(ts, s, paintScreen, tilePaintScreen); WRAP(ts, s, preparePaintScreen, tilePreparePaintScreen); WRAP(ts, s, donePaintScreen, tileDonePaintScreen); WRAP(ts, s, windowResizeNotify, tileResizeNotify); WRAP(ts, s, paintWindow, tilePaintWindow); addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_VERTICALLY].value.action); addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_HORIZONTALLY].value.action); addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TILE].value. action); addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_CASCADE].value.action); addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_RESTORE].value.action); addScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TOGGLE].value.action); return TRUE; } static void tileFiniScreen(CompPlugin * p, CompScreen * s) { TILE_SCREEN(s); TILE_DISPLAY(s->display); freeWindowPrivateIndex(s, ts->windowPrivateIndex); free(ts->viewports); //Restore the original function UNWRAP(ts, s, paintScreen); UNWRAP(ts, s, preparePaintScreen); UNWRAP(ts, s, donePaintScreen); UNWRAP(ts, s, windowResizeNotify); UNWRAP(ts, s, paintWindow); removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_HORIZONTALLY].value.action); removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_VERTICALLY].value.action); removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TILE].value.action); removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_CASCADE].value.action); removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_RESTORE].value.action); removeScreenAction(s, &td->opt[TILE_DISPLAY_OPTION_TOGGLE].value.action); //Free the pointer free(ts); } static Bool tileSetDisplayOption(CompDisplay * display, char *name, CompOptionValue * value) { CompOption *o; int index; TILE_DISPLAY(display); o = compFindOption(td->opt, NUM_OPTIONS(td), name, &index); if (!o) return FALSE; switch (index) { case TILE_DISPLAY_OPTION_VERTICALLY: case TILE_DISPLAY_OPTION_HORIZONTALLY: case TILE_DISPLAY_OPTION_TILE: case TILE_DISPLAY_OPTION_CASCADE: case TILE_DISPLAY_OPTION_RESTORE: case TILE_DISPLAY_OPTION_TOGGLE: if (setDisplayAction(display, o, value)) return TRUE; break; case TILE_DISPLAY_OPTION_ANIMATE: case TILE_DISPLAY_OPTION_JOIN: if (compSetBoolOption(o, value)) return TRUE; break; case TILE_DISPLAY_OPTION_ANIMATION_DURATION: case TILE_DISPLAY_OPTION_DELTA: case TILE_DISPLAY_OPTION_LEFT_OCCUPANCY: if (compSetIntOption(o, value)) return TRUE; break; case TILE_DISPLAY_OPTION_EXCLUDE_LIST: if (compSetOptionList(o, value)) return TRUE; break; case TILE_DISPLAY_OPTION_ANIMATION_TYPE: if (compSetStringOption(o, value)) { int i; for (i = 0; i < NUM_ANIMATIONS; i++) { if (strcmp(o->value.s, animationTypeString[i]) == 0) { td->currentAnimationType = i; return TRUE; } } } break; case TILE_DISPLAY_OPTION_TOGGLE_TYPE: if (compSetStringOption(o, value)) { int i; for (i = 0; i < TILE_TYPE_NUM; i++) { if (strcmp(o->value.s, tileTypeString[i]) == 0) { td->currentToggleType = i; return TRUE; } } } break; default: break; } return FALSE; } // this is resizeConstrainMinMax from resize.c, thanks to David Reveman/Nigel Cunningham static void constrainMinMax(CompWindow * w, int width, int height, int *newWidth, int *newHeight) { const XSizeHints *hints = &w->sizeHints; int min_width = 0; int min_height = 0; int max_width = MAXSHORT; int max_height = MAXSHORT; if ((hints->flags & PBaseSize) && (hints->flags & PMinSize)) { min_width = hints->min_width; min_height = hints->min_height; } else if (hints->flags & PBaseSize) { min_width = hints->base_width; min_height = hints->base_height; } else if (hints->flags & PMinSize) { min_width = hints->min_width; min_height = hints->min_height; } if (hints->flags & PMaxSize) { max_width = hints->max_width; max_height = hints->max_height; } #define CLAMP(v, min, max) ((v) <= (min) ? (min) : (v) >= (max) ? (max) : (v)) /* clamp width and height to min and max values */ width = CLAMP(width, min_width, max_width); height = CLAMP(height, min_height, max_height); #undef CLAMP *newWidth = width; *newHeight = height; } // Moves window to [x,y] and resizes to width x height if no animation or starts animation static Bool placeWindow(CompWindow *w, int x, int y, int width, int height) { // window existence check if(!w) return FALSE; // this checks if the window isnt smaller than minimum size it has defined constrainMinMax(w, width, height, &width, &height); // check if the window isnt already where its going to be if(x == w->attrib.x && y == w->attrib.y && width == w->attrib.width && height == w->attrib.height) return TRUE; TILE_WINDOW(w); TILE_SCREEN(w->screen); TILE_DISPLAY(w->screen->display); // set previous coordinates for animation tw->previousX = w->attrib.x; tw->previousY = w->attrib.y; tw->previousWidth = w->attrib.width; tw->previousHeight = w->attrib.height; // set future coordinates for animation tw->futureX = x; tw->futureY = y; tw->futureWidth = width; tw->futureHeight = height; tw->resizedAlready = False; // window is not resized now if(!td->opt[TILE_DISPLAY_OPTION_ANIMATE].value.b) setWindowFutureSize(w); else { if(td->currentAnimationType != fade) // for now all animations except fade resize window before animation { setWindowFutureSize(w); } // set animation if(td->opt[TILE_DISPLAY_OPTION_ANIMATE].value.b) { ts->isResizing = TRUE; tw->isResizing = TRUE; ts->msResizing = 0; ts->oneDuration = td->animationDuration/THIS_VIEWPORT(w->screen).tiledCount; } } return TRUE; } static Bool setWindowFutureSize(CompWindow *w) { TILE_WINDOW(w); TILE_SCREEN(w->screen); int x = tw->futureX; int y = tw->futureY; int width = tw->futureWidth; int height = tw->futureHeight; XWindowChanges xwc; xwc.x = x; xwc.y = y; xwc.width = width; xwc.height = height; if(THIS_VIEWPORT(w->screen).currentTileType == none) { maximizeWindow(w, tw->prevState); } else { maximizeWindow(w, 0); } // FIXME // restack window where it should be, under next window because maximization/restoration breaks it somehow if(tw->prevState&MAXIMIZE_STATE && tw->next) restackWindowBelow(w, tw->next); // if after restoring the window is maximized, then restore "restore data" :P if((w->state & MAXIMIZE_STATE) && THIS_VIEWPORT(w->screen).currentTileType == none) { xwc.x = x; xwc.y = y; xwc.width = width; xwc.height = height; // restore "restore data" saveVertRestoreData(w, &xwc); saveHorzRestoreData(w, &xwc); } else configureXWindow (w, CWHeight | CWWidth | CWY | CWX, &xwc); return TRUE; } // Heavily inspired by windowIs3D and isSDWindow, returns TRUE if the window is usable for tiling static Bool isTileWindow(CompWindow * w) { TILE_DISPLAY(w->screen->display); // Exclude windows from exclusion list CompOption *o = &td->opt[TILE_DISPLAY_OPTION_EXCLUDE_LIST]; int i; for (i = 0; i < o->value.list.nValue; i++) { if (w->resClass && (strcmp(o->value.list.value[i].s, w->resClass) == 0)) return FALSE; } if (w->attrib.override_redirect) return FALSE; if (w->state & CompWindowStateOffscreenMask) return FALSE; if (w->wmType & (CompWindowTypeDockMask | CompWindowTypeDesktopMask)) return FALSE; if (w->state & CompWindowStateSkipPagerMask) return FALSE; // Normal window if ((w->type & CompWindowTypeNormalMask)==CompWindowTypeNormalMask && !w->minimized && w->placed) return TRUE; return FALSE; } // Finds windows on desktop that are to be tiled static Bool loadTiledWindows(CompScreen *s) { TILE_SCREEN(s); int count = 0; int decoHeight = 0, decoWidth = 0; CompWindow *first = 0, *previous = 0; CompWindow *w; if(THIS_VIEWPORT(s).currentTileType != none) for (w = s->windows; w; w = w->next) { int x,y; defaultViewportForWindow(w,&x,&y); if(isTileWindow(w) && s->x == x) { if(previous) { TILE_WINDOW(previous); tw->next = w; } TILE_WINDOW(w); if(!first) first = w; tw->prev = previous; previous = w; tw->next = 0; count++; decoHeight = w->input.top + w->input.bottom; decoWidth = w->input.left + w->input.right; } } else // when reloading windows for restoration, in case window order was changed since last tiling for (w = s->windows; w; w = w->next) { TILE_WINDOW(w); int x,y; defaultViewportForWindow(w,&x,&y); if((tw->originalWidth || tw->originalHeight) && s->x == x) { // This fixes problem with window closing animation running while restoring window, causing beryl crash if(IPCS_GetBool(IPCS_OBJECT(w), tw->isOtherAnimationAtom)) continue; if(previous) { TILE_WINDOW(previous); tw->next = w; } if(!first) first = w; tw->prev = previous; previous = w; tw->next = 0; count++; decoHeight = w->input.top + w->input.bottom; decoWidth = w->input.left + w->input.right; } } THIS_VIEWPORT(s).firstTiled = first; THIS_VIEWPORT(s).tiledCount = count; ts->decoHeight = decoHeight; ts->decoWidth = decoWidth; return TRUE; } // save window coordinates to use for restore static Bool saveCoords(CompWindow *w) { TILE_WINDOW(w); // if the window was maximized save "restore data" for later restoring if(w->state&MAXIMIZE_STATE) { XWindowChanges xwc; restoreVertRestoreData(w, &xwc); restoreHorzRestoreData(w, &xwc); tw->originalX = xwc.x; tw->originalY = xwc.y; tw->originalWidth = xwc.width; tw->originalHeight = xwc.height; } else { tw->originalX = w->serverX; tw->originalY = w->serverY; tw->originalWidth = w->serverWidth; tw->originalHeight = w->serverHeight; } // save state tw->prevState = w->state; return TRUE; } // Applies tiling/restoring static Bool applyTiling(CompScreen *s) { TILE_SCREEN(s); TILE_DISPLAY(s->display); if(ts->isResizing) return FALSE; loadTiledWindows(s); if(THIS_VIEWPORT(s).tiledCount>1) { XRectangle workArea; screenGetOutputDevWorkArea(s, screenGetCurrentOutputDev(s), &workArea); CompWindow *w = THIS_VIEWPORT(s).firstTiled; int i = 0; switch(THIS_VIEWPORT(s).currentTileType) { /* Tile into grid */ case tile : { int numWidth = ceil(sqrt(THIS_VIEWPORT(s).tiledCount)); int numHeight = ceil((float)THIS_VIEWPORT(s).tiledCount/numWidth); int height = (workArea.height - ts->decoHeight*numHeight)/numHeight; int width = (workArea.width - ts->decoWidth*numWidth)/numWidth; int currentX = w->input.left + workArea.x; int currentY = w->input.top + workArea.y; while(w) { TILE_WINDOW(w); if(!tw->originalWidth || !tw->originalHeight) saveCoords(w); placeWindow(w, currentX, currentY, width, height); i++; if(i%numWidth==0) { currentX = w->input.left + workArea.x; currentY += height + ts->decoHeight; } else currentX += width + ts->decoWidth; w = tw->next; } } break; /* Tile left */ case left: { int x = 0, y = 0, wid = 0, hei = 0; int height = (workArea.height - ts->decoHeight*(THIS_VIEWPORT(s).tiledCount-1))/(THIS_VIEWPORT(s).tiledCount-1); int occupancy = td->opt[TILE_DISPLAY_OPTION_LEFT_OCCUPANCY].value.i; while(w) { TILE_WINDOW(w); if (!tw->next) // this is the last window in the list - the active/topmost window { x = workArea.x + w->input.left; y = workArea.y + w->input.top; wid = workArea.width * occupancy / 100; hei = workArea.height - w->input.top - w->input.bottom; } else { x = workArea.x + (w->input.left * 2) + w->input.right + (workArea.width * occupancy / 100); y = workArea.y + w->input.top*(i+1) + w->input.bottom*(i) + height*(i); wid = (workArea.width * (100 - occupancy) / 100) - ((w->input.left + w->input.right) * 2); hei = height; } if(!tw->originalWidth || !tw->originalHeight) saveCoords(w); placeWindow(w, x, y, wid, hei); i++; w = tw->next; } } break; /* Tile vertically */ case vert: { int width = (workArea.width - ts->decoWidth*THIS_VIEWPORT(s).tiledCount)/THIS_VIEWPORT(s).tiledCount; int height = workArea.height - w->input.top - w->input.bottom; int y = w->input.top + workArea.y; while(w) { TILE_WINDOW(w); int x = workArea.x + w->input.left*(i+1) + w->input.right*i + width*i; if(!tw->originalWidth || !tw->originalHeight) saveCoords(w); placeWindow(w, x, y, width, height); i++; w = tw->next; } } break; /* Tile horizontally */ case horz: { int width = workArea.width - w->input.left - w->input.right; int height=(workArea.height - ts->decoHeight*THIS_VIEWPORT(s).tiledCount)/THIS_VIEWPORT(s).tiledCount; int x = w->input.left + workArea.x; while(w) { TILE_WINDOW(w); int y = workArea.y + w->input.top*(i+1) + w->input.bottom*i + height*i; if(!tw->originalWidth || !tw->originalHeight) saveCoords(w); placeWindow(w, x, y, width, height); i++; w = tw->next; } } break; /* Cascade */ case cascade: { int delta = td->opt[TILE_DISPLAY_OPTION_DELTA].value.i; int currentX = w->input.left + workArea.x; int currentY = w->input.top + workArea.y; int height = workArea.height - delta*(THIS_VIEWPORT(s).tiledCount - 1) - ts->decoHeight; int width = workArea.width - delta*(THIS_VIEWPORT(s).tiledCount - 1) - ts->decoWidth; while(w) { TILE_WINDOW(w); if(!tw->originalWidth || !tw->originalHeight) saveCoords(w); placeWindow(w, currentX, currentY, width, height); currentX += delta; currentY += delta; w = tw->next; } } break; /* Restore */ case none: { while(w) { TILE_WINDOW(w); placeWindow(w, tw->originalX, tw->originalY, tw->originalWidth, tw->originalHeight); tw->originalX = 0; tw->originalY = 0; tw->originalWidth = 0; tw->originalHeight = 0; w = tw->next; } } break; } } return TRUE; } static Bool tileTile(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption) { CompScreen *s; s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0)); if (s) { TILE_SCREEN(s); THIS_VIEWPORT(s).currentTileType = tile; applyTiling(s); } return FALSE; } static Bool tileVertically(CompDisplay * d, CompAction * ac, CompActionState state,CompOption * option, int nOption) { CompScreen *s; s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0)); if (s) { TILE_SCREEN(s); THIS_VIEWPORT(s).currentTileType = vert; applyTiling(s); } return FALSE; } static Bool tileHorizontally(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption) { CompScreen *s; s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0)); if (s) { TILE_SCREEN(s); THIS_VIEWPORT(s).currentTileType = horz; applyTiling(s); } return FALSE; } static Bool tileCascade(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption) { CompScreen *s; s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0)); if (s) { TILE_SCREEN(s); THIS_VIEWPORT(s).currentTileType = cascade; applyTiling(s); } return FALSE; } static Bool tileRestore(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption) { CompScreen *s; s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0)); if (s) { TILE_SCREEN(s); if(THIS_VIEWPORT(s).currentTileType != none) { THIS_VIEWPORT(s).currentTileType = none; applyTiling(s); } } return FALSE; } static Bool tileToggle(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption) { CompScreen *s; s = findScreenAtDisplay(d, getIntOptionNamed(option, nOption, "root", 0)); if (s) { TILE_SCREEN(s); if(THIS_VIEWPORT(s).currentTileType != none) { THIS_VIEWPORT(s).currentTileType = none; applyTiling(s); } else { TILE_DISPLAY(d); THIS_VIEWPORT(s).currentTileType = td->currentToggleType; applyTiling(s); } } return FALSE; } static void tileDisplayInitOptions(TileDisplay * td) { CompOption *o; o = &td->opt[TILE_DISPLAY_OPTION_VERTICALLY]; o->name = "tile_vertically"; o->group = N_("Key Bindings"); o->subGroup = N_("Tile Windows Vertically"); o->displayHints = ""; o->shortDesc = N_("Tile Windows Vertically"); o->longDesc = N_("Move and resize all visible windows so that they have full height, same width and occupy whole screen."); o->type = CompOptionTypeAction; o->value.action.initiate = tileVertically; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = 0; o->value.action.state = CompActionStateInitKey; // o->value.action.key.modifiers = TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_MOD; // o->value.action.key.keysym = // XStringToKeysym(TILE_VERTICALLY_DISPLAY_OPTION_INITIATE_KEY); o = &td->opt[TILE_DISPLAY_OPTION_HORIZONTALLY]; o->name = "tile_horizontally"; o->group = N_("Key Bindings"); o->subGroup = N_("Tile Windows Horizontally"); o->displayHints = ""; o->shortDesc = N_("Tile Windows Horizontally"); o->longDesc = N_("Move and resize all visible windows so that they have full width, same height and occupy whole screen."); o->type = CompOptionTypeAction; o->value.action.initiate = tileHorizontally; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = 0; o->value.action.state = CompActionStateInitKey; // o->value.action.key.modifiers = TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_MOD; // o->value.action.key.keysym = // XStringToKeysym(TILE_HORIZONTALLY_DISPLAY_OPTION_INITIATE_KEY); o = &td->opt[TILE_DISPLAY_OPTION_TILE]; o->name = "tile_tile"; o->group = N_("Key Bindings"); o->subGroup = N_("Tile Windows"); o->displayHints = ""; o->shortDesc = N_("Tile Windows"); o->longDesc = N_("Move and resize all visible windows both vertically and horizontally, so that the occupy whole screen and are in a grid."); o->type = CompOptionTypeAction; o->value.action.initiate = tileTile; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = CompBindingTypeKey; o->value.action.state = CompActionStateInitKey; o->value.action.key.modifiers = TILE_TILE_DISPLAY_OPTION_INITIATE_MOD; o->value.action.key.keysym = XStringToKeysym(TILE_TILE_DISPLAY_OPTION_INITIATE_KEY); o = &td->opt[TILE_DISPLAY_OPTION_CASCADE]; o->name = "tile_cascade"; o->group = N_("Key Bindings"); o->subGroup = N_("Cascade Windows"); o->displayHints = ""; o->shortDesc = N_("Cascade Windows"); o->longDesc = N_("Move and resize all visible windows with the delta value set for cascading."); o->type = CompOptionTypeAction; o->value.action.initiate = tileCascade; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = CompBindingTypeKey; o->value.action.state = CompActionStateInitKey; o->value.action.key.modifiers = TILE_CASCADE_DISPLAY_OPTION_INITIATE_MOD; o->value.action.key.keysym = XStringToKeysym(TILE_CASCADE_DISPLAY_OPTION_INITIATE_KEY); o = &td->opt[TILE_DISPLAY_OPTION_RESTORE]; o->name = "tile_restore"; o->group = N_("Key Bindings"); o->subGroup = N_("Restore Windows"); o->displayHints = ""; o->shortDesc = N_("Restore Windows"); o->longDesc = N_("Restore windows to original position before tiling."); o->type = CompOptionTypeAction; o->value.action.initiate = tileRestore; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = CompBindingTypeKey; o->value.action.state = CompActionStateInitKey; o->value.action.key.modifiers = TILE_RESTORE_DISPLAY_OPTION_INITIATE_MOD; o->value.action.key.keysym = XStringToKeysym(TILE_RESTORE_DISPLAY_OPTION_INITIATE_KEY); o = &td->opt[TILE_DISPLAY_OPTION_TOGGLE]; o->name = "tile_toggle"; o->group = N_("Key Bindings"); o->subGroup = N_("Toggle tiling"); o->displayHints = ""; o->shortDesc = N_("Toggle windows tiling"); o->longDesc = N_("Toggle between tiling and restoring."); o->type = CompOptionTypeAction; o->value.action.initiate = tileToggle; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = CompBindingTypeKey; o->value.action.state = CompActionStateInitEdge; o->value.action.state |= CompActionStateInitKey; o->value.action.key.modifiers = TILE_TOGGLE_DISPLAY_OPTION_INITIATE_MOD; o->value.action.key.keysym = XStringToKeysym(TILE_TOGGLE_DISPLAY_OPTION_INITIATE_KEY); o = &td->opt[TILE_DISPLAY_OPTION_ANIMATE]; o->advanced = False; o->name = "tile_animate"; o->group = N_("Options"); o->subGroup = N_("Animation"); o->displayHints = ""; o->shortDesc = N_("Animate windows when tiling"); o->longDesc = N_("Adds animation to tiling process."); o->type = CompOptionTypeBool; o->value.b = TILE_DEFAULT_ANIMATE; o = &td->opt[TILE_DISPLAY_OPTION_TOGGLE_TYPE]; o->advanced = False; o->name = "tile_toggle_type"; o->group = N_("Options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Tiling method used when toggling"); o->longDesc = N_("Choose tiling type you want when using toggle."); o->type = CompOptionTypeString; o->value.s = strdup(tileTypeString[TILE_DEFAULT_TOGGLE_TYPE]); o->rest.s.string = tileTypeString; o->rest.s.nString = TILE_TYPE_NUM; o = &td->opt[TILE_DISPLAY_OPTION_ANIMATION_TYPE]; o->advanced = False; o->name = "tile_animation_type"; o->group = N_("Options"); o->subGroup = N_("Animation"); o->displayHints = ""; o->shortDesc = N_("Animation type"); o->longDesc = N_("Select desired animation on tiling."); o->type = CompOptionTypeString; o->value.s = strdup(animationTypeString[TILE_DEFAULT_ANIMATION]); o->rest.s.string = animationTypeString; o->rest.s.nString = NUM_ANIMATIONS; o = &td->opt[TILE_DISPLAY_OPTION_ANIMATION_DURATION]; o->advanced = False; o->name = "tile_animation_duration"; o->group = N_("Options"); o->subGroup = N_("Animation"); o->displayHints = ""; o->shortDesc = N_("Animation Duration"); o->longDesc = N_("Duration in millisecond the window resizing animation should take."); o->type = CompOptionTypeInt; o->value.i = TILE_ANIMATION_DURATION_DEFAULT; o->rest.i.min = TILE_ANIMATION_DURATION_MIN; o->rest.i.max = TILE_ANIMATION_DURATION_MAX; o = &td->opt[TILE_DISPLAY_OPTION_JOIN]; o->advanced = False; o->name = "tile_join"; o->group = N_("Options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Join windows together (experimental !)"); o->longDesc = N_("Tries to join the windows together when horizontal, vertical or left tiling is enabled so that when you resize a window surrounding windows resize accordingly. This may pwn your windows if you dont leave them enough space (will be fixed later)."); o->type = CompOptionTypeBool; o->value.b = TILE_DEFAULT_JOIN; o = &td->opt[TILE_DISPLAY_OPTION_DELTA]; o->advanced = False; o->name = "tile_delta"; o->group = N_("Options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Cascade Delta"); o->longDesc = N_("Distance between windows when using cascade."); o->type = CompOptionTypeInt; o->value.i = TILE_DELTA_DEFAULT; o->rest.i.min = TILE_DELTA_MIN; o->rest.i.max = TILE_DELTA_MAX; o = &td->opt[TILE_DISPLAY_OPTION_LEFT_OCCUPANCY]; o->advanced = False; o->name = "tile_left_occupancy"; o->group = N_("Options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Left Occupancy"); o->longDesc = N_("Occupancy percentage for window placed left. This number is percentage of screen width, which the active window will have it as width when tiled. Applies to Left tiling type."); o->type = CompOptionTypeInt; o->value.i = TILE_LEFT_OCCUPANCY_DEFAULT; o->rest.i.min = TILE_LEFT_OCCUPANCY_MIN; o->rest.i.max = TILE_LEFT_OCCUPANCY_MAX; o = &td->opt[TILE_DISPLAY_OPTION_EXCLUDE_LIST]; o->advanced = False; o->name = "exclude_list"; o->group = N_("Options"); o->subGroup = N_("Exclusions"); o->displayHints = ""; o->shortDesc = N_("WM_CLASS to exclude"); o->longDesc = N_("Window classes which should not be tiled."); o->type = CompOptionTypeList; o->value.list.type = CompOptionTypeString; o->value.list.nValue = 0; o->value.list.value = 0; o->rest.s.string = 0; o->rest.s.nString = 0; } static CompOption *tileGetDisplayOptions(CompDisplay * display, int *count) { if (display) { TILE_DISPLAY(display); *count = NUM_OPTIONS(td); return td->opt; } else { TileDisplay *td = malloc(sizeof(TileDisplay)); tileDisplayInitOptions(td); *count = NUM_OPTIONS(td); return td->opt; } } static Bool tileInitDisplay(CompPlugin * p, CompDisplay * d) { //Generate a tile display TileDisplay *td = (TileDisplay *) malloc(sizeof(TileDisplay)); //Allocate a private index td->screenPrivateIndex = allocateScreenPrivateIndex(d); //Check if its valid if (td->screenPrivateIndex < 0) { free(td); return FALSE; } tileDisplayInitOptions(td); td->animationDuration = 0; td->currentAnimationType = TILE_DEFAULT_ANIMATION; td->currentToggleType = TILE_DEFAULT_TOGGLE_TYPE; //Record the display d->privates[displayPrivateIndex].ptr = td; return TRUE; } static void tileFiniDisplay(CompPlugin * p, CompDisplay * d) { TILE_DISPLAY(d); //Free the private index freeScreenPrivateIndex(d, td->screenPrivateIndex); //Free the pointer free(td); } static Bool tileInitWindow(CompPlugin * p, CompWindow * w) { TileWindow *tw; TILE_SCREEN(w->screen); tw = malloc(sizeof(TileWindow)); if (!tw) return FALSE; tw->next = 0; tw->prev = 0; tw->originalX = 0; tw->originalY = 0; tw->originalWidth = 0; tw->originalHeight = 0; tw->previousX = 0; tw->previousY = 0; tw->previousWidth = 0; tw->previousHeight = 0; tw->isResizing = FALSE; tw->isOtherAnimationAtom = IPCS_GetAtom(IPCS_OBJECT(w), IPCS_BOOL, "IS_ANIMATED", True); // animation plugin sets is_animated to true while it animates the window tw->prevState = 0; // random color, from group.c thanks to the author for doing it tw->outlineColor[0] = rand() % 0xFFFF; tw->outlineColor[1] = rand() % 0xFFFF; tw->outlineColor[2] = rand() % 0xFFFF; w->privates[ts->windowPrivateIndex].ptr = tw; return TRUE; } static void tileFiniWindow(CompPlugin * p, CompWindow * w) { TILE_WINDOW(w); TILE_SCREEN(w->screen); if(tw->originalWidth > 0 && tw->originalHeight > 0) { // when one window is destroyed, join the linked list CompWindow *prev = tw->prev; CompWindow *next = tw->next; if(prev) { TileWindow *twprev = GET_TILE_WINDOW(prev, GET_TILE_SCREEN (prev->screen, GET_TILE_DISPLAY (prev->screen->display))); twprev->next = next; } else { THIS_VIEWPORT(w->screen).firstTiled = next; } } free(tw); } static Bool tileInit(CompPlugin * p) { displayPrivateIndex = allocateDisplayPrivateIndex(); if (displayPrivateIndex < 0) return FALSE; return TRUE; } static void tileFini(CompPlugin * p) { if (displayPrivateIndex >= 0) freeDisplayPrivateIndex(displayPrivateIndex); } CompPluginVTable tileVTable = { "tile", N_("Tile"), N_("Tile windows"), tileInit, tileFini, tileInitDisplay, tileFiniDisplay, tileInitScreen, tileFiniScreen, tileInitWindow, tileFiniWindow, tileGetDisplayOptions, tileSetDisplayOption, 0, /*tileGetScreenOptions */ 0, /*tileSetScreenOption */ 0, 0, 0, 0, BERYL_ABI_INFO, "beryl-plugins-unsupported" #if BERYL_VERSION >= 35 ,"wm" #endif #if BERYL_VERSION >= 36 ,0,0 #endif #if BERYL_VERSION >= 37 ,False #endif }; CompPluginVTable *getCompPluginInfo(void) { return &tileVTable; }