------------------------------------------------------------------------------- |-- Module : XMonad.Hooks.Place-- Copyright : Quentin Moser <quentin.moser@unifr.ch>-- License : BSD-style (see LICENSE)---- Maintainer : Quentin Moser <quentin.moser@unifr.ch>-- Stability : unstable-- Portability : unportable---- Automatic placement of floating windows.-------------------------------------------------------------------------------moduleXMonad.Hooks.Place(-- * Usage-- $usage-- * Placement actionsplaceFocused,placeHook-- * Placement policies-- $placements,Placement,smart,simpleSmart,fixed,underMouse,inBounds,withGaps-- * Others,purePlaceWindow)whereimportXMonadimportqualifiedXMonad.StackSetasSimportXMonad.Layout.WindowArrangerimportXMonad.Actions.FloatKeysimportqualifiedData.MapasMimportData.Ratio((%))importData.List(sortBy,minimumBy,partition)importData.Maybe(maybe,fromMaybe,catMaybes)importData.Monoid(Endo(..))importControl.Monad(guard,join)importControl.Monad.Trans(lift)-- $usage-- This module provides a 'ManageHook' that automatically places-- floating windows at appropriate positions on the screen, as well-- as an 'X' action to manually trigger repositioning.---- You can use this module by including the following in your @~\/.xmonad\/xmonad.hs@:---- > import XMonad.Hooks.Place---- and adding 'placeHook' to your 'manageHook', for example:---- > main = xmonad $ defaultConfig { manageHook = placeHook simpleSmart-- > <+> manageHook defaultConfig }---- Note that 'placeHook' should be applied after most other hooks, especially hooks-- such as 'doFloat' and 'doShift'. Since hooks combined with '<+>' are applied from-- right to left, this means that 'placeHook' should be the /first/ hook in your chain.---- You can also define a key to manually trigger repositioning with 'placeFocused' by-- adding the following to your keys definition:---- > , ((modm, xK_w), placeFocused simpleSmart)---- Both 'placeHook' and 'placeFocused' take a 'Placement' parameter, which specifies-- the placement policy to use (smart, under the mouse, fixed position, etc.). See-- 'Placement' for a list of available policies.{- Placement policies -}-- $placements-- Placement policies determine how windows will be placed by 'placeFocused' and 'placeHook'.---- A few examples:---- * Basic smart placement---- > myPlacement = simpleSmart---- * Under the mouse (pointer at the top-left corner), but constrained-- inside of the screen area---- > myPlacement = inBounds (underMouse (0, 0))---- * Smart placement with a preference for putting windows near-- the center of the screen, and with 16px gaps at the top and bottom-- of the screen where no window will be placed---- > myPlacement = withGaps (16,0,16,0) (smart (0.5,0.5))-- | The type of placement policiesdataPlacement=Smart(Rational,Rational)|Fixed(Rational,Rational)|UnderMouse(Rational,Rational)|Bounds(Dimension,Dimension,Dimension,Dimension)Placementderiving(Show,Read,Eq)-- | Try to place windows with as little overlap as possiblesmart::(Rational,Rational)-- ^ Where the window should be placed inside-- the available area. See 'fixed'.->Placementsmart=SmartsimpleSmart::PlacementsimpleSmart=inBounds$smart(0,0)-- | Place windows at a fixed positionfixed::(Rational,Rational)-- ^ Where windows should go.---- * (0,0) -> top left of the screen---- * (1,0) -> top right of the screen---- * etc->Placementfixed=Fixed-- | Place windows under the mouseunderMouse::(Rational,Rational)-- ^ Where the pointer should be relative to-- the window's frame; see 'fixed'.->PlacementunderMouse=UnderMouse-- | Apply the given placement policy, constraining the-- placed windows inside the screen boundaries.inBounds::Placement->PlacementinBounds=Bounds(0,0,0,0)-- | Same as 'inBounds', but allows specifying gaps along the screen's edgeswithGaps::(Dimension,Dimension,Dimension,Dimension)-- ^ top, right, bottom and left gaps->Placement->PlacementwithGaps=Bounds{- Placement functions -}-- | Repositions the focused window according to a placement policy. Works for-- both \"real\" floating windows and windows in a 'WindowArranger'-based-- layout.placeFocused::Placement->X()placeFocusedp=withFocused$\window->doinfo<-gets$screenInfo.S.current.windowsetfloats<-gets$M.keys.S.floating.windowsetr'@(Rectanglex'y'__)<-placeWindowpwindowinfofloats-- use X.A.FloatKeys if the window is floating, send-- a WindowArranger message otherwise.caseelemwindowfloatsofTrue->keysMoveWindowTo(x',y')(0,0)windowFalse->sendMessage$SetGeometryr'-- | Hook to automatically place windows when they are created.placeHook::Placement->ManageHookplaceHookp=dowindow<-askr<-Query$lift$getWindowRectanglewindowallRs<-Query$lift$getAllRectanglespointer<-Query$lift$getPointerwindowreturn$Endo$\theWS->fromMaybetheWS$doletcurrentRect=screenRect$S.screenDetail$S.currenttheWSfloats=M.keys$S.floatingtheWSguard(window`elem`floats)-- Look for the workspace(s) on which the window is to be-- spawned. Each of them also needs an associated screen-- rectangle; for hidden workspaces, we use the current-- workspace's screen.letinfos=filter((window`elem`).stackContents.S.stack.fst)$[screenInfo$S.currenttheWS]++(mapscreenInfo$S.visibletheWS)++zip(S.hiddentheWS)(repeatcurrentRect)guard(not$nullinfos)let(workspace,screen)=headinfosrs=catMaybes$map(flipM.lookupallRs)$organizeClientsworkspacewindowfloatsr'=purePlaceWindowpscreenrspointerrnewRect=r2rrscreenr'newFloats=M.insertwindownewRect(S.floatingtheWS)return$theWS{S.floating=newFloats}placeWindow::Placement->Window->(S.WorkspaceWorkspaceId(LayoutWindow)Window,Rectangle)-- ^ The workspace with reference to which the window should be placed,-- and the screen's geometry.->[Window]-- ^ The list of floating windows.->XRectangleplaceWindowpwindow(ws,s)floats=do(r,rs,pointer)<-getNecessaryDatawindowwsfloatsreturn$purePlaceWindowpsrspointerr-- | Compute the new position of a window according to a placement policy.purePlaceWindow::Placement-- ^ The placement strategy->Rectangle-- ^ The screen->[Rectangle]-- ^ The other visible windows->(Position,Position)-- ^ The pointer's position.->Rectangle-- ^ The window to be placed->RectanglepurePlaceWindow(Bounds(t,r,b,l)p')(Rectanglesxsyswsh)rspw=lets'=(Rectangle(sx+fil)(sy+fit)(sw-l-r)(sh-t-b))incheckBoundss'$purePlaceWindowp's'rspwpurePlaceWindow(Fixedratios)s__w=placeRatioratiosswpurePlaceWindow(UnderMouse(rx,ry))__(px,py)(Rectangle__wh)=Rectangle(px-truncate(rx*fiw))(py-truncate(ry*fih))whpurePlaceWindow(Smartratios)srs_w=placeSmartratiossrs(rect_widthw)(rect_heightw)-- | Helper: Places a Rectangle at a fixed position indicated by two Rationals-- inside another,placeRatio::(Rational,Rational)->Rectangle->Rectangle->RectangleplaceRatio(rx,ry)(Rectanglex1y1w1h1)(Rectangle__w2h2)=Rectangle(scalerxx1(x1+fiw1-fiw2))(scaleryy1(y1+fih1-fih2))w2h2-- | Helper: Ensures its second parameter is contained inside the first-- by possibly moving it.checkBounds::Rectangle->Rectangle->RectanglecheckBounds(Rectanglex1y1w1h1)(Rectanglex2y2w2h2)=Rectangle(maxx1(min(x1+fiw1-fiw2)x2))(maxy1(min(y1+fih1-fih2)y2))w2h2{- Utilities -}scale::(RealFraca,Integralb)=>a->b->b->bscalern1n2=truncate$r*fin2+(1-r)*fin1fi::(Integrala,Numb)=>a->bfi=fromIntegralr2rr::Rectangle->Rectangle->S.RationalRectr2rr(Rectanglex0y0w0h0)(Rectanglexywh)=S.RationalRect((fix-fix0)%fiw0)((fiy-fiy0)%fih0)(fiw%fiw0)(fih%fih0){- Querying stuff -}stackContents::Maybe(S.Stackw)->[w]stackContents=maybe[]S.integratescreenInfo::S.ScreenilasidScreenDetail->(S.Workspaceila,Rectangle)screenInfo(S.Screen{S.workspace=ws,S.screenDetail=(SDs)})=(ws,s)getWindowRectangle::Window->XRectanglegetWindowRectanglewindow=dod<-asksdisplay(_,x,y,w,h,_,_)<-io$getGeometrydwindow-- We can't use the border width returned by-- getGeometry because it will be 0 if the-- window isn't mapped yet.b<-asks$borderWidth.configreturn$Rectanglexy(w+2*b)(h+2*b)getAllRectangles::X(M.MapWindowRectangle)getAllRectangles=dows<-getswindowsetletallWindows=join$map(stackContents.S.stack)$(S.workspace.S.current)ws:(mapS.workspace.S.visible)ws++S.hiddenwsallRects<-mapMgetWindowRectangleallWindowsreturn$M.fromList$zipallWindowsallRectsorganizeClients::S.WorkspaceabWindow->Window->[Window]->[Window]organizeClientswswfloats=let(floatCs,layoutCs)=partition(`elem`floats)$filter(/=w)$stackContents$S.stackwsinreverselayoutCs++reversefloatCs-- About the ordering: the smart algorithm will overlap windows-- starting ith the head of the list. So:-- - we put the non-floating windows first since they'll-- probably be below the floating ones,-- - we reverse the lists, since the newer/more important-- windows are usually near the head.getPointer::Window->X(Position,Position)getPointerwindow=dod<-asksdisplay(_,_,_,x,y,_,_,_)<-io$queryPointerdwindowreturn(fix,fiy)-- | Return values are, in order: window's rectangle,-- other windows' rectangles and pointer's coordinates.getNecessaryData::Window->S.WorkspaceWorkspaceId(LayoutWindow)Window->[Window]->X(Rectangle,[Rectangle],(Position,Position))getNecessaryDatawindowwsfloats=dor<-getWindowRectanglewindowrs<-return(organizeClientswswindowfloats)>>=mapMgetWindowRectanglepointer<-getPointerwindowreturn(r,rs,pointer){- Smart placement algorithm -}-- | Alternate representation for rectangles.dataSmartRectanglea=SR{sr_x0,sr_y0::a-- ^ Top left coordinates, inclusive,sr_x1,sr_y1::a-- ^ Bottom right coorsinates, exclusive}deriving(Show,Eq)r2sr::Rectangle->SmartRectanglePositionr2sr(Rectanglexywh)=SRxy(x+fiw)(y+fih)sr2r::SmartRectanglePosition->Rectanglesr2r(SRx0y0x1y1)=Rectanglex0y0(fi$x1-x0)(fi$y1-y0)width::Numa=>SmartRectanglea->awidthr=sr_x1r-sr_x0rheight::Numa=>SmartRectanglea->aheightr=sr_y1r-sr_y0risEmpty::Reala=>SmartRectanglea->BoolisEmptyr=(widthr<=0)||(heightr<=0)contains::Reala=>SmartRectanglea->SmartRectanglea->Boolcontainsr1r2=sr_x0r1<=sr_x0r2&&sr_y0r1<=sr_y0r2&&sr_x1r1>=sr_x1r2&&sr_y1r1>=sr_y1r2-- | Main placement functionplaceSmart::(Rational,Rational)-- ^ point of the screen where windows-- should be placed first, if possible.->Rectangle-- ^ screen->[Rectangle]-- ^ other clients->Dimension-- ^ width->Dimension-- ^ height->RectangleplaceSmart(rx,ry)s@(Rectanglesxsyswsh)rswh=letfree=mapsr2r$findSpace(r2srs)(mapr2srrs)(fiw)(fih)inpositionfree(scalerxsx(sx+fisw-fiw))(scalerysy(sy+fish-fih))wh-- | Second part of the algorithm:-- Chooses the best position in which to place a window,-- according to a list of free areas and an ideal position for-- the top-left corner.-- We can't use semi-open surfaces for this, so we go back to-- X11 Rectangles/Positions/etc instead.position::[Rectangle]-- ^ Free areas->Position->Position-- ^ Ideal coordinates->Dimension->Dimension-- ^ Width and height of the window->Rectanglepositionrsxywh=minimumBydistanceOrder$mapclosestrswheredistanceOrderr1r2=compare(distance(rect_xr1,rect_yr1)(x,y)::Dimension)(distance(rect_xr2,rect_yr2)(x,y)::Dimension)distance(x1,y1)(x2,y2)=truncate$(sqrt::Double->Double)$fi$(x1-x2)^(2::Int)+(y1-y2)^(2::Int)closestr=checkBoundsr(Rectanglexywh)-- | First part of the algorithm:-- Tries to find an area in which to place a new-- rectangle so that it overlaps as little as possible with-- other rectangles already present. The first rectangles in-- the list will be overlapped first.findSpace::Reala=>SmartRectanglea-- ^ The total available area->[SmartRectanglea]-- ^ The parts already in use->a-- ^ Width of the rectangle to place->a-- ^ Height of the rectangle to place->[SmartRectanglea]findSpacetotal[]__=[total]findSpacetotalrs@(_:rs')wh=casefilterlargeEnough$cleanup$subtractRectstotalrsof[]->findSpacetotalrs'whas->aswherelargeEnoughr=widthr>=w&&heightr>=h-- | Subtracts smaller rectangles from a total rectangle-- , returning a list of remaining rectangular areas.subtractRects::Reala=>SmartRectanglea->[SmartRectanglea]->[SmartRectanglea]subtractRectstotal[]=[total]subtractRectstotal(r:rs)=dototal'<-subtractRectstotalrsfilter(not.isEmpty)[total'{sr_y1=min(sr_y1total')(sr_y0r)}-- Above,total'{sr_x0=max(sr_x0total')(sr_x1r)}-- Right,total'{sr_y0=max(sr_y0total')(sr_y1r)}-- Below,total'{sr_x1=min(sr_x1total')(sr_x0r)}-- Left]-- | "Nubs" a list of rectangles, dropping all those that are-- already contained in another rectangle of the list.cleanup::Reala=>[SmartRectanglea]->[SmartRectanglea]cleanuprs=foldrdropIfContained[]$sortBysizeOrderrssizeOrder::Reala=>SmartRectanglea->SmartRectanglea->OrderingsizeOrderr1r2|w1<w2=LT|w1==w2&&h1<h2=LT|w1==w2&&h1==h2=EQ|otherwise=GTwherew1=widthr1w2=widthr2h1=heightr1h2=heightr2dropIfContained::Reala=>SmartRectanglea->[SmartRectanglea]->[SmartRectanglea]dropIfContainedrrs=ifany(`contains`r)rsthenrselser:rs