Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X.L.DecorationEx: extensible mechanism for window decorations #857

Merged
merged 21 commits into from
Jan 21, 2024

Conversation

portnov
Copy link
Contributor

@portnov portnov commented Dec 21, 2023

Sorry for a lot of letters :)

Preface

At some moment I wanted to customize window decorations in xmonad: set decoration bar height a bit bigger than it is by default, set another font, maybe adjust padding between window buttons, probably add non-standard window buttons (for example to move windows between groups in X.L.Groups)... Initially I expected that all of it is possible if not by simple configuration, then by writing a custom DecorationStyle, which would inherit most methods from the default one and override only some methods. Probably I'm spoiled with OOP :)

This is an example what I wanted to achieve in the beginning:

Screenshot_20231206_163349

But when I tried to do so, it appeared that X.L.Decoration is not exactly designed for ease of adding new features without rewriting from scratch. For example,

  • Offsets of window buttons are hardcoded in X.L.DecorationAddons, in pixels; they are not automatically calculated from font characteristics.
  • Although user can specify custom offsets of buttons in Theme definition, these values will be used only when drawing buttons; when handling clicks, anyway hardcoded values are used.
  • There is AlignRightOffset, but no AlignLeftOffset, so it is not possible to have more than one button at the left.
  • Whole painting of window decoration (background, window title and buttons) is implemented in one method (paintTextAndIcons); that method is directly called from Decoration layout modifier, so it is not possible to override just some part of painting process. For example, I started from simple wish of having borders of different color from different side: a bit lighter on top and left, a bit darker on right and bottom, for simple 3D-like look. But it appeared I cannot override only drawing of border without copy-pasting (or modifying) Decoration modifier itself.
  • Data constructor of default DecorationStyle implementation and related types are not exported, so it is not possible to write custom instances which delegate some method implementation to default, and override only some of them.
  • Because data constructors are not exported, it is hard to combine different aspects of decoration look & feel from different modules. For example, there is DefaultDecoration, and there is buttonDeco. Then there is X.L.Tabbed. If you want to combine buttons with tabs... well, you got the idea I think.

I honestly started from trying to copy-paste (since in the beginning I hoped that all modifications will be just in my ~/.xmonad/lib) as less code as possible, and where possible - call default implementation of methods. But that road took me far. I had to understand how default implementation works (and for that - to change naming style during copy-pasting, because insert_dwr ((w,r),(Just dw,Just dr)) xs = (dw,dr):(w, shrink ds dr r):xs is... sorry but :D)).

So I ended up with a whole new implementation of LayoutModifier for decorations and related classes, see description below. When I understood that I have a more or less generic framework for window decoration, I figured that now I can write another implementation of DecorationEngine, which would be able to draw more fancy window decorations, much more like traditional WMs: with gradients and pixmaps. Obviously it's "a bit tedious" to write such things in plain xlib, so I considered several alternatives and ended up with cairo.

Currently I have all code for generic framework and new default decoration engine in separate repo (https://github.com/portnov/xmonad-decoration-ex), current PR is basically a copy-paste from there. And I have cairo-based engine in another repo (https://github.com/portnov/xmonad-decoration-cairo), that will probably later be PR-ed into xmonad-extra.

This is what is now possible with cairo engine:

Screenshot_20231217_144813

More screenshots : https://github.com/portnov/xmonad-decoration-ex/issues/1

More technical description

Within this mechanism, there are the following entities which define
how decorations will look and work:

  • Main object is DecorationEx layout modifier. It is from where everyting
    starts. It creates, shows and hides decoration windows (rectangles) when
    needed. It is parametrized with decoration geometry, decoration engine and
    theme. It calls these components to do their part of work.
  • DecorationGeometry defines where decoration rectangles should be placed.
    For example, standard horizontal bar above each window; or tab bar.
  • DecorationEngine defines how decorations look and how they react on clicks.
    Different implementations of decoration engine can use different API
    to draw decorations. Within this package, there is one implementation
    (TextDecoration), which uses plain Xlib calls, and displays decoration
    widgets with text fragments, like [X] or [_]. Other engines can, for
    example, use Cairo library to draw nice gradients and image-based widgets.
  • Decoration widget is an element placed on window decoration. It defines how
    it looks and how it responds to clicks. Examples include usual window
    buttons (minimize, maximize, close), window icon, window title.
  • Decoration theme defines colors and fonts for decoration engine. It also
    contains a list of decoration widgets and says where to place them (at the
    left, at the right or in the center).

This mechanism makes a huge use of parametrized data types and type families,
in order to make it possible to define different types of decorations, and
easily combine different aspects of decorations. For example, each decoration
engine can be combined with each decoration geometry.

See haddock documentation for more details.

Checklist

  • I've read CONTRIBUTING.md

  • I've tested these changes by using these decoration for several weeks. I do not know better ways of testing this.

  • I updated the CHANGES.md file

@portnov
Copy link
Contributor Author

portnov commented Dec 21, 2023

Hmm, it seems I will have to address compilation issues. Strange, simple stack build in this branch works for me. Will investigate.

@portnov
Copy link
Contributor Author

portnov commented Dec 21, 2023

There is still "orphan instances" thing. Will think what to do with them.

@portnov
Copy link
Contributor Author

portnov commented Dec 22, 2023

Nearly minimal configuration:


import XMonad

import XMonad.Config.Desktop
import XMonad.Hooks.EwmhDesktops

import XMonad.Layout.DecorationEx

theme = (themeEx def) {
          exWidgetsLeft = [titleW],
          exWidgetsCenter = [],
          exWidgetsRight = [minimizeW, maximizeW, closeW]
        }

main =  do
  xmonad $ ewmh $
    desktopConfig {
        terminal           = "konsole",
        focusFollowsMouse  = False,
        borderWidth        = 2,
        modMask            = mod4Mask,
        workspaces         = ["1", "2"],
        clickJustFocuses   = False,
 
        layoutHook         = textDecoration shrinkText theme (layoutHook def)
    }

Screenshot_20231222_172736

@portnov
Copy link
Contributor Author

portnov commented Dec 22, 2023

Wow. Warnings fixed.
Please review. Suggestions are welcome.

Copy link
Contributor

@geekosaur geekosaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also add something to the changelog.

XMonad/Layout/DecorationEx.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx/LayoutModifier.hs Outdated Show resolved Hide resolved
-- @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Layout.DecorationEx.LayoutModifier
-- Then edit your @layoutHook@ by adding the DwmStyle decoration to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should DwmStyle be replaced by something more generic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, agreed. In fact if you look what does my "DwmGeometry" currently, it's more general than DWM. It is similar to DWM in that it draws decoration bar over the window, but it can place the bar on the left, on the right or in the center. But honestly I have no idea how to name it. Maybe you have ideas? :)

XMonad/Layout/DecorationEx/LayoutModifier.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx/Widgets.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx/Widgets.hs Outdated Show resolved Hide resolved
portnov and others added 8 commits December 23, 2023 01:34
thanks to @geekosaur

Co-authored-by: brandon s allbery kf8nh <[email protected]>
See also xmonad#859

Co-authored-by: brandon s allbery kf8nh <[email protected]>
Co-authored-by: brandon s allbery kf8nh <[email protected]>
Co-authored-by: brandon s allbery kf8nh <[email protected]>
thanks to @geekosaur

Co-authored-by: brandon s allbery kf8nh <[email protected]>
@portnov
Copy link
Contributor Author

portnov commented Dec 27, 2023

@geekosaur ,
about calcWidgetPlace: I decided, rather than invent a special data type just for one function, let's just allow the method to return any X coordinate of rectangle. It will be simply ignored by functions which align widgets.

About "DwmStyle", I didn't come up with better name. Maybe let it be as it is?

@portnov
Copy link
Contributor Author

portnov commented Dec 27, 2023

Also, I have a couple of smaller improvements in continuation of this topic. I guess it's better to put them in separate PR after this one will be merged?

@portnov
Copy link
Contributor Author

portnov commented Jan 5, 2024

@geekosaur
Don't want to sound pushy, but what's status of this? is it waiting for another round of review or?...

@geekosaur
Copy link
Contributor

I was waiting to see if @liskin or @slotThe wanted to say something, and giving extra time because of the holidays. I'm also failing to come up with anything better than DwmStyle, so I guess it stands.

Copy link
Member

@slotThe slotThe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I (admittedly) skimmed through this and overall it looks great, thanks! 🎉

XMonad/Layout/DecorationEx.hs Show resolved Hide resolved
XMonad/Layout/DecorationEx/DwmGeometry.hs Outdated Show resolved Hide resolved
XMonad/Layout/DecorationEx/LayoutModifier.hs Outdated Show resolved Hide resolved
@portnov
Copy link
Contributor Author

portnov commented Jan 21, 2024

Hello
What's the status of this? Can this be merged or we're waiting for something?

@geekosaur geekosaur merged commit a5fb7e0 into xmonad:master Jan 21, 2024
18 checks passed
@portnov
Copy link
Contributor Author

portnov commented Jan 21, 2024

woo-hoo! thanks to all :)

SimSaladin added a commit to SimSaladin/xmonad-contrib that referenced this pull request Apr 2, 2024
* upstream/master:
  X.P: Don't read/write to history file if size is 0
  missed the test suite
  X.P.OrgMode: Add ability to specify time spans
  X.P.OrgMode: Fallback to "today" if no day is given
  ci: Regenerate haskell-ci
  remove extraneous ) in docstring
  update time to <1.15
  build(deps): bump cachix/install-nix-action from 25 to 26
  Update StatusBar library to use the X monad instead of IO.
  X.H.ManageHelpers: `isNotification` predicate
  ci: Regenerate haskell-ci
  ci: Bump GHC patch versions in tested-with
  .mailmap: Update
  feat: add profiles
  X.U.EZConfig: Fix checkKeymap warning that all keybindings are duplicate
  ci: Adopt the liskin/gh-workflow-keepalive action
  X.L.Hidden: use the modifyWindowSet function
  X.*: Fix typos (xmonad#871)
  Update CHANGES.md
  Fix build-with-cabal.sh when XDG_CONFIG_HOME is defined
  Bump version to 0.18.0.9
  cabal: Bump xmonad dependency
  Bump version to 0.18.0
  X.P: Fix some typos in comments
  build(deps): bump actions/cache from 3 to 4
  only sink window if actually fullscreened
  X.L.DecorationEx: extensible mechanism for window decorations (xmonad#857)
  ci: Regenerate haskell-ci
  ci: Bump GHC patch versions in tested-with
  build(deps): bump cachix/install-nix-action from 24 to 25
  X.H.EwmhDesktops: Fix haddock markup
  X.P: Export {selected,setCurrent,getCurrent}Completion[s]
  Fix example Xmonad.Prompt.Input usage example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants