Boundary of stateless blabbery

How to get XMonad play well with fullscreen mpv

Here’s the problem: Using smartBorders, lessBorders OnlyFloat or in fact any layout-based border-disabling hook with a fullscreen mpv window does not work as intended. This also applies to the older mplayers, too. What I mean is that mpv’s window dimensions are subtly twisted when it is either set fullscreen on startup (via –fs) or via pressing f in a running window.

However, the distortion is very subtle: mpv is stretched only by 2 pixels or so both horizontally and vertically (right and down). So on a 1920x1080 screen a fullscreen mpv window’s dimensions are actually 1922x1082, with (0,0) in upper left corner. The difference is close to impossible to spot on single-headed systems, but in xinerama the pixels overflow to screen on right and are rendered there. You can use xprop to check the dimensions.

I guess most people couldn’t care less for this, especially on single-head.

But I can’t stand it.

First workaround

Of course, because the problem is directly related to xmonad’s borders, a quick and dirty fix would be to just entirely disable borders. But that is very inconvenient when there are multiple windows on screen(s) and you can’t tell which has focus (without a status bar or something).

So a few months ago I came up with this:

myLayout = onWorkspaces ["full"] layoutFull $ ... $ avoidStruts $ ...
      layoutFull = noBorders $ fullscreenFull Full

What this does is that on a workspace named “full” every window is borderless, tiled and fill the whole screen. Note that it is important to not apply avoidStruts on the full layout, as otherwise status bars would eat into the “fullscreen”.

This is a good workaround, but it is far from perfect: it only works in the workspaces (“full”) defined in the layout.

But finally, after years of struggling I have come up with a solution!

(I mean, really. Years. this has bothered me from when I started using xmonad. 0.10 was then bleeding edge.)

You can find my solution at the end of this post. But first I want to document the why of the problem.

But why is this happening?

You may or may not have noticed that I haven’t mentioned ManageHooks at all so far. That’s because ManageHooks cannot solve the underlaying problem. Here’s why.

First of all, just isFullscreen --> doFullFloat or similar ManageHooks won’t even match on modern mpv --fs. ManageHooks are run when the window is created, and mpv doesn’t set the properties isFullscreen would match

(I can’t remember the details, though. But the –fstype option from mplayers is completely removed in mpv and there is now only a –x11-netwm, which doesn’t seem to do anything on my system.)

Instead, mpv does all it’s fullscreening via Extended Window Manager Hints (EWMH), namely NET_WM_STATE. These cannot be catched in ManageHooks AFAIK. They are events (i think), and are only fed to handleEventHook.

Okay, then what about a ManageHook that makes every mpv window fullscreen regardless if it wants or not? So we get something like className =? "mpv" --> doFullFloat and… wait, the borders!

Yeah, you can’t (reliably) remove borders from a specific window in a ManageHook. The borders are drawn based based on the borderWidth setting in XConfig. Furthermore, the borders are redrawn in many mystic places all over the xmonad core! You could hack it together and call setWindowBorderWidth :: ... -> IO () in the ManageHook, but they are instantly redrawn in somewhere else–with the global borderWidth.

The real problem is of course that lessBorders OnlyFloat and similar are meant to solve exactly this case. And in fact the modifier works as intended: when you doFullFloat a mpv window, what happens is that the border modifier kicks in and removes borders as intented and the window is resized to a new size that fills the extra space. But the calculated new size is wrong (a few pixels too big).

I don’t know if this is because of mpv doing something wrong or XMonad or both, but other window managers don’t seem to have the same issue. Whether xmonad complies perfectly with standars and the others do not, I don’t know, but I still want my pixels.

And don’t get me wrong, XMonad is wonderful software and the best WM I know. I would have stuck with it even if I hadn’t solved the mpv problem.

The solution

Here it is. I am too lazy to spell out required imports. But you can do that much by yourself, right? :) (Just hoogle for identifier +xmonad-contrib).

FWIW I am running the latest tarballs from darcs, both xmonad and XMonadcontrib.

  1. Add or make this your handleEventHook

    myConfig = def
      { handleEventHook = fullscreenEventHook <+> removeBordersEventHook
      , ...
  2. Add this to your ManageHook.

    className =? "mpv" --> doFloat

    Actually, I have no idea why it is required to float the window manually because fullscreenEventHook should do it according to the source. If you know I would very much like to learn, too!

  3. Finally copy-paste this function to your xmonad.hs

    -- | Remove borders from every mpv window as soon as possible in an event
    -- hook, because otherwise dimensions are messed and the fullscreen mpv is
    -- stretched by a couple pixels.
    -- Basically the effect is the same as with
    -- "XMonad.Layout.NoBorders.lessBorders OnlyFloat", except that OnlyFloat
    -- messes up the dimensions when used together with fullscreenEventHook
    -- (e.g. NET_WM_STATE). Well at least in mplayer/mpv.
    -- I have no idea how often/where the border is re-applied, but resetting
    -- it to 0 whenever possible just works :)
    removeBordersEventHook :: Event -> X All
    removeBordersEventHook ev = do
        whenX (className =? "mpv" `runQuery` w) $ withDisplay $ \d ->
            io $ setWindowBorderWidth d w 0
        return (All True)
            w = ev_window ev

    This event hook removes borders from all mpv windows early and repeatedly, effectively obsolating the need of resizing the window when fullscreening it and avoiding the problem I discussed in the previous section.


Actually, I am not 120% happy with this solution either because it floats and removes borders from every mpv window, always. I think the event hook could be modified somehow to only trigger on fullscreened mpv, but I am not sure how.

P.S. I also learned about i3lock today. This is what my lock screen looks like now (dual-head):



And oh, my xmonad.hs just exploded to over 600 lines today. I have a feeling I am implementing too much stuff that could already be found in XMonadContrib, but I can’t figure out what.

comments powered by Disqus