77 Commits

Author SHA1 Message Date
Tony Zorman
5cdddab1f1 INSTALL: Suggest newer tagged releases 2022-09-03 14:52:45 +02:00
Tony Zorman
488b52ffaa Bump version to 0.17.1 2022-09-03 14:47:26 +02:00
Tony Zorman
83a8bb8d51 TUTORIAL: Fix link to xmobar.hs example
In [1] xmobar renamed the "examples" directory to "etc".

[1]: 6330397707
2022-08-13 06:27:17 +02:00
Tony Zorman
b06d885e76 X.ManageHook: Clarify {app,class}Name documentation
Users often mistakenly use className to query the application (resource)
name of a window; probably since WM_CLASS even has "class" in its name,
so confusion ensues.  Improve the documentation to explicitly state
which of the two strings in WM_CLASS the respective functions match.
2022-08-08 11:03:25 +02:00
Tony Zorman
a13a1dcee8 Merge pull request #404 from geekosaur/forever-away
attempt to work around the join point bug (#389)
2022-08-04 08:48:43 +02:00
brandon s allbery kf8nh
8965e41d06 attempt to work around the join point bug (#389) 2022-08-04 08:44:50 +02:00
Tony Zorman
28afc9bdc6 Merge pull request #405 from slotThe/modal-util-to-core
X.Operations: Export setNumlockMask, grabKeys
2022-08-02 11:21:50 +02:00
Tony Zorman
23f36d7e23 Merge pull request #409 from alternateved/fix-nix-build
Fix Nix builds
2022-08-02 08:03:30 +02:00
Tomasz Hołubowicz
117583e473 flake.nix: Fix build
As advised in [1], provide a way to set the path for the xmessage and
ghc binaries via XMONAD_XMESSAGE and XMONAD_GHC environment variables.

[1]: 36d5761b3e
2022-08-02 08:02:40 +02:00
Tony Zorman
bf6e66b100 X.Operations: Export setNumlockMask, grabKeys
As discussed in xmonad/xmonad-contrib/#703, certain functions that
X.U.Grab has vendored should really just be exported from core.

Related: https://github.com/xmonad/xmonad-contrib/pull/703
2022-08-01 11:59:37 +02:00
Tomas Janousek
366c09b3d7 TUTORIAL: Fix links to xmobar 2022-07-30 14:59:14 +01:00
Tomas Janousek
ed5c8667b1 Revert "Revert "Merge pull request #350 from Thiago4532/opaque-window-border""
This reverts commit e5a258f19c.

https://github.com/xmonad/xmonad-contrib/pull/731 fixes the
WindowNavigation issue with alpha channels, so we can re-enable alpha
support.
2022-07-05 23:09:10 +01:00
brandon s allbery kf8nh
1695aeb28a Revert "Revert "CHANGES: Add missing entry for #350""
This reverts commit 29475fa7f8.

xmonad/xmonad-contrib#731 fixes the WindowNavigation issue with alpha
channels, so we can re-enable alpha support. A subsequent commit will
bump versions/dependencies to keep things in sync, although strictly
speaking that's not necessary here.
2022-07-05 17:50:17 -04:00
Tony Zorman
521e8356fc X.ManageHook: Define infix operators in an infix way
As discussed in #401, while hlint complains on a definition like

    (<||>) x y = ifM x (pure True) y

because it could be eta reduced, actually doing that and writing

    (<||>) x = ifM x (pure True)

feels a bit awkward.  The solution is to always actually define these
kinds of infix operators in an infix way; i.e., we write

    x <||> y = ifM x (pure True) y

instead.  For the sake of consistency, it now seems prudent to define
all infix operators in this way (with exceptions for literal aliases,
like `(<+>) = mappend`).

Related: https://github.com/xmonad/xmonad/pull/401
2022-06-29 09:40:55 +02:00
Tomas Janousek
45a89130d9 Merge branch 'hlint' 2022-06-25 18:49:02 +01:00
Tomas Janousek
79602bfec5 tests: Apply hlint hints 2022-06-25 18:49:02 +01:00
Tomas Janousek
711b28f494 Apply hlint hints
Makes src/ hlint-clean.
2022-06-25 18:49:02 +01:00
Tomas Janousek
0edb65107b Clean up LANGUAGE pragmas
Make it git-friendly and drop some extra ones as suggested by hlint.
2022-06-25 18:49:02 +01:00
Tomas Janousek
845d770f35 man: Apply hlint hints
These were applied to src/XMonad/Config.hs back in 2010 (77b3f62610) … :-)
2022-06-25 18:49:02 +01:00
Tomas Janousek
3f1a37f216 ci: Enable hlint in haskell-ci
Closes: https://github.com/xmonad/xmonad/pull/402
Related: https://github.com/xmonad/xmonad-contrib/issues/669
2022-06-25 18:49:02 +01:00
Andrew Lushin
165e25f9e0 Lint some pieces of code
Closes: https://github.com/xmonad/xmonad/pull/401
2022-06-25 18:49:02 +01:00
Tomas Janousek
9189d002dd ci: Prevent scheduled workflows from being auto-disabled by GitHub 2022-06-19 17:04:43 +01:00
Tomas Janousek
29475fa7f8 Revert "CHANGES: Add missing entry for #350"
This reverts commit c537a0658a.

A discussion of why, as well as a new way forward, in being discussed in

    https://github.com/xmonad/xmonad/issues/395

However, since we would like to release a new minor version soon-ish,
it's better to revert this for now.

Fixes: e5a258f19c ("Revert "Merge pull request #350 from Thiago4532/opaque-window-border"")
2022-06-19 16:54:29 +01:00
Tony Zorman
e5a258f19c Revert "Merge pull request #350 from Thiago4532/opaque-window-border"
This reverts commit dbe9c4f799, reversing
changes made to f6e4e278b5.

A discussion of why, as well as a new way forward, in being discussed in

    https://github.com/xmonad/xmonad/issues/395

However, since we would like to release a new minor version soon-ish,
it's better to revert this for now.
2022-06-18 17:40:25 +02:00
Tomáš Janoušek
0c8ed88d8a Merge pull request #342 from TheMC47/contributing-changes
CONTRIBUTING: Update to reflect current practices
2022-06-13 11:39:34 +02:00
Yecine Megdiche
9442871016 CONTRIBUTING: Document expectations about maintenance and getting involved
Related: https://github.com/xmonad/xmonad/issues/341
2022-06-13 11:05:25 +02:00
Tomas Janousek
adb363a480 CONTRIBUTING: Add missing periods
Related: https://github.com/xmonad/xmonad/issues/341
2022-06-13 11:03:28 +02:00
Yecine Megdiche
3d65a37c7e CONTRIBUTING: Drop "Rebasing and Squashing Commits" in favor of online docs
Single-commit pull requests are discouraged by the core team now, so
drop that section and just refer to cbeams a kernel docs instead.

Related: https://github.com/xmonad/xmonad/issues/341
2022-06-13 10:59:41 +02:00
Tony Zorman
54d921c5a6 TUTORIAL: Only user letter keys for the bindings
On IRC an issue came up where a user couldn't press M-] since they were
using a German keyboard layout and ] is put behind some AltGr
combination.  The correct way to specify that would be something along
the lines of M-M5-9, but we don't really want to go into that in the
tutorial.  Thus, only use the obvious modifiers, as well as letters,
which should work on every layout.
2022-05-17 18:31:01 +02:00
brandon s allbery kf8nh
f61fdbaf0c MAINTAINERS: Add geekosaur's GPG key 2022-05-15 22:08:38 +02:00
Tomas Janousek
d88643c639 TUTORIAL: Fix broken link to liskin's xmobarrc
I switched to compiled config as well now:
d816717dee
2022-05-09 11:59:21 +01:00
Tony Zorman
eaaf0aafcd stack: Bump default resolver to 19.6
No impact on CI, just makes it easier for contributors to use the latest
9.0 GHC.
2022-05-07 09:40:56 +02:00
Tony Zorman
23df88d778 Merge pull request #392 from LSLeary/flake-module
Apply Patch in Nix Flake; Enable Configuration
2022-05-04 20:46:41 +02:00
L. S. Leary
90d0ca4a2e flake.nix: Point to xmonad-contrib/NIX.md. Assume maintainership. 2022-05-04 19:18:11 +12:00
L. S. Leary
f3f0c712d8 flake.nix: Configure the flake via NixOS module. 2022-04-25 01:58:46 +12:00
L. S. Leary
a5b708ba00 flake.nix: Provide the hoverlay and a version of fromHOL taking a
compiler argument, as well as the hpath function that does the work.
2022-04-25 01:46:39 +12:00
L. S. Leary
6fc90cd9d3 flake.nix: Bring in the patch from unstable.
Separate the noise from the overlay, exporting it as lib.fromHOL for reuse
in the xmonad-contrib flake.
2022-04-25 01:40:53 +12:00
Tony Zorman
3009304352 Merge pull request #391 from geekosaur/document-build-scripts
document build scripts
2022-04-19 19:39:54 +02:00
brandon s allbery kf8nh
5dd964e109 document build scripts
Requires https://github.com/xmonad/xmonad-contrib/pull/710
2022-04-18 17:24:51 -04:00
Tomas Janousek
90c719148b ci: Discard old caches to fix build failures
We're getting "undefined reference" errors during linking, suggesting
some build artifacts in the cache are stale and need to be rebuilt.
2022-04-18 23:24:09 +02:00
slotThe
831ca49331 X.StackSet: Add links to references 2022-04-12 12:48:35 +02:00
slotThe
2c9e24e0f6 X.StackSet: Give all functions their own Haddock comments
So far, some functions like focus{Up,Down} and swap{Up,Down} had
combined Haddock comments, since giving each their own would result in
some duplication of information.  This is nicer when reading the source,
but Haddock can't really handle this when generating the HTML page,
which will come out a bit garbled in that case.  Since a lot of users
may be only reading the Haddocks, we should prefer this to a source with
as few redundancies as possible.

Closes: https://github.com/xmonad/xmonad/issues/387
2022-04-12 11:42:01 +02:00
slotThe
a854cdaf9b ci: Update supported GHC versions
+ Prefer GHC 8.10.7 to 8.10.4, as versions seem to have stabilised now.
+ Add support for Stackage LTS 19; this ships with GHC 9.0.2.
+ Since a new version of 9.2 has been released, prefer 9.2.2 over 9.2.1.

Related: https://github.com/xmonad/xmonad-contrib/pull/694
2022-04-05 14:33:23 +02:00
slotThe
c2904425e9 TUTORIAL: Fix typos
Mostly capitalising names that ought to be capitalised.
2022-03-11 15:49:01 +01:00
slotThe
89ea1356c1 INSTALL.md: Add Void dependencies
Since things like `ncurses-libtinfo` are needed due to [1], it makes
sense to have all dependencies spelled out explicitly.

Related: https://github.com/xmonad/xmonad-web/issues/57
[1]: https://github.com/void-linux/void-packages/issues/7403
2022-03-08 09:23:41 +01:00
Tomas Janousek
f4a5b88e64 MAINTAINERS: Tweak the release procedure a bit
* Clarify what needs to be done with CHANGES.md
* Early release announcement preparation
2022-02-14 18:15:53 +00:00
Tomas Janousek
906b9d34b3 X.Operations: Whitespace cleanup after #371 2022-02-01 18:12:05 +00:00
Tomas Janousek
c537a0658a CHANGES: Add missing entry for #350 2022-02-01 18:09:12 +00:00
Tony Zorman
8546ea095b Merge pull request #371 from andrea-berling/will-float
Add function to detect floating windows in ManageHook
2022-01-26 11:22:53 +01:00
Andrea Berlingieri
c2e632a2b9 Factor our common logic for floating windows
Factour out the code used to detect whether a window should be floating
in Operations.hs in a new function named isFixedSizeOrTransient

Modify willFloat to use the factored out code from Opeartions.hs
2022-01-22 23:00:41 +01:00
Andrea Berlingieri
b6af6bb86a Add function to detect floating windows in ManageHook
Add a willFloat function to deteect whether the managed window will be
floating or not

Add description of added change to CHANGES.md
2022-01-22 21:56:53 +01:00
Tomas Janousek
eee0a0dc39 flake.nix: Use upstream gitignore.nix instead of Ivan's fork 2022-01-15 12:03:35 +00:00
Mike Nrafter
0f5b5c2297 Fixed flake.nix's use of GitIgnore. 2022-01-12 19:58:52 -07:00
slotThe
e25d090112 cabal: Add myself to authors
About time, I suppose :)
2021-12-11 19:54:05 +01:00
Tony Zorman
eb2ee340e4 Merge pull request #352 from slotThe/custom-cursor
X.Operations: Use custom cursor for dragging/resizing
2021-12-01 12:11:29 +01:00
slotThe
79278d9475 X.Operations: Use custom cursor for dragging/resizing
When dragging and resizing windows, users may expect the cursor to
change to indicate the respective behaviour.  In particular, many other
window managers already do this [1] [2].

Thus, introduce a new (non-exported) `mouseDragCursor` function that
takes a cursor shape and change the generic resize and move functions to
use that.  The reason that we don't change `mouseDrag` itself (for now)
is that this is exported and quite a few contrib modules use it—breaking
compatibility with xmonad-0.17.0 so soon after the release seems unwise.

Fixes: https://github.com/xmonad/xmonad/issues/348

[1]: https://git.suckless.org/dwm/file/dwm.c.html#l1567
[2]: 7a8fa9d27a/lib/awful/mouse/resize.lua (L23)
2021-11-25 09:12:22 +01:00
Tony Zorman
dbe9c4f799 Merge pull request #350 from Thiago4532/opaque-window-border
X.Operations: Make window borders opaque
2021-11-22 17:49:44 +01:00
Tomas Janousek
f6e4e278b5 README: Make spaces _not_ part of hyperlinks 2021-11-22 11:28:19 +00:00
Tomas Janousek
673de33436 README: Add badges for IRC and Matrix 2021-11-22 11:26:20 +00:00
slotThe
a5b6e09985 INSTALL.md: Remind users to check which xmonad
We have had this situation happen a few times now: users update
xmonad (say, to 0.17.0) but forget that they still have an older version
installed via the distributions repositories.  Features that depend on
the "bootstrap" xmonad executable to be updated (like the improved XDG
support) then fail badly.

Thus, remind users to check whether the right executable is present.
2021-11-20 11:26:18 +01:00
Thiago Mota
bb448cc293 X.Operations: Make window borders opaque 2021-11-18 19:53:05 -03:00
slotThe
ae4c5e26be MAINTAINERS.md: Add PGP key for slotThe
Also, move the bullet point to the end so as to keep the list
alphabetically ordered.
2021-11-17 19:34:38 +01:00
slotThe
54df2e9acd INSTALL.md: Mention more packages as dependencies for Arch
At least on Arch, none of the listed packages necessarily require that
the user has a working Xorg setup—this has already caused some confusion
for people.  In particular, xmessage is very much needed in order to
show warnings and compilation errors.
2021-11-14 13:26:34 +01:00
Tomas Janousek
7f6d758ce5 MAINTAINERS: Update the Hackage release step 2021-11-08 18:15:07 +00:00
Tomas Janousek
9f64c2ca90 ci: Show error body from Hackage when it fails
Prevents having to upload the candidate manually to see what's wrong.
2021-11-08 18:14:52 +00:00
Tomas Janousek
9849800dc5 ci: Swap candidate/final release logic
During the release of xmonad 0.17.0, I realized that we need to be able
to upload candidates before tagging the release on GitHub, because there
might be issues with the tarball and Hackage may reject it. When that
happened, I had to remove the release, delete the tag, upload the
candidate manually to see what's wrong with it, try to fix it, upload it
manually again, and so on.

This commit swaps the logic: when the workflow is invoked manually, it
uploads the candidate. This can be done multiple times, and once
everything is fine, the release can finally be tagged and it's released
to Hackage proper. The only disadvantage is that we need to remember to
try uploading the candidate. Not sure if there's a perfect solution…
2021-11-08 18:13:23 +00:00
Tomáš Janoušek
a902fefaf1 Merge pull request #346 from liskin/ghc92
Test against GHC 9.2.1; fix new warnings
2021-11-04 11:05:14 +00:00
Tomas Janousek
0f708e76b1 Fix -Wnoncanonical-monad-instances, -Wnoncanonical-monoid-instances 2021-10-31 11:53:42 +00:00
Tomas Janousek
6e6f562b0d Fix Pattern match(es) are non-exhaustive warnings
Many of these are legitimate, like the one in rescreen where it really
can be empty and xmonad might crash. Or the one in Main, where using an
irrefutable pattern means a pattern-match failure isn't reported using
the MonadFail instance of IO, but is left to crash later when the thunk
is evaluated.

Others are just GHC not knowing it won't crash, and we can use
Data.List.NonEmpty to tell it.
2021-10-31 11:41:53 +00:00
Tomas Janousek
12d1b31d6c ci: Test against GHC 9.2 2021-10-31 11:04:27 +00:00
Tomas Janousek
b92bd28d97 ci: Update haskell-ci 2021-10-31 10:52:58 +00:00
Tomáš Janoušek
e1daf46c75 Merge pull request #344 from slotThe/dont-print-getWindowAttributes
X.Operations: Silently catch in setWindowBorderWithFallback
2021-10-29 12:24:30 +01:00
slotThe
a8e1249ba7 stack: Bump default resolver to 18.14
No impact on CI, just makes it easier for contributors to use the latest
8.10 GHC.

Related: xmonad-contrib@f5f6ef41cb6cce3ba14957c31640f10b5751c90c
2021-10-29 11:00:04 +02:00
slotThe
e3824687c7 X.Operations: Silently catch in setWindowBorderWithFallback
While we catch the exception that `getWindowAttributes` can throw in
`setWindowBorderWithFallback`, we immediately turn around and print the
error to stderr.  Since this exception is raised every time a window is
closed[1] , it clutters stderr and may even confuse users as to why
xmonad is throwing these exceptions.

[1]: Depending on how the window is closed, we either have no way of
running `windows` on our own (say, the window is closed by a keybinding
of the program itself), or the focus change (and thus the call to
`windows`) runs before we can handle the DestroyWindowEvent.
2021-10-29 09:43:13 +02:00
Tomas Janousek
c979ee67c0 MAINTAINERS: Update release procedure (dev version bump, …) 2021-10-28 18:06:13 +01:00
Tomas Janousek
6c92dd22ad Bump version to 0.17.0.9 and prepare CHANGES.md sections
We need to bump the version early to avoid overwriting
https://xmonad.github.io/xmonad-docs/xmonad-0.17.0/
2021-10-28 18:05:15 +01:00
slotThe
66ac855959 TUTORIAL.md: Fix link for Libera webchat
The kiwiirc webchat was superseded by one hosted directly by the Libera
project.
2021-10-27 19:22:30 +02:00
37 changed files with 743 additions and 535 deletions

View File

@@ -2,14 +2,16 @@ Piggy-back on the haskell-ci workflow for automatic releases to Hackage.
This extends the workflow with two additional triggers: This extends the workflow with two additional triggers:
* When a release is created on GitHub, a candidate release is uploaded to * When the Haskell-CI workflow is triggered manually with a non-empty version
Hackage and docs are submitted for it as Hackage can't build them itself input (matching the version in the cabal file), a candidate release is
(https://github.com/haskell/hackage-server/issues/925). uploaded to Hackage and docs are submitted for it as Hackage can't build
them itself (https://github.com/haskell/hackage-server/issues/925).
* To make a final release, the workflow can be triggered manually by entering Note that promoting the candidate on Hackage discards the uploaded docs
the correct version number matching the version in the cabal file. This is (https://github.com/haskell/hackage-server/issues/70). Don't do that.
here because promoting the candidate on Hackage discards the uploaded docs
(https://github.com/haskell/hackage-server/issues/70). * When a release is created on GitHub, a final release is uploaded to Hackage
and docs are submitted for it.
The automation uses a special Hackage user: https://hackage.haskell.org/user/xmonad The automation uses a special Hackage user: https://hackage.haskell.org/user/xmonad
and each repo (X11, xmonad, xmonad-contrib) has its own HACKAGE_API_KEY token and each repo (X11, xmonad, xmonad-contrib) has its own HACKAGE_API_KEY token
@@ -17,7 +19,7 @@ set in GitHub repository secrets.
--- .github/workflows/haskell-ci.yml.orig --- .github/workflows/haskell-ci.yml.orig
+++ .github/workflows/haskell-ci.yml +++ .github/workflows/haskell-ci.yml
@@ -14,8 +14,17 @@ @@ -14,8 +14,15 @@
# #
name: Haskell-CI name: Haskell-CI
on: on:
@@ -31,21 +33,19 @@ set in GitHub repository secrets.
+ workflow_dispatch: + workflow_dispatch:
+ inputs: + inputs:
+ version: + version:
+ # releases to Hackage are final and cannot be reverted, thus require + description: candidate version (must match version in cabal file)
+ # manual entry of version as a poor man's mistake avoidance
+ description: version (must match version in cabal file)
jobs: jobs:
linux: linux:
name: Haskell-CI - Linux - ${{ matrix.compiler }} name: Haskell-CI - Linux - ${{ matrix.compiler }}
@@ -28,6 +37,7 @@ @@ -31,6 +38,7 @@
include: compilerVersion: 9.0.2
- compiler: ghc-9.0.1 setup-method: ghcup
allow-failure: false allow-failure: false
+ upload: true + upload: true
- compiler: ghc-8.10.4 - compiler: ghc-8.10.7
allow-failure: false compilerKind: ghc
- compiler: ghc-8.8.4 compilerVersion: 8.10.7
@@ -171,8 +181,66 @@ @@ -209,8 +217,80 @@
${CABAL} -vnormal check ${CABAL} -vnormal check
- name: haddock - name: haddock
run: | run: |
@@ -66,50 +66,64 @@ set in GitHub repository secrets.
+ with: + with:
+ path: ${{ github.workspace }}/haddock/*-docs.tar.gz + path: ${{ github.workspace }}/haddock/*-docs.tar.gz
+ - name: hackage upload (candidate) + - name: hackage upload (candidate)
+ if: matrix.upload && github.event_name == 'release' + if: matrix.upload && github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
+ shell: bash
+ run: | + run: |
+ set -ex + set -ex
+ PACKAGE_VERSION="${PACKAGE_VERSION#v}" + PACKAGE_VERSION="${PACKAGE_VERSION#v}"
+ curl \ + res=$(
+ --silent --show-error --fail \ + curl \
+ --header "Accept: text/plain" \ + --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \ + --header "Accept: text/plain" \
+ --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \ + --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
+ https://hackage.haskell.org/packages/candidates/ + --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
+ curl \ + https://hackage.haskell.org/packages/candidates/
+ --silent --show-error --fail \ + )
+ -X PUT \ + [[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
+ --header "Accept: text/plain" \ + res=$(
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \ + curl \
+ --header "Content-Type: application/x-tar" \ + --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
+ --header "Content-Encoding: gzip" \ + -X PUT \
+ --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \ + --header "Accept: text/plain" \
+ https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/candidate/docs + --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
+ env: + --header "Content-Type: application/x-tar" \
+ HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }} + --header "Content-Encoding: gzip" \
+ PACKAGE_NAME: ${{ github.event.repository.name }} + --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
+ PACKAGE_VERSION: ${{ github.event.release.tag_name }} + https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/candidate/docs
+ - name: hackage upload (release) + )
+ if: matrix.upload && github.event_name == 'workflow_dispatch' + [[ $res == 2?? ]]
+ run: |
+ set -ex
+ PACKAGE_VERSION="${PACKAGE_VERSION#v}"
+ curl \
+ --silent --show-error --fail \
+ --header "Accept: text/plain" \
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
+ --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
+ https://hackage.haskell.org/packages/
+ curl \
+ --silent --show-error --fail \
+ -X PUT \
+ --header "Accept: text/plain" \
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
+ --header "Content-Type: application/x-tar" \
+ --header "Content-Encoding: gzip" \
+ --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
+ https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/docs
+ env: + env:
+ HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }} + HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
+ PACKAGE_NAME: ${{ github.event.repository.name }} + PACKAGE_NAME: ${{ github.event.repository.name }}
+ PACKAGE_VERSION: ${{ github.event.inputs.version }} + PACKAGE_VERSION: ${{ github.event.inputs.version }}
+ - name: hackage upload (release)
+ if: matrix.upload && github.event_name == 'release'
+ shell: bash
+ run: |
+ set -ex
+ PACKAGE_VERSION="${PACKAGE_VERSION#v}"
+ res=$(
+ curl \
+ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
+ --header "Accept: text/plain" \
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
+ --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
+ https://hackage.haskell.org/packages/
+ )
+ [[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
+ res=$(
+ curl \
+ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
+ -X PUT \
+ --header "Accept: text/plain" \
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
+ --header "Content-Type: application/x-tar" \
+ --header "Content-Encoding: gzip" \
+ --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
+ https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/docs
+ )
+ [[ $res == 2?? ]]
+ env:
+ HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
+ PACKAGE_NAME: ${{ github.event.repository.name }}
+ PACKAGE_VERSION: ${{ github.event.release.tag_name }}

View File

@@ -8,9 +8,9 @@
# #
# For more information, see https://github.com/haskell-CI/haskell-ci # For more information, see https://github.com/haskell-CI/haskell-ci
# #
# version: 0.12 # version: 0.14.3
# #
# REGENDATA ("0.12",["github","cabal.project"]) # REGENDATA ("0.14.3",["github","cabal.project"])
# #
name: Haskell-CI name: Haskell-CI
on: on:
@@ -22,63 +22,109 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
# releases to Hackage are final and cannot be reverted, thus require description: candidate version (must match version in cabal file)
# manual entry of version as a poor man's mistake avoidance
description: version (must match version in cabal file)
jobs: jobs:
linux: linux:
name: Haskell-CI - Linux - ${{ matrix.compiler }} name: Haskell-CI - Linux - ${{ matrix.compiler }}
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
timeout-minutes:
60
container: container:
image: buildpack-deps:bionic image: buildpack-deps:bionic
continue-on-error: ${{ matrix.allow-failure }} continue-on-error: ${{ matrix.allow-failure }}
strategy: strategy:
matrix: matrix:
include: include:
- compiler: ghc-9.0.1 - compiler: ghc-9.2.2
compilerKind: ghc
compilerVersion: 9.2.2
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.0.2
compilerKind: ghc
compilerVersion: 9.0.2
setup-method: ghcup
allow-failure: false allow-failure: false
upload: true upload: true
- compiler: ghc-8.10.4 - compiler: ghc-8.10.7
compilerKind: ghc
compilerVersion: 8.10.7
setup-method: ghcup
allow-failure: false allow-failure: false
- compiler: ghc-8.8.4 - compiler: ghc-8.8.4
compilerKind: ghc
compilerVersion: 8.8.4
setup-method: hvr-ppa
allow-failure: false allow-failure: false
- compiler: ghc-8.6.5 - compiler: ghc-8.6.5
compilerKind: ghc
compilerVersion: 8.6.5
setup-method: hvr-ppa
allow-failure: false allow-failure: false
- compiler: ghc-8.4.4 - compiler: ghc-8.4.4
compilerKind: ghc
compilerVersion: 8.4.4
setup-method: hvr-ppa
allow-failure: false allow-failure: false
fail-fast: false fail-fast: false
steps: steps:
- name: apt - name: apt
run: | run: |
apt-get update apt-get update
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
apt-add-repository -y 'ppa:hvr/ghc' if [ "${{ matrix.setup-method }}" = ghcup ]; then
apt-get update mkdir -p "$HOME/.ghcup/bin"
apt-get install -y $CC cabal-install-3.4 libx11-dev libxext-dev libxinerama-dev libxrandr-dev libxss-dev curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
apt-get update
apt-get install -y libx11-dev libxext-dev libxinerama-dev libxrandr-dev libxss-dev
else
apt-add-repository -y 'ppa:hvr/ghc'
apt-get update
apt-get install -y "$HCNAME" libx11-dev libxext-dev libxinerama-dev libxrandr-dev libxss-dev
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
fi
env: env:
CC: ${{ matrix.compiler }} HCKIND: ${{ matrix.compilerKind }}
HCNAME: ${{ matrix.compiler }}
HCVER: ${{ matrix.compilerVersion }}
- name: Set PATH and environment variables - name: Set PATH and environment variables
run: | run: |
echo "$HOME/.cabal/bin" >> $GITHUB_PATH echo "$HOME/.cabal/bin" >> $GITHUB_PATH
echo "LANG=C.UTF-8" >> $GITHUB_ENV echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
echo "CABAL_DIR=$HOME/.cabal" >> $GITHUB_ENV echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
echo "CABAL_CONFIG=$HOME/.cabal/config" >> $GITHUB_ENV echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
HCDIR=$(echo "/opt/$CC" | sed 's/-/\//') HCDIR=/opt/$HCKIND/$HCVER
HCNAME=ghc if [ "${{ matrix.setup-method }}" = ghcup ]; then
HC=$HCDIR/bin/$HCNAME HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
echo "HC=$HC" >> $GITHUB_ENV echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCDIR/bin/$HCNAME-pkg" >> $GITHUB_ENV echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
echo "HADDOCK=$HCDIR/bin/haddock" >> $GITHUB_ENV echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> $GITHUB_ENV echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
else
HC=$HCDIR/bin/$HCKIND
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
fi
HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
echo "HCNUMVER=$HCNUMVER" >> $GITHUB_ENV echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
echo "ARG_TESTS=--enable-tests" >> $GITHUB_ENV echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
echo "ARG_BENCH=--enable-benchmarks" >> $GITHUB_ENV echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
echo "HEADHACKAGE=false" >> $GITHUB_ENV echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
echo "ARG_COMPILER=--$HCNAME --with-compiler=$HC" >> $GITHUB_ENV echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
echo "GHCJSARITH=0" >> $GITHUB_ENV echo "GHCJSARITH=0" >> "$GITHUB_ENV"
env: env:
CC: ${{ matrix.compiler }} HCKIND: ${{ matrix.compilerKind }}
HCNAME: ${{ matrix.compiler }}
HCVER: ${{ matrix.compilerVersion }}
- name: env - name: env
run: | run: |
env env
@@ -101,6 +147,10 @@ jobs:
repository hackage.haskell.org repository hackage.haskell.org
url: http://hackage.haskell.org/ url: http://hackage.haskell.org/
EOF EOF
cat >> $CABAL_CONFIG <<EOF
program-default-options
ghc-options: $GHCJOBS +RTS -M3G -RTS
EOF
cat $CABAL_CONFIG cat $CABAL_CONFIG
- name: versions - name: versions
run: | run: |
@@ -110,6 +160,11 @@ jobs:
- name: update cabal index - name: update cabal index
run: | run: |
$CABAL v2-update -v $CABAL v2-update -v
- name: cache (tools)
uses: actions/cache@v2
with:
key: ${{ runner.os }}-${{ matrix.compiler }}-tools-c0dbbd39
path: ~/.haskell-ci-tools
- name: install cabal-plan - name: install cabal-plan
run: | run: |
mkdir -p $HOME/.cabal/bin mkdir -p $HOME/.cabal/bin
@@ -119,6 +174,12 @@ jobs:
rm -f cabal-plan.xz rm -f cabal-plan.xz
chmod a+x $HOME/.cabal/bin/cabal-plan chmod a+x $HOME/.cabal/bin/cabal-plan
cabal-plan --version cabal-plan --version
- name: install hlint
run: |
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then HLINTVER=$(cd /tmp && (${CABAL} v2-install -v $ARG_COMPILER --dry-run hlint --constraint='hlint >=3.4 && <3.5' | perl -ne 'if (/\bhlint-(\d+(\.\d+)*)\b/) { print "$1"; last; }')); echo "HLint version $HLINTVER" ; fi
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then if [ ! -e $HOME/.haskell-ci-tools/hlint-$HLINTVER/hlint ]; then echo "Downloading HLint version $HLINTVER"; mkdir -p $HOME/.haskell-ci-tools; curl --write-out 'Status Code: %{http_code} Redirects: %{num_redirects} Total time: %{time_total} Total Dsize: %{size_download}\n' --silent --location --output $HOME/.haskell-ci-tools/hlint-$HLINTVER.tar.gz "https://github.com/ndmitchell/hlint/releases/download/v$HLINTVER/hlint-$HLINTVER-x86_64-linux.tar.gz"; tar -xzv -f $HOME/.haskell-ci-tools/hlint-$HLINTVER.tar.gz -C $HOME/.haskell-ci-tools; fi ; fi
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then mkdir -p $CABAL_DIR/bin && ln -sf "$HOME/.haskell-ci-tools/hlint-$HLINTVER/hlint" $CABAL_DIR/bin/hlint ; fi
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then hlint --version ; fi
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
@@ -139,7 +200,8 @@ jobs:
- name: generate cabal.project - name: generate cabal.project
run: | run: |
PKGDIR_xmonad="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/xmonad-[0-9.]*')" PKGDIR_xmonad="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/xmonad-[0-9.]*')"
echo "PKGDIR_xmonad=${PKGDIR_xmonad}" >> $GITHUB_ENV echo "PKGDIR_xmonad=${PKGDIR_xmonad}" >> "$GITHUB_ENV"
rm -f cabal.project cabal.project.local
touch cabal.project touch cabal.project
touch cabal.project.local touch cabal.project.local
echo "packages: ${PKGDIR_xmonad}" >> cabal.project echo "packages: ${PKGDIR_xmonad}" >> cabal.project
@@ -177,6 +239,10 @@ jobs:
- name: tests - name: tests
run: | run: |
$CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
- name: hlint
run: |
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then (cd ${PKGDIR_xmonad} && hlint -h ${GITHUB_WORKSPACE}/source/.hlint.yaml -XHaskell2010 src) ; fi
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then (cd ${PKGDIR_xmonad} && hlint -h ${GITHUB_WORKSPACE}/source/.hlint.yaml -XHaskell2010 .) ; fi
- name: cabal check - name: cabal check
run: | run: |
cd ${PKGDIR_xmonad} || false cd ${PKGDIR_xmonad} || false
@@ -199,50 +265,64 @@ jobs:
with: with:
path: ${{ github.workspace }}/haddock/*-docs.tar.gz path: ${{ github.workspace }}/haddock/*-docs.tar.gz
- name: hackage upload (candidate) - name: hackage upload (candidate)
if: matrix.upload && github.event_name == 'release' if: matrix.upload && github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
shell: bash
run: | run: |
set -ex set -ex
PACKAGE_VERSION="${PACKAGE_VERSION#v}" PACKAGE_VERSION="${PACKAGE_VERSION#v}"
curl \ res=$(
--silent --show-error --fail \ curl \
--header "Accept: text/plain" \ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \ --header "Accept: text/plain" \
--form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
https://hackage.haskell.org/packages/candidates/ --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
curl \ https://hackage.haskell.org/packages/candidates/
--silent --show-error --fail \ )
-X PUT \ [[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
--header "Accept: text/plain" \ res=$(
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \ curl \
--header "Content-Type: application/x-tar" \ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
--header "Content-Encoding: gzip" \ -X PUT \
--data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \ --header "Accept: text/plain" \
https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/candidate/docs --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
env: --header "Content-Type: application/x-tar" \
HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }} --header "Content-Encoding: gzip" \
PACKAGE_NAME: ${{ github.event.repository.name }} --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
PACKAGE_VERSION: ${{ github.event.release.tag_name }} https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/candidate/docs
- name: hackage upload (release) )
if: matrix.upload && github.event_name == 'workflow_dispatch' [[ $res == 2?? ]]
run: |
set -ex
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
curl \
--silent --show-error --fail \
--header "Accept: text/plain" \
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
--form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
https://hackage.haskell.org/packages/
curl \
--silent --show-error --fail \
-X PUT \
--header "Accept: text/plain" \
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
--header "Content-Type: application/x-tar" \
--header "Content-Encoding: gzip" \
--data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/docs
env: env:
HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }} HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
PACKAGE_NAME: ${{ github.event.repository.name }} PACKAGE_NAME: ${{ github.event.repository.name }}
PACKAGE_VERSION: ${{ github.event.inputs.version }} PACKAGE_VERSION: ${{ github.event.inputs.version }}
- name: hackage upload (release)
if: matrix.upload && github.event_name == 'release'
shell: bash
run: |
set -ex
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
res=$(
curl \
--silent --show-error --output /dev/stderr --write-out '%{http_code}' \
--header "Accept: text/plain" \
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
--form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
https://hackage.haskell.org/packages/
)
[[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
res=$(
curl \
--silent --show-error --output /dev/stderr --write-out '%{http_code}' \
-X PUT \
--header "Accept: text/plain" \
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
--header "Content-Type: application/x-tar" \
--header "Content-Encoding: gzip" \
--data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/docs
)
[[ $res == 2?? ]]
env:
HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
PACKAGE_NAME: ${{ github.event.repository.name }}
PACKAGE_VERSION: ${{ github.event.release.tag_name }}

View File

@@ -38,3 +38,12 @@ jobs:
--preferred \ --preferred \
--exclude X11 \ --exclude X11 \
*.cabal *.cabal
workflow-keepalive:
runs-on: ubuntu-latest
steps:
- name: Re-enable workflow
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api -X PUT repos/${{ github.repository }}/actions/workflows/packdeps.yml/enable

View File

@@ -18,10 +18,10 @@ jobs:
ghc: 8.6.5 ghc: 8.6.5
- resolver: lts-16 - resolver: lts-16
ghc: 8.8.4 ghc: 8.8.4
- resolver: lts-17
ghc: 8.10.4
- resolver: lts-18 - resolver: lts-18
ghc: 8.10.7 ghc: 8.10.7
- resolver: lts-19
ghc: 9.0.2
steps: steps:
- name: Clone project - name: Clone project
@@ -55,17 +55,17 @@ jobs:
- name: Refresh caches once a month - name: Refresh caches once a month
id: cache-date id: cache-date
# GHA writes caches on the first miss and then never updates them again; # GHA writes caches on the first miss and then never updates them again;
# force updating the cache at least once a month # force updating the cache at least once a month. Additionally, the
# date is prefixed with an epoch number to let us manually refresh the
# cache when needed. This is a workaround for https://github.com/actions/cache/issues/2
run: | run: |
echo "::set-output name=date::$(date +%Y-%m)" echo "::set-output name=date::1-$(date +%Y-%m)"
- name: Cache Haskell package metadata - name: Cache Haskell package metadata
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.stack/pantry path: ~/.stack/pantry
key: stack-pantry-${{ runner.os }}-${{ steps.cache-date.outputs.date }} key: stack-pantry-${{ runner.os }}-${{ steps.cache-date.outputs.date }}
restore-keys: |
stack-pantry-${{ runner.os }}-
- name: Cache Haskell dependencies - name: Cache Haskell dependencies
uses: actions/cache@v2 uses: actions/cache@v2
@@ -78,7 +78,6 @@ jobs:
restore-keys: | restore-keys: |
stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}-${{ hashFiles('stack.yaml') }}- stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}-${{ hashFiles('stack.yaml') }}-
stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}- stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}-
stack-${{ runner.os }}-${{ matrix.resolver }}-
- name: Update hackage index - name: Update hackage index
# always update index to prevent the shared ~/.stack/pantry cache from being empty # always update index to prevent the shared ~/.stack/pantry cache from being empty

2
.hlint.yaml Normal file
View File

@@ -0,0 +1,2 @@
# Ignore these warnings.
- ignore: {name: "Use camelCase"}

View File

@@ -1,5 +1,25 @@
# Change Log / Release Notes # Change Log / Release Notes
## 0.17.1 (September 3, 2021)
### Enhancements
* Added custom cursor shapes for resizing and moving windows.
* Exported `cacheNumlockMask` and `mkGrabs` from `XMonad.Operations`.
### Bug Fixes
* Fixed border color of windows with alpha channel. Now all windows have the
same opaque border color.
* Change the main loop to try to avoid [GHC bug 21708] on systems
running GHC 9.2 up to version 9.2.3. The issue has been fixed in
[GHC 9.2.4] and all later releases.
[GHC bug 21708]: https://gitlab.haskell.org/ghc/ghc/-/issues/21708
[GHC 9.2.4]: https://discourse.haskell.org/t/ghc-9-2-4-released/4851
## 0.17.0 (October 27, 2021) ## 0.17.0 (October 27, 2021)
### Enhancements ### Enhancements
@@ -45,6 +65,9 @@
* Added `withUnfocused` function to `XMonad.Operations`, allowing for `X` * Added `withUnfocused` function to `XMonad.Operations`, allowing for `X`
operations to be applied to unfocused windows. operations to be applied to unfocused windows.
* Added `willFloat` function to `XMonad.ManageHooks` to detect whether the
(about to be) managed window will be a floating window or not
[these build scripts]: https://github.com/xmonad/xmonad-testing/tree/master/build-scripts [these build scripts]: https://github.com/xmonad/xmonad-testing/tree/master/build-scripts
### Bug Fixes ### Bug Fixes

View File

@@ -70,8 +70,11 @@ Here are some tips for getting your changes merged into xmonad:
and [xmonad][] have test-suites that you could run with and [xmonad][] have test-suites that you could run with
`stack test` for example. `stack test` for example.
* Make sure you read the section on rebasing and squashing commits * When committing, try to follow existing practices. For more
below. information on what good commit messages look like, see [How to
Write a Git Commit Message][commit-cbeams] and the [Kernel
documentation][commit-kernel] about committing logical changes
separately.
## Style Guidelines ## Style Guidelines
@@ -83,7 +86,7 @@ as well!
and provide a type signature; use Haddock syntax in the comments. and provide a type signature; use Haddock syntax in the comments.
* Follow the coding style of the module that you are making changes to * Follow the coding style of the module that you are making changes to
(`n` spaces for indentation, where to break long type signatures, …) (`n` spaces for indentation, where to break long type signatures, …).
* New code should not introduce any new warnings. If you want to * New code should not introduce any new warnings. If you want to
check this yourself before submitting a pull request, there is the check this yourself before submitting a pull request, there is the
@@ -95,7 +98,7 @@ as well!
enforced in our GitHub CI. enforced in our GitHub CI.
* Partial functions are to be avoided: the window manager should not * Partial functions are to be avoided: the window manager should not
crash, so do not call `error` or `undefined` crash, so do not call `error` or `undefined`.
* Any pure function added to the core should have QuickCheck * Any pure function added to the core should have QuickCheck
properties precisely defining its behavior. properties precisely defining its behavior.
@@ -103,84 +106,15 @@ as well!
* New modules should identify the author, and be submitted under the * New modules should identify the author, and be submitted under the
same license as xmonad (BSD3 license). same license as xmonad (BSD3 license).
## Rebasing and Squashing Commits ## Keep rocking!
Under no circumstances should you ever merge the master branch into xmonad is a passion project created and maintained by the community.
your feature branch. This makes it nearly impossible to review your We'd love for you to maintain your own contributed modules (approve
changes and we *will not accept your PR* if you do this. changes from other contributors, review code, etc.). However, before
we'd be comfortable adding you to the [xmonad GitHub
Instead of merging you should rebase your changes on top of the master organization][xmonad-gh-org] we need to trust that you have sufficient
branch. If a core team member asks you to "rebase your changes" this knowledge of Haskell and git; and have a way of chatting with you ([IRC,
is what they are talking about. Matrix, etc.][community]).
It's also helpful to squash all of your commits so that your pull
request only contains a single commit. Again, this makes it easier to
review your changes and identify the changes later on in the Git
history.
### How to Rebase Your Changes
The goal of rebasing is to bring recent changes from the master branch
into your feature branch. This often helps resolve conflicts where
you have changed a file that also changed in a recently merged pull
request (i.e. the `CHANGES.md` file). Here is how you do that.
1. Make sure that you have a `git remote` configured for the main
repository. I like to call this remote `upstream`:
```shell
$ git remote add upstream https://github.com/xmonad/xmonad-contrib.git
```
2. Pull from upstream and rewrite your changes on top of master. For
this to work you should not have any modified files in your
working directory. Run these commands from within your feature
branch (the branch you are asking to be merged):
```shell
$ git fetch --all
$ git pull --rebase upstream master
```
3. If the rebase was successful you can now push your feature branch
back to GitHub. You need to force the push since your commits
have been rewritten and have new IDs:
```shell
$ git push --force-with-lease
```
4. Your pull request should now be conflict-free and only contain the
changes that you actually made.
### How to Squash Commits
The goal of squashing commits is to produce a clean Git history where
each pull request contains just one commit.
1. Use `git log` to see how many commits you are including in your
pull request. (If you've already submitted your pull request you
can see this in the GitHub interface.)
2. Rebase all of those commits into a single commit. Assuming you
want to squash the last four (4) commits into a single commit:
```shell
$ git rebase -i HEAD~4
```
3. Git will open your editor and display the commits you are
rebasing with the word "pick" in front of them.
4. Leave the first listed commit as "pick" and change the remaining
commits from "pick" to "squash".
5. Save the file and exit your editor. Git will create a new commit
and open your editor so you can modify the commit message.
6. If everything was successful you can push your changed history
back up to GitHub:
```shell
$ git push --force-with-lease
```
[hlint]: https://github.com/ndmitchell/hlint [hlint]: https://github.com/ndmitchell/hlint
[xmonad]: https://github.com/xmonad/xmonad [xmonad]: https://github.com/xmonad/xmonad
@@ -191,3 +125,7 @@ each pull request contains just one commit.
[xmonad-doc-developing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html [xmonad-doc-developing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html
[`#xmonad` IRC channel]: https://web.libera.chat/#xmonad [`#xmonad` IRC channel]: https://web.libera.chat/#xmonad
[matrix channel]: https://matrix.to/#/#xmonad:matrix.org [matrix channel]: https://matrix.to/#/#xmonad:matrix.org
[commit-cbeams]: https://cbea.ms/git-commit/
[commit-kernel]: https://www.kernel.org/doc/html/v4.10/process/submitting-patches.html#separate-your-changes
[community]: https://xmonad.org/community.html
[xmonad-gh-org]: https://github.com/xmonad

View File

@@ -47,10 +47,21 @@ $ sudo dnf install \
``` console ``` console
$ sudo pacman -S \ $ sudo pacman -S \
> git \ > git \
> xorg-server xorg-apps xorg-xinit xorg-xmessage \
> libx11 libxft libxinerama libxrandr libxss \ > libx11 libxft libxinerama libxrandr libxss \
> pkgconf > pkgconf
``` ```
#### Void
``` console
$ sudo xbps-install \
> git \
> ncurses-libtinfo-libs ncurses-libtinfo-devel \
> libX11-devel libXft-devel libXinerama-devel libXrandr-devel libXScrnSaver-devel \
> pkg-config
```
## Preparation ## Preparation
We'll use the [XDG] directory specifications here, meaning our We'll use the [XDG] directory specifications here, meaning our
@@ -90,8 +101,8 @@ This will give you the latest `HEAD`; if you want you can also check
out a tagged release, e.g.: out a tagged release, e.g.:
``` console ``` console
$ git clone --branch v0.15 https://github.com/xmonad/xmonad $ git clone --branch v0.17.1 https://github.com/xmonad/xmonad
$ git clone --branch v0.16 https://github.com/xmonad/xmonad-contrib $ git clone --branch v0.17.1 https://github.com/xmonad/xmonad-contrib
``` ```
(Sources and binaries don't usually go into `~/.config`. In our case, (Sources and binaries don't usually go into `~/.config`. In our case,
@@ -199,7 +210,9 @@ Installing things is as easy as typing `stack install`. This will
install the correct version of GHC, as well as build all of the required install the correct version of GHC, as well as build all of the required
packages (`stack build`) and then copy the relevant executables packages (`stack build`) and then copy the relevant executables
(`xmonad`, in our case) to `~/.local/bin`. Make sure to add that (`xmonad`, in our case) to `~/.local/bin`. Make sure to add that
directory to your `$PATH`! directory to your `$PATH`! The command `which xmonad` should now return
that executable. In case it does not, check if you still have xmonad
installed via your package manager and uninstall it.
If you're getting build failures while building the `X11` package it may If you're getting build failures while building the `X11` package it may
be that you don't have the required C libraries installed. See be that you don't have the required C libraries installed. See
@@ -343,6 +356,15 @@ exec stack ghc -- \
Don't forget to mark the file as `+x`: `chmod +x build`! Don't forget to mark the file as `+x`: `chmod +x build`!
Some example build scripts for `stack` and `cabal` are provided in the
`xmonad-contrib` distribution. You can see those online in the
[scripts/build][] directory. You might wish to use these if you have
special dependencies for your `xmonad.hs`, especially with cabal as
you must use a cabal file and often a `cabal.project` to specify them;
`cabal install --lib` above generally isn't enough, and when it is
it can be difficult to keep track of when you want to replicate your
configuration on another system.
#### Don't Recompile on Every Startup #### Don't Recompile on Every Startup
By default, xmonad always recompiles itself when a build script is used By default, xmonad always recompiles itself when a build script is used
@@ -374,3 +396,4 @@ executable will also be within that directory and not in
[ghcup]: https://www.haskell.org/ghcup/ [ghcup]: https://www.haskell.org/ghcup/
[what xmonad would do]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Core.hs#L659-L667 [what xmonad would do]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Core.hs#L659-L667
[Hackage]: https://hackage.haskell.org/ [Hackage]: https://hackage.haskell.org/
[scripts/build]: https://github.com/xmonad/xmonad-contrib/blob/master/scripts/build

View File

@@ -2,7 +2,7 @@
## The XMonad Core Team ## The XMonad Core Team
* Brandon S Allbery [GitHub][geekosaur], IRC: `geekosaur` * Brandon S Allbery [GitHub][geekosaur], IRC: `geekosaur`, [GPG][gpg:geekosaur]
* Brent Yorgey [GitHub][byorgey], IRC: `byorgey` * Brent Yorgey [GitHub][byorgey], IRC: `byorgey`
@@ -10,10 +10,10 @@
* Sibi Prabakaran [GitHub][psibi], [Twitter][twitter:psibi], IRC: `sibi` * Sibi Prabakaran [GitHub][psibi], [Twitter][twitter:psibi], IRC: `sibi`
* slotThe [GitHub][slotThe], IRC: `Solid`
* Tomáš Janoušek [GitHub][liskin], [Twitter][twitter:liskin], IRC: `liskin`, [GPG][gpg:liskin] * Tomáš Janoušek [GitHub][liskin], [Twitter][twitter:liskin], IRC: `liskin`, [GPG][gpg:liskin]
* Tony Zorman [GitHub][slotThe], IRC: `Solid`, [GPG][gpg:slotThe]
[geekosaur]: https://github.com/geekosaur [geekosaur]: https://github.com/geekosaur
[byorgey]: https://github.com/byorgey [byorgey]: https://github.com/byorgey
[dmwit]: https://github.com/dmwit [dmwit]: https://github.com/dmwit
@@ -21,7 +21,9 @@
[liskin]: https://github.com/liskin [liskin]: https://github.com/liskin
[slotThe]: https://github.com/slotThe [slotThe]: https://github.com/slotThe
[gpg:geekosaur]: https://github.com/geekosaur.gpg
[gpg:liskin]: https://github.com/liskin.gpg [gpg:liskin]: https://github.com/liskin.gpg
[gpg:slotThe]: https://github.com/slotThe.gpg
[twitter:dmwit]: https://twitter.com/dmwit13 [twitter:dmwit]: https://twitter.com/dmwit13
[twitter:psibi]: https://twitter.com/psibi [twitter:psibi]: https://twitter.com/psibi
@@ -71,38 +73,40 @@ When the time comes to release another version of xmonad and xmonad-contrib:
2. Review documentation files and make sure they are accurate: 2. Review documentation files and make sure they are accurate:
- [`README.md`](README.md) - [`README.md`](README.md)
- [`CHANGES.md`](CHANGES.md) - [`CHANGES.md`](CHANGES.md) (bump version, set date)
- [`INSTALL.md`](INSTALL.md) - [`INSTALL.md`](INSTALL.md)
- [`man/xmonad.1.markdown.in`](man/xmonad.1.markdown.in) - [`man/xmonad.1.markdown.in`](man/xmonad.1.markdown.in)
- [haddocks](https://xmonad.github.io/xmonad-docs/) - [haddocks](https://xmonad.github.io/xmonad-docs/)
If the manpage changes, wait for the CI to rebuild the rendered outputs. If the manpage changes, wait for the CI to rebuild the rendered outputs.
3. Make sure that `tested-with:` covers several recent releases of GHC, that 3. Update the website:
- Draft a [new release announcement][web-announce].
- Check install instructions, guided tour, keybindings cheat sheet, …
4. Make sure that `tested-with:` covers several recent releases of GHC, that
`.github/workflows/haskell-ci.yml` had been updated to test all these GHC `.github/workflows/haskell-ci.yml` had been updated to test all these GHC
versions and that `.github/workflows/stack.yml` tests with several recent versions and that `.github/workflows/stack.yml` tests with several recent
revisions of [Stackage][] LTS. revisions of [Stackage][] LTS.
4. Create a release on GitHub: 5. Trigger the Haskell-CI workflow and fill in the candidate version number.
This will upload a release candidate to Hackage.
- https://github.com/xmonad/xmonad/releases/new
- https://github.com/xmonad/xmonad-contrib/releases/new
CI will upload a release candidate to Hackage. Check again that
everything looks good. To publish a final release, run the CI workflow
once again with the correct version number:
- https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml - https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml
- https://github.com/xmonad/xmonad-contrib/actions/workflows/haskell-ci.yml - https://github.com/xmonad/xmonad-contrib/actions/workflows/haskell-ci.yml
See [haskell-ci-hackage.patch][] for details about the release infrastructure. Check that everything looks good. If not, push fixes and do another
candidate. When everything's ready, create a release on GitHub:
5. Update the website: - https://github.com/xmonad/xmonad/releases/new
- https://github.com/xmonad/xmonad-contrib/releases/new
- Post a [new release announcement][web-announce] CI will automatically upload the final release to Hackage.
- Check install instructions, guided tour, keybindings cheat sheet, …
7. Post announcement to: See [haskell-ci-hackage.patch][] for details about the Hackage automation.
6. Post announcement to:
- [xmonad.org website](https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts) - [xmonad.org website](https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts)
- [XMonad mailing list](https://mail.haskell.org/mailman/listinfo/xmonad) - [XMonad mailing list](https://mail.haskell.org/mailman/listinfo/xmonad)
@@ -111,13 +115,17 @@ When the time comes to release another version of xmonad and xmonad-contrib:
- [Twitter](https://twitter.com/xmonad) - [Twitter](https://twitter.com/xmonad)
- [Reddit](https://www.reddit.com/r/xmonad/) - [Reddit](https://www.reddit.com/r/xmonad/)
See [old announcements][old-announce] for inspiration. See [old announcements][old-announce] ([even older][older-announce]) for inspiration.
7. Bump version for development (add `.9`) and prepare fresh sections in
[`CHANGES.md`](CHANGES.md).
[packdeps]: https://hackage.haskell.org/package/packdeps [packdeps]: https://hackage.haskell.org/package/packdeps
[Stackage]: https://www.stackage.org/ [Stackage]: https://www.stackage.org/
[haskell-ci-hackage.patch]: .github/workflows/haskell-ci-hackage.patch [haskell-ci-hackage.patch]: .github/workflows/haskell-ci-hackage.patch
[web-announce]: https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts [web-announce]: https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts
[old-announce]: https://github.com/xmonad/xmonad-web/tree/55614349421ebafaef4a47424fcb16efa80ff768 [old-announce]: https://github.com/xmonad/xmonad-web/blob/gh-pages/news/_posts/2021-10-27-xmonad-0-17-0.md
[older-announce]: https://github.com/xmonad/xmonad-web/tree/55614349421ebafaef4a47424fcb16efa80ff768
## Website and Other Accounts ## Website and Other Accounts

View File

@@ -1,35 +1,20 @@
<p align="center"> <p align="center">
<a href="https://xmonad.org/"> <a href="https://xmonad.org/"><img alt="XMonad logo" src="https://xmonad.org/images/logo-wrapped.svg" height=150></a>
<img alt="XMonad logo" src="https://xmonad.org/images/logo-wrapped.svg" height=150>
</a>
</p> </p>
<p align="center"> <p align="center">
<a href="https://hackage.haskell.org/package/xmonad"> <a href="https://hackage.haskell.org/package/xmonad"><img alt="Hackage" src="https://img.shields.io/hackage/v/xmonad?logo=haskell"></a>
<img alt="Hackage" src="https://img.shields.io/hackage/v/xmonad?logo=haskell"> <a href="https://github.com/xmonad/xmonad/blob/readme/LICENSE"><img alt="License" src="https://img.shields.io/github/license/xmonad/xmonad"></a>
</a> <a href="https://haskell.org/"><img alt="Made in Haskell" src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell"></a>
<a href="https://github.com/xmonad/xmonad/blob/readme/LICENSE">
<img alt="License" src="https://img.shields.io/github/license/xmonad/xmonad">
</a>
<a href="https://haskell.org/">
<img alt="Made in Haskell" src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell">
</a>
<br> <br>
<a href="https://github.com/xmonad/xmonad/actions/workflows/stack.yml"> <a href="https://github.com/xmonad/xmonad/actions/workflows/stack.yml"><img alt="Stack" src="https://img.shields.io/github/workflow/status/xmonad/xmonad/Stack?label=Stack&logo=githubactions&logoColor=white"></a>
<img alt="Stack" src="https://img.shields.io/github/workflow/status/xmonad/xmonad/Stack?label=Stack&logo=githubactions&logoColor=white"> <a href="https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml"><img alt="Cabal" src="https://img.shields.io/github/workflow/status/xmonad/xmonad/Haskell-CI?label=Cabal&logo=githubactions&logoColor=white"></a>
</a> <a href="https://github.com/xmonad/xmonad/actions/workflows/nix.yml"><img alt="Nix" src="https://img.shields.io/github/workflow/status/xmonad/xmonad/Nix?label=Nix&logo=githubactions&logoColor=white"></a>
<a href="https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml">
<img alt="Cabal" src="https://img.shields.io/github/workflow/status/xmonad/xmonad/Haskell-CI?label=Cabal&logo=githubactions&logoColor=white">
</a>
<a href="https://github.com/xmonad/xmonad/actions/workflows/nix.yml">
<img alt="Nix" src="https://img.shields.io/github/workflow/status/xmonad/xmonad/Nix?label=Nix&logo=githubactions&logoColor=white">
</a>
<br> <br>
<a href="https://github.com/sponsors/xmonad"> <a href="https://github.com/sponsors/xmonad"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/xmonad?label=GitHub%20Sponsors&logo=githubsponsors"></a>
<img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/xmonad?label=GitHub%20Sponsors&logo=githubsponsors"> <a href="https://opencollective.com/xmonad"><img alt="Open Collective" src="https://img.shields.io/opencollective/all/xmonad?label=Open%20Collective&logo=opencollective"></a>
</a> <br>
<a href="https://opencollective.com/xmonad"> <a href="https://web.libera.chat/#xmonad"><img alt="Chat on #xmonad@irc.libera.chat" src="https://img.shields.io/badge/%23%20chat-on%20libera-brightgreen"></a>
<img alt="Open Collective" src="https://img.shields.io/opencollective/all/xmonad?label=Open%20Collective&logo=opencollective"> <a href="https://matrix.to/#/#xmonad:matrix.org"><img alt="Chat on #xmonad:matrix.org" src="https://img.shields.io/matrix/xmonad:matrix.org?logo=matrix"></a>
</a>
</p> </p>
# xmonad # xmonad

View File

@@ -54,7 +54,7 @@ in our case `M` will not necessarily mean Alt (also called `Meta`), but
to Super instead (I will show you how to do this below). to Super instead (I will show you how to do this below).
This guide should work for any GNU/Linux distribution and even for BSD This guide should work for any GNU/Linux distribution and even for BSD
folks. Because debian-based distributions are still rather popular, we folks. Because Debian-based distributions are still rather popular, we
will give you the `apt` commands when it comes to installing software. will give you the `apt` commands when it comes to installing software.
If you use another distribution, just substitute the appropriate If you use another distribution, just substitute the appropriate
commands for your system. commands for your system.
@@ -187,8 +187,8 @@ example, but that will change soon enough so it's worth introducing it
here as well. here as well.
What if we wanted to add other keybindings? Say you also want to bind What if we wanted to add other keybindings? Say you also want to bind
`M-S-z` to lock your screen with the screensaver, `M-S-=` to take a `M-S-z` to lock your screen with the screensaver, `M-C-s` to take a
snapshot of one window, and `M-]` to spawn Firefox. This can be snapshot of one window, and `M-f` to spawn Firefox. This can be
achieved with the `additionalKeysP` function from the achieved with the `additionalKeysP` function from the
[XMonad.Util.EZConfig] module—luckily we already have it imported! Our [XMonad.Util.EZConfig] module—luckily we already have it imported! Our
config file, starting with `main`, now looks like: config file, starting with `main`, now looks like:
@@ -200,8 +200,8 @@ main = xmonad $ def
} }
`additionalKeysP` `additionalKeysP`
[ ("M-S-z", spawn "xscreensaver-command -lock") [ ("M-S-z", spawn "xscreensaver-command -lock")
, ("M-S-=", unGrab *> spawn "scrot -s" ) , ("M-C-s", unGrab *> spawn "scrot -s" )
, ("M-]" , spawn "firefox" ) , ("M-f" , spawn "firefox" )
] ]
``` ```
@@ -247,7 +247,7 @@ import XMonad.Layout.ThreeColumns
to the top of our configuration file. Most modules have a lot of to the top of our configuration file. Most modules have a lot of
accompanying text and usage examples in them—so while the type accompanying text and usage examples in them—so while the type
signatures may seem scary, don't be afraid to look up the signatures may seem scary, don't be afraid to look up the
[xmonad-contrib documentation] on hackage! [xmonad-contrib documentation] on Hackage!
Next we just need to tell xmonad that we want to use that particular Next we just need to tell xmonad that we want to use that particular
layout. To do this, there is the `layoutHook`. Let's use the default layout. To do this, there is the `layoutHook`. Let's use the default
@@ -313,8 +313,8 @@ main = xmonad $ def
} }
`additionalKeysP` `additionalKeysP`
[ ("M-S-z", spawn "xscreensaver-command -lock") [ ("M-S-z", spawn "xscreensaver-command -lock")
, ("M-S-=", unGrab *> spawn "scrot -s" ) , ("M-C-s", unGrab *> spawn "scrot -s" )
, ("M-]" , spawn "firefox" ) , ("M-f" , spawn "firefox" )
] ]
``` ```
@@ -396,8 +396,8 @@ main = xmonad $ ewmhFullscreen $ ewmh $ def
} }
`additionalKeysP` `additionalKeysP`
[ ("M-S-z", spawn "xscreensaver-command -lock") [ ("M-S-z", spawn "xscreensaver-command -lock")
, ("M-S-=", unGrab *> spawn "scrot -s" ) , ("M-C-s", unGrab *> spawn "scrot -s" )
, ("M-]" , spawn "firefox" ) , ("M-f" , spawn "firefox" )
] ]
``` ```
@@ -420,8 +420,8 @@ myConfig = def
} }
`additionalKeysP` `additionalKeysP`
[ ("M-S-z", spawn "xscreensaver-command -lock") [ ("M-S-z", spawn "xscreensaver-command -lock")
, ("M-S-=", unGrab *> spawn "scrot -s" ) , ("M-C-s", unGrab *> spawn "scrot -s" )
, ("M-]" , spawn "firefox" ) , ("M-f" , spawn "firefox" )
] ]
``` ```
@@ -592,14 +592,14 @@ Config { overrideRedirect = False
``` ```
First, we set the font to use for the bar, as well as the colors. The First, we set the font to use for the bar, as well as the colors. The
position options are documented well on the [xmobar home page] or, position options are documented well in xmobar's [quick-start.org]. The
alternatively, in the [quick-start.org] on GitHub. The particular particular option of `TopW L 90` says to put the bar in the upper left
option of `TopW L 90` says to put the bar in the upper left of the of the screen, and make it consume 90% of the width of the screen (we
screen, and make it consume 90% of the width of the screen (we need to need to leave a little bit of space for `trayer-srg`). If you're up for
leave a little bit of space for `trayer-srg`). If you're up for it—and it—and this really requires more shell-scripting than Haskell
this really requires more shell-scripting than Haskell knowledge—you can knowledge—you can also try to seamlessly embed trayer into xmobar by
also try to seamlessly embed trayer into xmobar by using using [trayer-padding-icon.sh] and following the advice given in that
[trayer-padding-icon.sh] and following the advice given in that thread. thread.
In the commands list you, well, define commands. Commands are the In the commands list you, well, define commands. Commands are the
pieces that generate the content to be displayed in your bar. These pieces that generate the content to be displayed in your bar. These
@@ -965,9 +965,9 @@ class name to float by defining the following manageHook:
myManageHook = (className =? "Gimp" --> doFloat) myManageHook = (className =? "Gimp" --> doFloat)
``` ```
Say we also want to float all dialogs. This is easy with the `isDialog` Say we also want to float all dialog windows. This is easy with the
function from [XMonad.Hooks.ManageHelpers] (which you should import) and `isDialog` function from [XMonad.Hooks.ManageHelpers] (which you should
a little modification to the `myManageHook` function: import) and a little modification to the `myManageHook` function:
``` haskell ``` haskell
myManageHook :: ManageHook myManageHook :: ManageHook
@@ -989,8 +989,8 @@ myConfig = def
} }
`additionalKeysP` `additionalKeysP`
[ ("M-S-z", spawn "xscreensaver-command -lock") [ ("M-S-z", spawn "xscreensaver-command -lock")
, ("M-S-=", unGrab *> spawn "scrot -s" ) , ("M-C-s", unGrab *> spawn "scrot -s" )
, ("M-]" , spawn "firefox" ) , ("M-f" , spawn "firefox" )
] ]
``` ```
@@ -1032,8 +1032,8 @@ myConfig = def
} }
`additionalKeysP` `additionalKeysP`
[ ("M-S-z", spawn "xscreensaver-command -lock") [ ("M-S-z", spawn "xscreensaver-command -lock")
, ("M-S-=", unGrab *> spawn "scrot -s" ) , ("M-C-s", unGrab *> spawn "scrot -s" )
, ("M-]" , spawn "firefox" ) , ("M-f" , spawn "firefox" )
] ]
myManageHook :: ManageHook myManageHook :: ManageHook
@@ -1113,7 +1113,7 @@ Config { overrideRedirect = False
} }
``` ```
For an explanation of the battery commands used above, see xmobars For an explanation of the battery commands used above, see xmobar's
[battery] documentation. [battery] documentation.
You can also specify workspaces in the same way and feed them to xmobar You can also specify workspaces in the same way and feed them to xmobar
@@ -1121,13 +1121,15 @@ via the property (e.g. have `"<fn=1>\xf120</fn>"` as one of your
workspace names). workspace names).
As an example how this would look like in a real configuration, you can As an example how this would look like in a real configuration, you can
look at [Liskin's], [slotThe's], or [TheMC47's] xmobar configuration. look at [Liskin's old][liskin-xmobarrc-old], [Liskin's current][liskin-xmobarrc],
Do note that the last two are Haskell-based and thus may be a little [slotThe's][slotThe-xmobarrc], or [TheMC47's][TheMC47-xmobarrc] xmobar
hard to understand for newcomers. configuration. Do note that the last three are Haskell-based and thus may
be a little hard to understand for newcomers.
[Liskin's]: https://github.com/liskin/dotfiles/blob/home/.xmobarrc [liskin-xmobarrc-old]: https://github.com/liskin/dotfiles/blob/75dfc057c33480ee9d3300d4d02fb79a986ef3a5/.xmobarrc
[TheMC47's]: https://github.com/TheMC47/dotfiles/tree/master/xmobar/xmobarrc [liskin-xmobarrc]: https://github.com/liskin/dotfiles/blob/home/.xmonad/xmobar.hs
[slotThe's]: https://gitlab.com/slotThe/dotfiles/-/blob/master/xmobar/.config/xmobarrc/src/xmobarrc.hs [TheMC47-xmobarrc]: https://github.com/TheMC47/dotfiles/tree/master/xmobar/xmobarrc
[slotThe-xmobarrc]: https://gitlab.com/slotThe/dotfiles/-/blob/master/xmobar/.config/xmobarrc/src/xmobarrc.hs
### Renaming Layouts ### Renaming Layouts
@@ -1222,7 +1224,7 @@ either :)
[log]: https://ircbrowse.tomsmeding.com/browse/lcxmonad [log]: https://ircbrowse.tomsmeding.com/browse/lcxmonad
[EWMH]: https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html [EWMH]: https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html
[ICCCM]: https://tronche.com/gui/x/icccm/ [ICCCM]: https://tronche.com/gui/x/icccm/
[webchat]: https://kiwiirc.com/nextclient/irc.libera.chat/?#xmonad [webchat]: https://web.libera.chat/#xmonad
[about xmonad]: https://xmonad.org/about.html [about xmonad]: https://xmonad.org/about.html
[shell variable]: https://www.shellscript.sh/variables1.html [shell variable]: https://www.shellscript.sh/variables1.html
[xmonad-testing]: https://github.com/xmonad/xmonad-testing [xmonad-testing]: https://github.com/xmonad/xmonad-testing
@@ -1230,7 +1232,7 @@ either :)
[xmonad guided tour]: https://xmonad.org/tour.html [xmonad guided tour]: https://xmonad.org/tour.html
[xmonad mailing list]: https://mail.haskell.org/mailman/listinfo/xmonad [xmonad mailing list]: https://mail.haskell.org/mailman/listinfo/xmonad
[xmonad's GitHub page]: https://github.com/xmonad/xmonad [xmonad's GitHub page]: https://github.com/xmonad/xmonad
[trayer-padding-icon.sh]: https://github.com/jaor/xmobar/issues/239#issuecomment-233206552 [trayer-padding-icon.sh]: https://codeberg.org/xmobar/xmobar/issues/239#issuecomment-537931
[xmonad-contrib documentation]: https://hackage.haskell.org/package/xmonad-contrib [xmonad-contrib documentation]: https://hackage.haskell.org/package/xmonad-contrib
[GNU Image Manipulation Program]: https://www.gimp.org/ [GNU Image Manipulation Program]: https://www.gimp.org/
[Basic Desktop Environment Integration]: https://wiki.haskell.org/Xmonad/Basic_Desktop_Environment_Integration [Basic Desktop Environment Integration]: https://wiki.haskell.org/Xmonad/Basic_Desktop_Environment_Integration
@@ -1251,14 +1253,13 @@ either :)
[XMonad.Util.ClickableWorkspaces]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-ClickableWorkspaces.html [XMonad.Util.ClickableWorkspaces]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-ClickableWorkspaces.html
[xmobar]: https://xmobar.org/ [xmobar]: https://xmobar.org/
[battery]: https://github.com/jaor/xmobar/blob/master/doc/plugins.org#batteryp-dirs-args-refreshrate [battery]: https://codeberg.org/xmobar/xmobar/src/branch/master/doc/plugins.org#batteryp-dirs-args-refreshrate
[xmobar.hs]: https://github.com/jaor/xmobar/blob/master/examples/xmobar.hs [xmobar.hs]: https://codeberg.org/xmobar/xmobar/src/branch/master/etc/xmobar.hs
[Wikipedia page]: https://en.wikipedia.org/wiki/ICAO_airport_code#Prefixes [Wikipedia page]: https://en.wikipedia.org/wiki/ICAO_airport_code#Prefixes
[quick-start.org]: https://github.com/jaor/xmobar/blob/master/doc/quick-start.org#configuration-options [quick-start.org]: https://codeberg.org/xmobar/xmobar/src/branch/master/doc/quick-start.org#configuration-options
[jao's xmobar.hs]: https://codeberg.org/jao/xmobar-config [jao's xmobar.hs]: https://codeberg.org/jao/xmobar-config
[weather monitor]: https://github.com/jaor/xmobar/blob/master/doc/plugins.org#weather-monitors [weather monitor]: https://codeberg.org/xmobar/xmobar/src/branch/master/doc/plugins.org#weather-monitors
[xmobar home page]: https://xmobar.org/ [xmobar's `Installation` section]: https://codeberg.org/xmobar/xmobar#installation
[xmobar's `Installation` section]: https://github.com/jaor/xmobar#installation
[Haskell]: https://www.haskell.org/ [Haskell]: https://www.haskell.org/
[trayer-srg]: https://github.com/sargon/trayer-srg [trayer-srg]: https://github.com/sargon/trayer-srg

View File

@@ -5,6 +5,11 @@ apt:
libxrandr-dev libxrandr-dev
libxss-dev libxss-dev
hlint: True
hlint-job: 9.0.2
hlint-yaml: .hlint.yaml
hlint-version: ==3.4.*
github-patches: github-patches:
.github/workflows/haskell-ci-hackage.patch .github/workflows/haskell-ci-hackage.patch

View File

@@ -1,20 +1,67 @@
# This file is maintained by @IvanMalison (github) # This file is maintained by @IvanMalison and @LSLeary (github)
# See xmonad-contrib/NIX.md for an overview of module usage.
{ {
inputs = { inputs = {
flake-utils.url = github:numtide/flake-utils; flake-utils.url = github:numtide/flake-utils;
git-ignore-nix.url = github:IvanMalison/gitignore.nix/master; git-ignore-nix.url = github:hercules-ci/gitignore.nix/master;
unstable.url = github:NixOS/nixpkgs/nixos-unstable;
}; };
outputs = { self, flake-utils, nixpkgs, git-ignore-nix }: outputs = { self, flake-utils, nixpkgs, unstable, git-ignore-nix }:
let let
overlay = final: prev: { hpath = { prefix ? null, compiler ? null }:
haskellPackages = prev.haskellPackages.override (old: { (if prefix == null then [] else [ prefix ]) ++
overrides = prev.lib.composeExtensions (old.overrides or (_: _: {})) (if compiler == null
(hself: hsuper: { then [ "haskellPackages" ]
xmonad = hself.callCabal2nix "xmonad" (git-ignore-nix.gitIgnoreSource ./.) { }; else [ "haskell" "packages" compiler ]
}); );
}); fromHOL = hol: comp: final: prev: with prev.lib; with attrsets;
}; setAttrByPath (hpath comp)
((getAttrFromPath (hpath comp) prev).override (old: {
overrides = composeExtensions (old.overrides or (_: _: {}))
(hol final prev);
}));
hoverlay = final: prev: hself: hsuper:
with prev.haskell.lib.compose; {
xmonad = hself.callCabal2nix "xmonad"
(git-ignore-nix.lib.gitignoreSource ./.) { };
};
overlay = fromHOL hoverlay { };
overlays = [ overlay ]; overlays = [ overlay ];
nixosModule = { config, pkgs, lib, ... }: with lib; with attrsets;
let
cfg = config.services.xserver.windowManager.xmonad.flake;
comp = { inherit (cfg) prefix compiler; };
in {
options = {
services.xserver.windowManager.xmonad.flake = with types; {
enable = mkEnableOption "flake";
prefix = mkOption {
default = null;
type = nullOr string;
example = literalExpression "\"unstable\"";
description = ''
Specify a nested alternative <literal>pkgs</literal> by attrName.
'';
};
compiler = mkOption {
default = null;
type = nullOr string;
example = literalExpression "\"ghc922\"";
description = ''
Which compiler to build xmonad with.
Must be an attribute of <literal>pkgs.haskell.packages</literal>.
Sets <option>xmonad.haskellPackages</option> to match.
'';
};
};
};
config = mkIf cfg.enable {
nixpkgs.overlays = [ (fromHOL hoverlay comp) ];
services.xserver.windowManager.xmonad.haskellPackages =
getAttrFromPath (hpath comp) pkgs;
};
};
nixosModules = [ nixosModule ];
in flake-utils.lib.eachDefaultSystem (system: in flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system overlays; }; let pkgs = import nixpkgs { inherit system overlays; };
in in
@@ -23,5 +70,8 @@
packages = p: [ p.xmonad ]; packages = p: [ p.xmonad ];
}; };
defaultPackage = pkgs.haskellPackages.xmonad; defaultPackage = pkgs.haskellPackages.xmonad;
}) // { inherit overlay overlays; } ; }) // {
inherit hoverlay overlay overlays nixosModule nixosModules;
lib = { inherit hpath fromHOL; };
};
} }

View File

@@ -123,7 +123,7 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
-- , ((modm , xK_b ), sendMessage ToggleStruts) -- , ((modm , xK_b ), sendMessage ToggleStruts)
-- Quit xmonad -- Quit xmonad
, ((modm .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) , ((modm .|. shiftMask, xK_q ), io exitSuccess)
-- Restart xmonad -- Restart xmonad
, ((modm , xK_q ), spawn "xmonad --recompile; xmonad --restart") , ((modm , xK_q ), spawn "xmonad --recompile; xmonad --restart")
@@ -154,18 +154,18 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- Mouse bindings: default actions bound to mouse events -- Mouse bindings: default actions bound to mouse events
-- --
myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList $ myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList
-- mod-button1, Set the window to floating mode and move by dragging -- mod-button1, Set the window to floating mode and move by dragging
[ ((modm, button1), (\w -> focus w >> mouseMoveWindow w [ ((modm, button1), \w -> focus w >> mouseMoveWindow w
>> windows W.shiftMaster)) >> windows W.shiftMaster)
-- mod-button2, Raise the window to the top of the stack -- mod-button2, Raise the window to the top of the stack
, ((modm, button2), (\w -> focus w >> windows W.shiftMaster)) , ((modm, button2), \w -> focus w >> windows W.shiftMaster)
-- mod-button3, Set the window to floating mode and resize by dragging -- mod-button3, Set the window to floating mode and resize by dragging
, ((modm, button3), (\w -> focus w >> mouseResizeWindow w , ((modm, button3), \w -> focus w >> mouseResizeWindow w
>> windows W.shiftMaster)) >> windows W.shiftMaster)
-- you may also bind events to the mouse scroll wheel (button4 and button5) -- you may also bind events to the mouse scroll wheel (button4 and button5)
] ]

View File

@@ -218,7 +218,7 @@ keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
, ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area , ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area
-- quit, or restart -- quit, or restart
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad , ((modMask .|. shiftMask, xK_q ), io exitSuccess) -- %! Quit xmonad
, ((modMask , xK_q ), spawn "if type xmonad; then xmonad --recompile && xmonad --restart; else xmessage xmonad not in \\$PATH: \"$PATH\"; fi") -- %! Restart xmonad , ((modMask , xK_q ), spawn "if type xmonad; then xmonad --recompile && xmonad --restart; else xmessage xmonad not in \\$PATH: \"$PATH\"; fi") -- %! Restart xmonad
, ((modMask .|. shiftMask, xK_slash ), helpCommand) -- %! Run xmessage with a summary of the default keybindings (useful for beginners) , ((modMask .|. shiftMask, xK_slash ), helpCommand) -- %! Run xmessage with a summary of the default keybindings (useful for beginners)

View File

@@ -1,6 +1,11 @@
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving, {-# LANGUAGE DeriveTraversable #-}
MultiParamTypeClasses, TypeSynonymInstances, DeriveDataTypeable, {-# LANGUAGE ExistentialQuantification #-}
LambdaCase, NamedFieldPuns, DeriveTraversable #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- | -- |
@@ -35,17 +40,18 @@ module XMonad.Core (
import XMonad.StackSet hiding (modify) import XMonad.StackSet hiding (modify)
import Prelude import Prelude
import Control.Exception (fromException, try, bracket, bracket_, throw, finally, SomeException(..)) import Control.Exception (fromException, try, bracket_, throw, finally, SomeException(..))
import qualified Control.Exception as E import qualified Control.Exception as E
import Control.Applicative ((<|>), empty) import Control.Applicative ((<|>), empty)
import Control.Monad.Fail import Control.Monad.Fail
import Control.Monad.State import Control.Monad.State
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad (void)
import Data.Semigroup import Data.Semigroup
import Data.Traversable (for) import Data.Traversable (for)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Data.Default.Class import Data.Default.Class
import Data.List (isInfixOf) import System.Environment (lookupEnv)
import System.FilePath import System.FilePath
import System.IO import System.IO
import System.Info import System.Info
@@ -60,7 +66,7 @@ import System.Exit
import Graphics.X11.Xlib import Graphics.X11.Xlib
import Graphics.X11.Xlib.Extras (getWindowAttributes, WindowAttributes, Event) import Graphics.X11.Xlib.Extras (getWindowAttributes, WindowAttributes, Event)
import Data.Typeable import Data.Typeable
import Data.List ((\\)) import Data.List (isInfixOf, (\\))
import Data.Maybe (isJust,fromMaybe) import Data.Maybe (isJust,fromMaybe)
import qualified Data.Map as M import qualified Data.Map as M
@@ -156,18 +162,13 @@ newtype ScreenDetail = SD { screenRect :: Rectangle }
-- instantiated on 'XConf' and 'XState' automatically. -- instantiated on 'XConf' and 'XState' automatically.
-- --
newtype X a = X (ReaderT XConf (StateT XState IO) a) newtype X a = X (ReaderT XConf (StateT XState IO) a)
deriving (Functor, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf) deriving (Functor, Applicative, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf)
instance Applicative X where
pure = return
(<*>) = ap
instance Semigroup a => Semigroup (X a) where instance Semigroup a => Semigroup (X a) where
(<>) = liftM2 (<>) (<>) = liftM2 (<>)
instance (Monoid a) => Monoid (X a) where instance (Monoid a) => Monoid (X a) where
mempty = return mempty mempty = pure mempty
mappend = liftM2 mappend
instance Default a => Default (X a) where instance Default a => Default (X a) where
def = return def def = return def
@@ -177,14 +178,13 @@ newtype Query a = Query (ReaderT Window X a)
deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO) deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO)
runQuery :: Query a -> Window -> X a runQuery :: Query a -> Window -> X a
runQuery (Query m) w = runReaderT m w runQuery (Query m) = runReaderT m
instance Semigroup a => Semigroup (Query a) where instance Semigroup a => Semigroup (Query a) where
(<>) = liftM2 (<>) (<>) = liftM2 (<>)
instance Monoid a => Monoid (Query a) where instance Monoid a => Monoid (Query a) where
mempty = return mempty mempty = pure mempty
mappend = liftM2 mappend
instance Default a => Default (Query a) where instance Default a => Default (Query a) where
def = return def def = return def
@@ -201,7 +201,7 @@ catchX job errcase = do
st <- get st <- get
c <- ask c <- ask
(a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of (a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of
Just x -> throw e `const` (x `asTypeOf` ExitSuccess) Just (_ :: ExitCode) -> throw e
_ -> do hPrint stderr e; runX c st errcase _ -> do hPrint stderr e; runX c st errcase
put s' put s'
return a return a
@@ -209,12 +209,12 @@ catchX job errcase = do
-- | Execute the argument, catching all exceptions. Either this function or -- | Execute the argument, catching all exceptions. Either this function or
-- 'catchX' should be used at all callsites of user customized code. -- 'catchX' should be used at all callsites of user customized code.
userCode :: X a -> X (Maybe a) userCode :: X a -> X (Maybe a)
userCode a = catchX (Just `liftM` a) (return Nothing) userCode a = catchX (Just <$> a) (return Nothing)
-- | Same as userCode but with a default argument to return instead of using -- | Same as userCode but with a default argument to return instead of using
-- Maybe, provided for convenience. -- Maybe, provided for convenience.
userCodeDef :: a -> X a -> X a userCodeDef :: a -> X a -> X a
userCodeDef defValue a = fromMaybe defValue `liftM` userCode a userCodeDef defValue a = fromMaybe defValue <$> userCode a
-- --------------------------------------------------------------------- -- ---------------------------------------------------------------------
-- Convenient wrappers to state -- Convenient wrappers to state
@@ -235,7 +235,7 @@ withWindowAttributes dpy win f = do
-- | True if the given window is the root window -- | True if the given window is the root window
isRoot :: Window -> X Bool isRoot :: Window -> X Bool
isRoot w = (w==) <$> asks theRoot isRoot w = asks $ (w ==) . theRoot
-- | Wrapper for the common case of atom internment -- | Wrapper for the common case of atom internment
getAtom :: String -> X Atom getAtom :: String -> X Atom
@@ -437,7 +437,7 @@ catchIO f = io (f `E.catch` \(SomeException e) -> hPrint stderr e >> hFlush stde
-- --
-- Note this function assumes your locale uses utf8. -- Note this function assumes your locale uses utf8.
spawn :: MonadIO m => String -> m () spawn :: MonadIO m => String -> m ()
spawn x = spawnPID x >> return () spawn x = void $ spawnPID x
-- | Like 'spawn', but returns the 'ProcessID' of the launched application -- | Like 'spawn', but returns the 'ProcessID' of the launched application
spawnPID :: MonadIO m => String -> m ProcessID spawnPID :: MonadIO m => String -> m ProcessID
@@ -458,7 +458,8 @@ xfork x = io . forkProcess . finally nullStdin $ do
-- | Use @xmessage@ to show information to the user. -- | Use @xmessage@ to show information to the user.
xmessage :: MonadIO m => String -> m () xmessage :: MonadIO m => String -> m ()
xmessage msg = void . xfork $ do xmessage msg = void . xfork $ do
executeFile "xmessage" True xmessageBin <- fromMaybe "xmessage" <$> liftIO (lookupEnv "XMONAD_XMESSAGE")
executeFile xmessageBin True
[ "-default", "okay" [ "-default", "okay"
, "-xrm", "*international:true" , "-xrm", "*international:true"
, "-xrm", "*fontSet:-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*,-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*,-*-*-*-*-*-*-18-*-*-*-*-*-*-*" , "-xrm", "*fontSet:-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*,-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*,-*-*-*-*-*-*-18-*-*-*-*-*-*-*"
@@ -651,11 +652,12 @@ getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> r
compile :: Directories -> Compile -> IO ExitCode compile :: Directories -> Compile -> IO ExitCode
compile dirs method = compile dirs method =
bracket_ uninstallSignalHandlers installSignalHandlers $ bracket_ uninstallSignalHandlers installSignalHandlers $
bracket (openFile (errFileName dirs) WriteMode) hClose $ \err -> do withFile (errFileName dirs) WriteMode $ \err -> do
let run = runProc (cfgDir dirs) err let run = runProc (cfgDir dirs) err
case method of case method of
CompileGhc -> CompileGhc -> do
run "ghc" ghcArgs ghc <- fromMaybe "ghc" <$> lookupEnv "XMONAD_GHC"
run ghc ghcArgs
CompileStackGhc stackYaml -> CompileStackGhc stackYaml ->
run "stack" ["build", "--silent", "--stack-yaml", stackYaml] .&&. run "stack" ["build", "--silent", "--stack-yaml", stackYaml] .&&.
run "stack" ("ghc" : "--stack-yaml" : stackYaml : "--" : ghcArgs) run "stack" ("ghc" : "--stack-yaml" : stackYaml : "--" : ghcArgs)

View File

@@ -1,4 +1,6 @@
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable, LambdaCase, MultiWayIf #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternGuards #-}
-- -------------------------------------------------------------------------- -- --------------------------------------------------------------------------
-- | -- |
@@ -39,7 +41,7 @@ import Data.Maybe (fromMaybe)
data Resize = Shrink | Expand data Resize = Shrink | Expand
-- | Increase the number of clients in the master pane. -- | Increase the number of clients in the master pane.
data IncMasterN = IncMasterN !Int newtype IncMasterN = IncMasterN Int
instance Message Resize instance Message Resize
instance Message IncMasterN instance Message IncMasterN
@@ -199,8 +201,8 @@ choose (Choose d l r) d' ml mr = f lr
(CL, CR) -> (hide l' , return r') (CL, CR) -> (hide l' , return r')
(CR, CL) -> (return l', hide r' ) (CR, CL) -> (return l', hide r' )
(_ , _ ) -> (return l', return r') (_ , _ ) -> (return l', return r')
f (x,y) = fmap Just $ liftM2 (Choose d') x y f (x,y) = Just <$> liftM2 (Choose d') x y
hide x = fmap (fromMaybe x) $ handle x Hide hide x = fromMaybe x <$> handle x Hide
instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where
runLayout (W.Workspace i (Choose CL l r) ms) = runLayout (W.Workspace i (Choose CL l r) ms) =

View File

@@ -1,4 +1,6 @@
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, NamedFieldPuns #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- | -- |
-- Module : XMonad.Main -- Module : XMonad.Main
@@ -19,6 +21,7 @@ import System.Locale.SetLocale
import qualified Control.Exception as E import qualified Control.Exception as E
import Data.Bits import Data.Bits
import Data.List ((\\)) import Data.List ((\\))
import Data.Foldable (traverse_)
import Data.Function import Data.Function
import qualified Data.Map as M import qualified Data.Map as M
import qualified Data.Set as S import qualified Data.Set as S
@@ -87,14 +90,14 @@ usage :: IO ()
usage = do usage = do
self <- getProgName self <- getProgName
putStr . unlines $ putStr . unlines $
concat ["Usage: ", self, " [OPTION]"] : [ "Usage: " <> self <> " [OPTION]"
"Options:" : , "Options:"
" --help Print this message" : , " --help Print this message"
" --version Print the version number" : , " --version Print the version number"
" --recompile Recompile your xmonad.hs" : , " --recompile Recompile your xmonad.hs"
" --replace Replace the running window manager with xmonad" : , " --replace Replace the running window manager with xmonad"
" --restart Request a running xmonad process to restart" : , " --restart Request a running xmonad process to restart"
[] ]
-- | Build the xmonad configuration file with ghc, then execute it. -- | Build the xmonad configuration file with ghc, then execute it.
-- If there are no errors, this function does not return. An -- If there are no errors, this function does not return. An
@@ -193,12 +196,12 @@ launch initxmc drs = do
xinesc <- getCleanedScreenInfo dpy xinesc <- getCleanedScreenInfo dpy
nbc <- do v <- initColor dpy $ normalBorderColor xmc nbc <- do v <- initColor dpy $ normalBorderColor xmc
~(Just nbc_) <- initColor dpy $ normalBorderColor Default.def Just nbc_ <- initColor dpy $ normalBorderColor Default.def
return (fromMaybe nbc_ v) return (fromMaybe nbc_ v)
fbc <- do v <- initColor dpy $ focusedBorderColor xmc fbc <- do v <- initColor dpy $ focusedBorderColor xmc
~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.def Just fbc_ <- initColor dpy $ focusedBorderColor Default.def
return (fromMaybe fbc_ v) return (fromMaybe fbc_ v)
hSetBuffering stdout NoBuffering hSetBuffering stdout NoBuffering
@@ -242,7 +245,7 @@ launch initxmc drs = do
let extst = maybe M.empty extensibleState serializedSt let extst = maybe M.empty extensibleState serializedSt
modify (\s -> s {extensibleState = extst}) modify (\s -> s {extensibleState = extst})
setNumlockMask cacheNumlockMask
grabKeys grabKeys
grabButtons grabButtons
@@ -264,10 +267,11 @@ launch initxmc drs = do
userCode $ startupHook initxmc userCode $ startupHook initxmc
rrData <- io $ xrrQueryExtension dpy rrData <- io $ xrrQueryExtension dpy
let rrUpdate = when (isJust rrData) . void . xrrUpdateConfiguration
-- main loop, for all you HOF/recursion fans out there. -- main loop, for all you HOF/recursion fans out there.
forever $ prehandle =<< io (nextEvent dpy e >> rrUpdate e >> getEvent e) -- forever $ prehandle =<< io (nextEvent dpy e >> rrUpdate e >> getEvent e)
-- sadly, 9.2.{1,2,3} join points mishandle the above and trash the heap (see #389)
mainLoop dpy e rrData
return () return ()
where where
@@ -278,6 +282,8 @@ launch initxmc drs = do
in local (\c -> c { mousePosition = mouse, currentEvent = Just e }) (handleWithHook e) in local (\c -> c { mousePosition = mouse, currentEvent = Just e }) (handleWithHook e)
evs = [ keyPress, keyRelease, enterNotify, leaveNotify evs = [ keyPress, keyRelease, enterNotify, leaveNotify
, buttonPress, buttonRelease] , buttonPress, buttonRelease]
rrUpdate e r = when (isJust r) (void (xrrUpdateConfiguration e))
mainLoop d e r = io (nextEvent d e >> rrUpdate e r >> getEvent e) >>= prehandle >> mainLoop d e r
-- | Runs handleEventHook from the configuration and runs the default handler -- | Runs handleEventHook from the configuration and runs the default handler
@@ -330,7 +336,7 @@ handle e@(DestroyWindowEvent {ev_window = w}) = do
-- it is synthetic or we are not expecting an unmap notification from a window. -- it is synthetic or we are not expecting an unmap notification from a window.
handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do
e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap) e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
if (synthetic || e == 0) if synthetic || e == 0
then unmanage w then unmanage w
else modify (\s -> s { waitingUnmap = M.update mpred w (waitingUnmap s) }) else modify (\s -> s { waitingUnmap = M.update mpred w (waitingUnmap s) })
where mpred 1 = Nothing where mpred 1 = Nothing
@@ -340,7 +346,7 @@ handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient
handle e@(MappingNotifyEvent {}) = do handle e@(MappingNotifyEvent {}) = do
io $ refreshKeyboardMapping e io $ refreshKeyboardMapping e
when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do
setNumlockMask cacheNumlockMask
grabKeys grabKeys
-- handle button release, which may finish dragging. -- handle button release, which may finish dragging.
@@ -428,7 +434,7 @@ handle event@(PropertyEvent { ev_event_type = t, ev_atom = a })
handle e@ClientMessageEvent { ev_message_type = mt } = do handle e@ClientMessageEvent { ev_message_type = mt } = do
a <- getAtom "XMONAD_RESTART" a <- getAtom "XMONAD_RESTART"
if (mt == a) if mt == a
then restart "xmonad" True then restart "xmonad" True
else broadcastMessage e else broadcastMessage e
@@ -459,38 +465,14 @@ scan dpy rootw = do
skip :: E.SomeException -> IO Bool skip :: E.SomeException -> IO Bool
skip _ = return False skip _ = return False
setNumlockMask :: X ()
setNumlockMask = do
dpy <- asks display
ms <- io $ getModifierMapping dpy
xs <- sequence [ do
ks <- io $ keycodeToKeysym dpy kc 0
if ks == xK_Num_Lock
then return (setBit 0 (fromIntegral m))
else return (0 :: KeyMask)
| (m, kcs) <- ms, kc <- kcs, kc /= 0]
modify (\s -> s { numberlockMask = foldr (.|.) 0 xs })
-- | Grab the keys back -- | Grab the keys back
grabKeys :: X () grabKeys :: X ()
grabKeys = do grabKeys = do
XConf { display = dpy, theRoot = rootw } <- ask XConf { display = dpy, theRoot = rootw } <- ask
let grab kc m = io $ grabKey dpy kc m rootw True grabModeAsync grabModeAsync
(minCode, maxCode) = displayKeycodes dpy
allCodes = [fromIntegral minCode .. fromIntegral maxCode]
io $ ungrabKey dpy anyKey anyModifier rootw io $ ungrabKey dpy anyKey anyModifier rootw
ks <- asks keyActions let grab :: (KeyMask, KeyCode) -> X ()
-- build a map from keysyms to lists of keysyms (doing what grab (km, kc) = io $ grabKey dpy kc km rootw True grabModeAsync grabModeAsync
-- XGetKeyboardMapping would do if the X11 package bound it) traverse_ grab =<< mkGrabs =<< asks (M.keys . keyActions)
syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0)
let keysymMap' = M.fromListWith (++) (zip syms [[code] | code <- allCodes])
-- keycodeToKeysym returns noSymbol for all unbound keycodes, and we don't
-- want to grab those whenever someone accidentally uses def :: KeySym
let keysymMap = M.delete noSymbol keysymMap'
let keysymToKeycodes sym = M.findWithDefault [] sym keysymMap
forM_ (M.keys ks) $ \(mask,sym) ->
forM_ (keysymToKeycodes sym) $ \kc ->
mapM_ (grab kc . (mask .|.)) =<< extraModifiers
-- | Grab the buttons -- | Grab the buttons
grabButtons :: X () grabButtons :: X ()
@@ -501,7 +483,7 @@ grabButtons = do
io $ ungrabButton dpy anyButton anyModifier rootw io $ ungrabButton dpy anyButton anyModifier rootw
ems <- extraModifiers ems <- extraModifiers
ba <- asks buttonActions ba <- asks buttonActions
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba) mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys ba)
-- | @replace@ to signals compliant window managers to exit. -- | @replace@ to signals compliant window managers to exit.
replace :: Display -> ScreenNumber -> Window -> IO () replace :: Display -> ScreenNumber -> Window -> IO ()

View File

@@ -1,5 +1,3 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- | -- |
-- Module : XMonad.ManageHook -- Module : XMonad.ManageHook
@@ -8,7 +6,6 @@
-- --
-- Maintainer : spencerjanssen@gmail.com -- Maintainer : spencerjanssen@gmail.com
-- Stability : unstable -- Stability : unstable
-- Portability : not portable, uses cunning newtype deriving
-- --
-- An EDSL for ManageHooks -- An EDSL for ManageHooks
-- --
@@ -27,7 +24,7 @@ import Control.Monad.Reader
import Data.Maybe import Data.Maybe
import Data.Monoid import Data.Monoid
import qualified XMonad.StackSet as W import qualified XMonad.StackSet as W
import XMonad.Operations (floatLocation, reveal) import XMonad.Operations (floatLocation, reveal, isFixedSizeOrTransient)
-- | Lift an 'X' action to a 'Query'. -- | Lift an 'X' action to a 'Query'.
liftX :: X a -> Query a liftX :: X a -> Query a
@@ -61,11 +58,11 @@ infixr 3 <&&>, <||>
-- | '&&' lifted to a 'Monad'. -- | '&&' lifted to a 'Monad'.
(<&&>) :: Monad m => m Bool -> m Bool -> m Bool (<&&>) :: Monad m => m Bool -> m Bool -> m Bool
(<&&>) x y = ifM x y (pure False) x <&&> y = ifM x y (pure False)
-- | '||' lifted to a 'Monad'. -- | '||' lifted to a 'Monad'.
(<||>) :: Monad m => m Bool -> m Bool -> m Bool (<||>) :: Monad m => m Bool -> m Bool -> m Bool
(<||>) x y = ifM x (pure True) y x <||> y = ifM x (pure True) y
-- | If-then-else lifted to a 'Monad'. -- | If-then-else lifted to a 'Monad'.
ifM :: Monad m => m Bool -> m a -> m a -> m a ifM :: Monad m => m Bool -> m a -> m a -> m a
@@ -83,7 +80,8 @@ title = ask >>= \w -> liftX $ do
return $ if null l then "" else head l return $ if null l then "" else head l
io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return "" io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return ""
-- | Return the application name. -- | Return the application name; i.e., the /first/ string returned by
-- @WM_CLASS@.
appName :: Query String appName :: Query String
appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClassHint d w) appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClassHint d w)
@@ -91,14 +89,15 @@ appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClas
resource :: Query String resource :: Query String
resource = appName resource = appName
-- | Return the resource class. -- | Return the resource class; i.e., the /second/ string returned by
-- @WM_CLASS@.
className :: Query String className :: Query String
className = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resClass $ io $ getClassHint d w) className = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resClass $ io $ getClassHint d w)
-- | A query that can return an arbitrary X property of type 'String', -- | A query that can return an arbitrary X property of type 'String',
-- identified by name. -- identified by name.
stringProperty :: String -> Query String stringProperty :: String -> Query String
stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap (fromMaybe "") $ getStringProperty d w p) stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fromMaybe "" <$> getStringProperty d w p)
getStringProperty :: Display -> Window -> String -> X (Maybe String) getStringProperty :: Display -> Window -> String -> X (Maybe String)
getStringProperty d w p = do getStringProperty d w p = do
@@ -106,6 +105,10 @@ getStringProperty d w p = do
md <- io $ getWindowProperty8 d a w md <- io $ getWindowProperty8 d a w
return $ fmap (map (toEnum . fromIntegral)) md return $ fmap (map (toEnum . fromIntegral)) md
-- | Return whether the window will be a floating window or not
willFloat :: Query Bool
willFloat = ask >>= \w -> liftX $ withDisplay $ \d -> isFixedSizeOrTransient d w
-- | Modify the 'WindowSet' with a pure function. -- | Modify the 'WindowSet' with a pure function.
doF :: (s -> s) -> Query (Endo s) doF :: (s -> s) -> Query (Endo s)
doF = return . Endo doF = return . Endo

View File

@@ -1,4 +1,10 @@
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PatternGuards #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- -------------------------------------------------------------------------- -- --------------------------------------------------------------------------
-- | -- |
-- Module : XMonad.Operations -- Module : XMonad.Operations
@@ -18,7 +24,7 @@ module XMonad.Operations (
manage, unmanage, killWindow, kill, isClient, manage, unmanage, killWindow, kill, isClient,
setInitialProperties, setWMState, setWindowBorderWithFallback, setInitialProperties, setWMState, setWindowBorderWithFallback,
hide, reveal, tileWindow, hide, reveal, tileWindow,
setTopFocus, focus, setTopFocus, focus, isFixedSizeOrTransient,
-- * Manage Windows -- * Manage Windows
windows, refresh, rescreen, modifyWindowSet, windowBracket, windowBracket_, clearEvents, getCleanedScreenInfo, windows, refresh, rescreen, modifyWindowSet, windowBracket, windowBracket_, clearEvents, getCleanedScreenInfo,
@@ -27,7 +33,7 @@ module XMonad.Operations (
-- * Keyboard and Mouse -- * Keyboard and Mouse
cleanMask, extraModifiers, cleanMask, extraModifiers,
mouseDrag, mouseMoveWindow, mouseResizeWindow, mouseDrag, mouseMoveWindow, mouseResizeWindow,
setButtonGrab, setFocusX, setButtonGrab, setFocusX, cacheNumlockMask, mkGrabs,
-- * Messages -- * Messages
sendMessage, broadcastMessage, sendMessageWithNoRefresh, sendMessage, broadcastMessage, sendMessageWithNoRefresh,
@@ -57,7 +63,7 @@ import qualified XMonad.StackSet as W
import Data.Maybe import Data.Maybe
import Data.Monoid (Endo(..),Any(..)) import Data.Monoid (Endo(..),Any(..))
import Data.List (nub, (\\), find) import Data.List (nub, (\\), find)
import Data.Bits ((.|.), (.&.), complement, testBit) import Data.Bits ((.|.), (.&.), complement, setBit, testBit)
import Data.Function (on) import Data.Function (on)
import Data.Ratio import Data.Ratio
import qualified Data.Map as M import qualified Data.Map as M
@@ -66,6 +72,7 @@ import qualified Data.Set as S
import Control.Arrow (second) import Control.Arrow (second)
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.State import Control.Monad.State
import Control.Monad (void)
import qualified Control.Exception as C import qualified Control.Exception as C
import System.IO import System.IO
@@ -78,6 +85,16 @@ import Graphics.X11.Xlib.Extras
-- --------------------------------------------------------------------- -- ---------------------------------------------------------------------
-- Window manager operations -- Window manager operations
-- | Detect whether a window has fixed size or is transient. This check
-- can be used to determine whether the window should be floating or not
--
isFixedSizeOrTransient :: Display -> Window -> X Bool
isFixedSizeOrTransient d w = do
sh <- io $ getWMNormalHints d w
let isFixedSize = isJust (sh_min_size sh) && sh_min_size sh == sh_max_size sh
isTransient <- isJust <$> io (getTransientForHint d w)
return (isFixedSize || isTransient)
-- | -- |
-- Add a new window to be managed in the current workspace. -- Add a new window to be managed in the current workspace.
-- Bring it into focus. -- Bring it into focus.
@@ -87,10 +104,8 @@ import Graphics.X11.Xlib.Extras
-- --
manage :: Window -> X () manage :: Window -> X ()
manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do
sh <- io $ getWMNormalHints d w
let isFixedSize = sh_min_size sh /= Nothing && sh_min_size sh == sh_max_size sh shouldFloat <- isFixedSizeOrTransient d w
isTransient <- isJust <$> io (getTransientForHint d w)
rr <- snd `fmap` floatLocation w rr <- snd `fmap` floatLocation w
-- ensure that float windows don't go over the edge of the screen -- ensure that float windows don't go over the edge of the screen
@@ -98,8 +113,8 @@ manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do
= W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h = W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h
adjust r = r adjust r = r
f ws | isFixedSize || isTransient = W.float w (adjust rr) . W.insertUp w . W.view i $ ws f ws | shouldFloat = W.float w (adjust rr) . W.insertUp w . W.view i $ ws
| otherwise = W.insertUp w ws | otherwise = W.insertUp w ws
where i = W.tag $ W.workspace $ W.current ws where i = W.tag $ W.workspace $ W.current ws
mh <- asks (manageHook . config) mh <- asks (manageHook . config)
@@ -128,7 +143,7 @@ killWindow w = withDisplay $ \d -> do
setEventType ev clientMessage setEventType ev clientMessage
setClientMessageEvent ev w wmprot 32 wmdelt currentTime setClientMessageEvent ev w wmprot 32 wmdelt currentTime
sendEvent d w False noEventMask ev sendEvent d w False noEventMask ev
else killClient d w >> return () else void (killClient d w)
-- | Kill the currently focused client. -- | Kill the currently focused client.
kill :: X () kill :: X ()
@@ -179,7 +194,7 @@ windows f = do
let m = W.floating ws let m = W.floating ws
flt = [(fw, scaleRationalRect viewrect r) flt = [(fw, scaleRationalRect viewrect r)
| fw <- filter (flip M.member m) (W.index this) | fw <- filter (`M.member` m) (W.index this)
, Just r <- [M.lookup fw m]] , Just r <- [M.lookup fw m]]
vs = flt ++ rs vs = flt ++ rs
@@ -205,7 +220,7 @@ windows f = do
-- all windows that are no longer in the windowset are marked as -- all windows that are no longer in the windowset are marked as
-- withdrawn, it is important to do this after the above, otherwise 'hide' -- withdrawn, it is important to do this after the above, otherwise 'hide'
-- will overwrite withdrawnState with iconicState -- will overwrite withdrawnState with iconicState
mapM_ (flip setWMState withdrawnState) (W.allWindows old \\ W.allWindows ws) mapM_ (`setWMState` withdrawnState) (W.allWindows old \\ W.allWindows ws)
isMouseFocused <- asks mouseFocused isMouseFocused <- asks mouseFocused
unless isMouseFocused $ clearEvents enterWindowMask unless isMouseFocused $ clearEvents enterWindowMask
@@ -221,8 +236,8 @@ windowBracket :: (a -> Bool) -> X a -> X a
windowBracket p action = withWindowSet $ \old -> do windowBracket p action = withWindowSet $ \old -> do
a <- action a <- action
when (p a) . withWindowSet $ \new -> do when (p a) . withWindowSet $ \new -> do
modifyWindowSet $ \_ -> old modifyWindowSet $ const old
windows $ \_ -> new windows $ const new
return a return a
-- | Perform an @X@ action. If it returns @Any True@, unwind the -- | Perform an @X@ action. If it returns @Any True@, unwind the
@@ -250,12 +265,11 @@ setWindowBorderWithFallback :: Display -> Window -> String -> Pixel -> X ()
setWindowBorderWithFallback dpy w color basic = io $ setWindowBorderWithFallback dpy w color basic = io $
C.handle fallback $ do C.handle fallback $ do
wa <- getWindowAttributes dpy w wa <- getWindowAttributes dpy w
pixel <- color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color pixel <- setPixelSolid . color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color
setWindowBorder dpy w pixel setWindowBorder dpy w pixel
where where
fallback :: C.SomeException -> IO () fallback :: C.SomeException -> IO ()
fallback e = do hPrint stderr e >> hFlush stderr fallback _ = setWindowBorder dpy w basic
setWindowBorder dpy w basic
-- | Hide a window by unmapping it and setting Iconified. -- | Hide a window by unmapping it and setting Iconified.
hide :: Window -> X () hide :: Window -> X ()
@@ -342,15 +356,16 @@ getCleanedScreenInfo = io . fmap nubScreens . getScreenInfo
-- | The screen configuration may have changed (due to -- xrandr), -- | The screen configuration may have changed (due to -- xrandr),
-- update the state and refresh the screen, and reset the gap. -- update the state and refresh the screen, and reset the gap.
rescreen :: X () rescreen :: X ()
rescreen = do rescreen = withDisplay getCleanedScreenInfo >>= \case
xinesc <- withDisplay getCleanedScreenInfo [] -> trace "getCleanedScreenInfo returned []"
xinesc:xinescs ->
windows $ \ws@(W.StackSet { W.current = v, W.visible = vs, W.hidden = hs }) -> windows $ \ws@W.StackSet{ W.current = v, W.visible = vs, W.hidden = hs } ->
let (xs, ys) = splitAt (length xinesc) $ map W.workspace (v:vs) ++ hs let (xs, ys) = splitAt (length xinescs) (map W.workspace vs ++ hs)
(a:as) = zipWith3 W.Screen xs [0..] $ map SD xinesc a = W.Screen (W.workspace v) 0 (SD xinesc)
in ws { W.current = a as = zipWith3 W.Screen xs [1..] $ map SD xinescs
, W.visible = as in ws { W.current = a
, W.hidden = ys } , W.visible = as
, W.hidden = ys }
-- --------------------------------------------------------------------- -- ---------------------------------------------------------------------
@@ -409,7 +424,7 @@ setFocusX w = withWindowSet $ \ws -> do
currevt <- asks currentEvent currevt <- asks currentEvent
let inputHintSet = wmh_flags hints `testBit` inputHintBit let inputHintSet = wmh_flags hints `testBit` inputHintBit
when ((inputHintSet && wmh_input hints) || (not inputHintSet)) $ when (inputHintSet && wmh_input hints || not inputHintSet) $
io $ do setInputFocus dpy w revertToPointerRoot 0 io $ do setInputFocus dpy w revertToPointerRoot 0
when (wmtf `elem` protocols) $ when (wmtf `elem` protocols) $
io $ allocaXEvent $ \ev -> do io $ allocaXEvent $ \ev -> do
@@ -417,12 +432,46 @@ setFocusX w = withWindowSet $ \ws -> do
setClientMessageEvent ev w wmprot 32 wmtf $ maybe currentTime event_time currevt setClientMessageEvent ev w wmprot 32 wmtf $ maybe currentTime event_time currevt
sendEvent dpy w False noEventMask ev sendEvent dpy w False noEventMask ev
where event_time ev = where event_time ev =
if (ev_event_type ev) `elem` timedEvents then if ev_event_type ev `elem` timedEvents then
ev_time ev ev_time ev
else else
currentTime currentTime
timedEvents = [ keyPress, keyRelease, buttonPress, buttonRelease, enterNotify, leaveNotify, selectionRequest ] timedEvents = [ keyPress, keyRelease, buttonPress, buttonRelease, enterNotify, leaveNotify, selectionRequest ]
cacheNumlockMask :: X ()
cacheNumlockMask = do
dpy <- asks display
ms <- io $ getModifierMapping dpy
xs <- sequence [ do ks <- io $ keycodeToKeysym dpy kc 0
if ks == xK_Num_Lock
then return (setBit 0 (fromIntegral m))
else return (0 :: KeyMask)
| (m, kcs) <- ms, kc <- kcs, kc /= 0
]
modify (\s -> s { numberlockMask = foldr (.|.) 0 xs })
-- | Given a list of keybindings, turn the given 'KeySym's into actual
-- 'KeyCode's and prepare them for grabbing.
mkGrabs :: [(KeyMask, KeySym)] -> X [(KeyMask, KeyCode)]
mkGrabs ks = withDisplay $ \dpy -> do
let (minCode, maxCode) = displayKeycodes dpy
allCodes = [fromIntegral minCode .. fromIntegral maxCode]
-- build a map from keysyms to lists of keysyms (doing what
-- XGetKeyboardMapping would do if the X11 package bound it)
syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0)
let -- keycodeToKeysym returns noSymbol for all unbound keycodes,
-- and we don't want to grab those whenever someone accidentally
-- uses def :: KeySym
keysymMap = M.delete noSymbol $
M.fromListWith (++) (zip syms [[code] | code <- allCodes])
keysymToKeycodes sym = M.findWithDefault [] sym keysymMap
extraMods <- extraModifiers
pure [ (mask .|. extraMod, keycode)
| (mask, sym) <- ks
, keycode <- keysymToKeycodes sym
, extraMod <- extraMods
]
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- Message handling -- Message handling
@@ -430,7 +479,7 @@ setFocusX w = withWindowSet $ \ws -> do
-- layout the windows, in which case changes are handled through a refresh. -- layout the windows, in which case changes are handled through a refresh.
sendMessage :: Message a => a -> X () sendMessage :: Message a => a -> X ()
sendMessage a = windowBracket_ $ do sendMessage a = windowBracket_ $ do
w <- W.workspace . W.current <$> gets windowset w <- gets $ W.workspace . W.current . windowset
ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
whenJust ml' $ \l' -> whenJust ml' $ \l' ->
modifyWindowSet $ \ws -> ws { W.current = (W.current ws) modifyWindowSet $ \ws -> ws { W.current = (W.current ws)
@@ -465,9 +514,9 @@ updateLayout i ml = whenJust ml $ \l ->
-- | Set the layout of the currently viewed workspace. -- | Set the layout of the currently viewed workspace.
setLayout :: Layout Window -> X () setLayout :: Layout Window -> X ()
setLayout l = do setLayout l = do
ss@(W.StackSet { W.current = c@(W.Screen { W.workspace = ws })}) <- gets windowset ss@W.StackSet{ W.current = c@W.Screen{ W.workspace = ws }} <- gets windowset
handleMessage (W.layout ws) (SomeMessage ReleaseResources) handleMessage (W.layout ws) (SomeMessage ReleaseResources)
windows $ const $ ss {W.current = c { W.workspace = ws { W.layout = l } } } windows $ const $ ss{ W.current = c{ W.workspace = ws{ W.layout = l } } }
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- Utilities -- Utilities
@@ -504,10 +553,14 @@ cleanMask km = do
nlm <- gets numberlockMask nlm <- gets numberlockMask
return (complement (nlm .|. lockMask) .&. km) return (complement (nlm .|. lockMask) .&. km)
-- | Set the 'Pixel' alpha value to 255.
setPixelSolid :: Pixel -> Pixel
setPixelSolid p = p .|. 0xff000000
-- | Get the 'Pixel' value for a named color. -- | Get the 'Pixel' value for a named color.
initColor :: Display -> String -> IO (Maybe Pixel) initColor :: Display -> String -> IO (Maybe Pixel)
initColor dpy c = C.handle (\(C.SomeException _) -> return Nothing) $ initColor dpy c = C.handle (\(C.SomeException _) -> return Nothing) $
(Just . color_pixel . fst) <$> allocNamedColor dpy colormap c Just . setPixelSolid . color_pixel . fst <$> allocNamedColor dpy colormap c
where colormap = defaultColormap dpy (defaultScreen dpy) where colormap = defaultColormap dpy (defaultScreen dpy)
------------------------------------------------------------------------ ------------------------------------------------------------------------
@@ -527,7 +580,7 @@ writeStateToFile = do
maybeShow _ = Nothing maybeShow _ = Nothing
wsData = W.mapLayout show . windowset wsData = W.mapLayout show . windowset
extState = catMaybes . map maybeShow . M.toList . extensibleState extState = mapMaybe maybeShow . M.toList . extensibleState
path <- asks $ stateFileName . directories path <- asks $ stateFileName . directories
stateData <- gets (\s -> StateFile (wsData s) (extState s)) stateData <- gets (\s -> StateFile (wsData s) (extState s))
@@ -590,11 +643,10 @@ floatLocation w =
catchX go $ do catchX go $ do
-- Fallback solution if `go' fails. Which it might, since it -- Fallback solution if `go' fails. Which it might, since it
-- calls `getWindowAttributes'. -- calls `getWindowAttributes'.
sc <- W.current <$> gets windowset sc <- gets $ W.current . windowset
return (W.screen sc, W.RationalRect 0 0 1 1) return (W.screen sc, W.RationalRect 0 0 1 1)
where fi x = fromIntegral x where go = withDisplay $ \d -> do
go = withDisplay $ \d -> do
ws <- gets windowset ws <- gets windowset
wa <- io $ getWindowAttributes d w wa <- io $ getWindowAttributes d w
let bw = (fromIntegral . wa_border_width) wa let bw = (fromIntegral . wa_border_width) wa
@@ -620,6 +672,9 @@ floatLocation w =
return (W.screen sc, rr) return (W.screen sc, rr)
fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
-- | Given a point, determine the screen (if any) that contains it. -- | Given a point, determine the screen (if any) that contains it.
pointScreen :: Position -> Position pointScreen :: Position -> Position
-> X (Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)) -> X (Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail))
@@ -650,14 +705,20 @@ float w = do
-- | Accumulate mouse motion events -- | Accumulate mouse motion events
mouseDrag :: (Position -> Position -> X ()) -> X () -> X () mouseDrag :: (Position -> Position -> X ()) -> X () -> X ()
mouseDrag f done = do mouseDrag = mouseDragCursor Nothing
-- | Like 'mouseDrag', but with the ability to specify a custom cursor
-- shape.
mouseDragCursor :: Maybe Glyph -> (Position -> Position -> X ()) -> X () -> X ()
mouseDragCursor cursorGlyph f done = do
drag <- gets dragging drag <- gets dragging
case drag of case drag of
Just _ -> return () -- error case? we're already dragging Just _ -> return () -- error case? we're already dragging
Nothing -> do Nothing -> do
XConf { theRoot = root, display = d } <- ask XConf { theRoot = root, display = d } <- ask
io $ grabPointer d root False (buttonReleaseMask .|. pointerMotionMask) io $ do cursor <- maybe (pure none) (createFontCursor d) cursorGlyph
grabModeAsync grabModeAsync none none currentTime grabPointer d root False (buttonReleaseMask .|. pointerMotionMask)
grabModeAsync grabModeAsync none cursor currentTime
modify $ \s -> s { dragging = Just (motion, cleanup) } modify $ \s -> s { dragging = Just (motion, cleanup) }
where where
cleanup = do cleanup = do
@@ -675,7 +736,9 @@ mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do
(_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w (_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w
let ox = fromIntegral ox' let ox = fromIntegral ox'
oy = fromIntegral oy' oy = fromIntegral oy'
mouseDrag (\ex ey -> do mouseDragCursor
(Just xC_fleur)
(\ex ey -> do
io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox))) io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox)))
(fromIntegral (fromIntegral (wa_y wa) + (ey - oy))) (fromIntegral (fromIntegral (wa_y wa) + (ey - oy)))
float w float w
@@ -688,12 +751,13 @@ mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do
wa <- io $ getWindowAttributes d w wa <- io $ getWindowAttributes d w
sh <- io $ getWMNormalHints d w sh <- io $ getWMNormalHints d w
io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa)) io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa))
mouseDrag (\ex ey -> do mouseDragCursor
(Just xC_bottom_right_corner)
(\ex ey -> do
io $ resizeWindow d w `uncurry` io $ resizeWindow d w `uncurry`
applySizeHintsContents sh (ex - fromIntegral (wa_x wa), applySizeHintsContents sh (ex - fromIntegral (wa_x wa),
ey - fromIntegral (wa_y wa)) ey - fromIntegral (wa_y wa))
float w) float w)
(float w) (float w)
-- --------------------------------------------------------------------- -- ---------------------------------------------------------------------
@@ -709,7 +773,7 @@ mkAdjust w = withDisplay $ \d -> liftIO $ do
sh <- getWMNormalHints d w sh <- getWMNormalHints d w
wa <- C.try $ getWindowAttributes d w wa <- C.try $ getWindowAttributes d w
case wa of case wa of
Left err -> const (return id) (err :: C.SomeException) Left (_ :: C.SomeException) -> return id
Right wa' -> Right wa' ->
let bw = fromIntegral $ wa_border_width wa' let bw = fromIntegral $ wa_border_width wa'
in return $ applySizeHints bw sh in return $ applySizeHints bw sh

View File

@@ -58,6 +58,8 @@ import Data.Foldable (foldr, toList)
import Data.Maybe (listToMaybe,isJust,fromMaybe) import Data.Maybe (listToMaybe,isJust,fromMaybe)
import qualified Data.List as L (deleteBy,find,splitAt,filter,nub) import qualified Data.List as L (deleteBy,find,splitAt,filter,nub)
import Data.List ( (\\) ) import Data.List ( (\\) )
import qualified Data.List.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty((:|)))
import qualified Data.Map as M (Map,insert,delete,empty) import qualified Data.Map as M (Map,insert,delete,empty)
-- $intro -- $intro
@@ -92,21 +94,20 @@ import qualified Data.Map as M (Map,insert,delete,empty)
-- resulting data structure will share as much of its components with -- resulting data structure will share as much of its components with
-- the old structure as possible. -- the old structure as possible.
-- --
-- Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation" -- <https://mail.haskell.org/pipermail/haskell/2005-April/015769.html Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation">
-- --
-- We use the zipper to keep track of the focused workspace and the -- We use the zipper to keep track of the focused workspace and the
-- focused window on each workspace, allowing us to have correct focus -- focused window on each workspace, allowing us to have correct focus
-- by construction. We closely follow Huet's original implementation: -- by construction. We closely follow Huet's original implementation:
-- --
-- G. Huet, /Functional Pearl: The Zipper/, -- <https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf G. Huet, Functional Pearl: The Zipper; 1997, J. Functional Programming 75(5):549554>
-- 1997, J. Functional Programming 75(5):549-554. --
-- and: -- and
-- R. Hinze and J. Jeuring, /Functional Pearl: The Web/. --
-- <https://dspace.library.uu.nl/handle/1874/2532 R. Hinze and J. Jeuring, Functional Pearl: Weaving a Web>
-- --
-- and Conor McBride's zipper differentiation paper. -- and Conor McBride's zipper differentiation paper.
-- Another good reference is: -- Another good reference is: <https://wiki.haskell.org/Zipper The Zipper, Haskell wikibook>
--
-- The Zipper, Haskell wikibook
-- $xinerama -- $xinerama
-- Xinerama in X11 lets us view multiple virtual workspaces -- Xinerama in X11 lets us view multiple virtual workspaces
@@ -208,10 +209,11 @@ abort x = error $ "xmonad: StackSet: " ++ x
-- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0. -- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0.
-- --
new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd
new l wids m | not (null wids) && length m <= length wids && not (null m) new l (wid:wids) (m:ms) | length ms <= length wids
= StackSet cur visi unseen M.empty = StackSet cur visi (map ws unseen) M.empty
where (seen,unseen) = L.splitAt (length m) $ map (\i -> Workspace i l Nothing) wids where ws i = Workspace i l Nothing
(cur:visi) = [ Screen i s sd | (i, s, sd) <- zip3 seen [0..] m ] (seen, unseen) = L.splitAt (length ms) wids
cur:visi = Screen (ws wid) 0 m : [ Screen (ws i) s sd | (i, s, sd) <- zip3 seen [1..] ms ]
-- now zip up visibles with their screen id -- now zip up visibles with their screen id
new _ _ _ = abort "non-positive argument to StackSet.new" new _ _ _ = abort "non-positive argument to StackSet.new"
@@ -238,7 +240,7 @@ view i s
| otherwise = s -- not a member of the stackset | otherwise = s -- not a member of the stackset
where equating f = \x y -> f x == f y where equating f x y = f x == f y
-- 'Catch'ing this might be hard. Relies on monotonically increasing -- 'Catch'ing this might be hard. Relies on monotonically increasing
-- workspace tags defined in 'new' -- workspace tags defined in 'new'
@@ -311,7 +313,7 @@ integrate :: Stack a -> [a]
integrate (Stack x l r) = reverse l ++ x : r integrate (Stack x l r) = reverse l ++ x : r
-- | -- |
-- /O(n)/ Flatten a possibly empty stack into a list. -- /O(n)/. Flatten a possibly empty stack into a list.
integrate' :: Maybe (Stack a) -> [a] integrate' :: Maybe (Stack a) -> [a]
integrate' = maybe [] integrate integrate' = maybe [] integrate
@@ -343,32 +345,44 @@ filter p (Stack f ls rs) = case L.filter p (f:rs) of
index :: StackSet i l a s sd -> [a] index :: StackSet i l a s sd -> [a]
index = with [] integrate index = with [] integrate
-- | -- | /O(1), O(w) on the wrapping case/. Move the window focus up the
-- /O(1), O(w) on the wrapping case/. -- stack, wrapping if we reach the end. The wrapping should model a
-- -- @cycle@ on the current stack. The @master@ window and window order
-- focusUp, focusDown. Move the window focus up or down the stack,
-- wrapping if we reach the end. The wrapping should model a 'cycle'
-- on the current stack. The 'master' window, and window order,
-- are unaffected by movement of focus. -- are unaffected by movement of focus.
-- focusUp :: StackSet i l a s sd -> StackSet i l a s sd
-- swapUp, swapDown, swap the neighbour in the stack ordering, wrapping
-- if we reach the end. Again the wrapping model should 'cycle' on
-- the current stack.
--
focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd
focusUp = modify' focusUp' focusUp = modify' focusUp'
-- | /O(1), O(w) on the wrapping case/. Like 'focusUp', but move the
-- window focus down the stack.
focusDown :: StackSet i l a s sd -> StackSet i l a s sd
focusDown = modify' focusDown' focusDown = modify' focusDown'
-- | /O(1), O(w) on the wrapping case/. Swap the upwards (left)
-- neighbour in the stack ordering, wrapping if we reach the end. Much
-- like for 'focusUp' and 'focusDown', the wrapping model should 'cycle'
-- on the current stack.
swapUp :: StackSet i l a s sd -> StackSet i l a s sd
swapUp = modify' swapUp' swapUp = modify' swapUp'
-- | /O(1), O(w) on the wrapping case/. Like 'swapUp', but for swapping
-- the downwards (right) neighbour.
swapDown :: StackSet i l a s sd -> StackSet i l a s sd
swapDown = modify' (reverseStack . swapUp' . reverseStack) swapDown = modify' (reverseStack . swapUp' . reverseStack)
-- | Variants of 'focusUp' and 'focusDown' that work on a -- | A variant of 'focusUp' with the same asymptotics that works on a
-- 'Stack' rather than an entire 'StackSet'. -- 'Stack' rather than an entire 'StackSet'.
focusUp', focusDown' :: Stack a -> Stack a focusUp' :: Stack a -> Stack a
focusUp' (Stack t (l:ls) rs) = Stack l ls (t:rs) focusUp' (Stack t (l:ls) rs) = Stack l ls (t:rs)
focusUp' (Stack t [] rs) = Stack x xs [] where (x:xs) = reverse (t:rs) focusUp' (Stack t [] rs) = Stack x xs []
focusDown' = reverseStack . focusUp' . reverseStack where (x :| xs) = NE.reverse (t :| rs)
-- | A variant of 'focusDown' with the same asymptotics that works on a
-- 'Stack' rather than an entire 'StackSet'.
focusDown' :: Stack a -> Stack a
focusDown' = reverseStack . focusUp' . reverseStack
-- | A variant of 'spawUp' with the same asymptotics that works on a
-- 'Stack' rather than an entire 'StackSet'.
swapUp' :: Stack a -> Stack a swapUp' :: Stack a -> Stack a
swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs) swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs)
swapUp' (Stack t [] rs) = Stack t (reverse rs) [] swapUp' (Stack t [] rs) = Stack t (reverse rs) []
@@ -522,8 +536,8 @@ sink w s = s { floating = M.delete w (floating s) }
-- Focus stays with the item moved. -- Focus stays with the item moved.
swapMaster :: StackSet i l a s sd -> StackSet i l a s sd swapMaster :: StackSet i l a s sd -> StackSet i l a s sd
swapMaster = modify' $ \c -> case c of swapMaster = modify' $ \c -> case c of
Stack _ [] _ -> c -- already master. Stack _ [] _ -> c -- already master.
Stack t ls rs -> Stack t [] (xs ++ x : rs) where (x:xs) = reverse ls Stack t (l:ls) rs -> Stack t [] (xs ++ x : rs) where (x :| xs) = NE.reverse (l :| ls)
-- natural! keep focus, move current to the top, move top to current. -- natural! keep focus, move current to the top, move top to current.
@@ -539,8 +553,8 @@ shiftMaster = modify' $ \c -> case c of
-- | /O(s)/. Set focus to the master window. -- | /O(s)/. Set focus to the master window.
focusMaster :: StackSet i l a s sd -> StackSet i l a s sd focusMaster :: StackSet i l a s sd -> StackSet i l a s sd
focusMaster = modify' $ \c -> case c of focusMaster = modify' $ \c -> case c of
Stack _ [] _ -> c Stack _ [] _ -> c
Stack t ls rs -> Stack x [] (xs ++ t : rs) where (x:xs) = reverse ls Stack t (l:ls) rs -> Stack x [] (xs ++ t : rs) where (x :| xs) = NE.reverse (l :| ls)
-- --
-- --------------------------------------------------------------------- -- ---------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
resolver: lts-16.22 resolver: lts-19.6
packages: packages:
- ./ - ./

View File

@@ -36,7 +36,7 @@ instance (Integral i, Integral s, Eq a, Arbitrary a, Arbitrary l, Arbitrary sd)
-- Pick a random window "number" in each workspace, to give focus. -- Pick a random window "number" in each workspace, to give focus.
focus <- sequence [ if null windows focus <- sequence [ if null windows
then return Nothing then return Nothing
else liftM Just $ choose (0, length windows - 1) else Just <$> choose (0, length windows - 1)
| windows <- wsWindows ] | windows <- wsWindows ]
let tags = [1 .. fromIntegral numWs] let tags = [1 .. fromIntegral numWs]
@@ -80,7 +80,7 @@ newtype NonEmptyWindowsStackSet = NonEmptyWindowsStackSet T
instance Arbitrary NonEmptyWindowsStackSet where instance Arbitrary NonEmptyWindowsStackSet where
arbitrary = arbitrary =
NonEmptyWindowsStackSet `fmap` (arbitrary `suchThat` (not . null . allWindows)) NonEmptyWindowsStackSet <$> (arbitrary `suchThat` (not . null . allWindows))
instance Arbitrary Rectangle where instance Arbitrary Rectangle where
arbitrary = Rectangle <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary arbitrary = Rectangle <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
@@ -99,7 +99,7 @@ newtype NonEmptyNubList a = NonEmptyNubList [a]
deriving ( Eq, Ord, Show, Read ) deriving ( Eq, Ord, Show, Read )
instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where
arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null)) arbitrary = NonEmptyNubList <$> ((nub <$> arbitrary) `suchThat` (not . null))
@@ -116,7 +116,7 @@ arbitraryTag :: T -> Gen Tag
arbitraryTag x = do arbitraryTag x = do
let ts = tags x let ts = tags x
-- There must be at least 1 workspace, thus at least 1 tag. -- There must be at least 1 workspace, thus at least 1 tag.
idx <- choose (0, (length ts) - 1) idx <- choose (0, length ts - 1)
return $ ts!!idx return $ ts!!idx
-- | Pull out an arbitrary window from a StackSet that is guaranteed to have a -- | Pull out an arbitrary window from a StackSet that is guaranteed to have a
@@ -136,5 +136,5 @@ arbitraryWindow :: NonEmptyWindowsStackSet -> Gen Window
arbitraryWindow (NonEmptyWindowsStackSet x) = do arbitraryWindow (NonEmptyWindowsStackSet x) = do
let ws = allWindows x let ws = allWindows x
-- We know that there are at least 1 window in a NonEmptyWindowsStackSet. -- We know that there are at least 1 window in a NonEmptyWindowsStackSet.
idx <- choose(0, (length ws) - 1) idx <- choose (0, length ws - 1)
return $ ws!!idx return $ ws!!idx

View File

@@ -64,7 +64,7 @@ prop_delete_focus_not_end = do
-- last one in the stack. -- last one in the stack.
`suchThat` \(x' :: T) -> `suchThat` \(x' :: T) ->
let currWins = index x' let currWins = index x'
in length (currWins) >= 2 && peek x' /= Just (last currWins) in length currWins >= 2 && peek x' /= Just (last currWins)
-- This is safe, as we know there are >= 2 windows -- This is safe, as we know there are >= 2 windows
let Just n = peek x let Just n = peek x
return $ peek (delete n x) == peek (focusDown x) return $ peek (delete n x) == peek (focusDown x)

View File

@@ -32,8 +32,8 @@ prop_focusWindow_master (NonNegative n) (x :: T) =
in index (focusWindow (s !! i) x) == index x in index (focusWindow (s !! i) x) == index x
-- shifting focus is trivially reversible -- shifting focus is trivially reversible
prop_focus_left (x :: T) = (focusUp (focusDown x)) == x prop_focus_left (x :: T) = focusUp (focusDown x) == x
prop_focus_right (x :: T) = (focusDown (focusUp x)) == x prop_focus_right (x :: T) = focusDown (focusUp x) == x
-- focus master is idempotent -- focus master is idempotent
prop_focusMaster_idem (x :: T) = focusMaster x == focusMaster (focusMaster x) prop_focusMaster_idem (x :: T) = focusMaster x == focusMaster (focusMaster x)
@@ -47,9 +47,9 @@ prop_focusWindow_works (NonNegative (n :: Int)) (x :: T) =
in (focus . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i) in (focus . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i)
-- rotation through the height of a stack gets us back to the start -- rotation through the height of a stack gets us back to the start
prop_focus_all_l (x :: T) = (foldr (const focusUp) x [1..n]) == x prop_focus_all_l (x :: T) = foldr (const focusUp) x [1..n] == x
where n = length (index x) where n = length (index x)
prop_focus_all_r (x :: T) = (foldr (const focusDown) x [1..n]) == x prop_focus_all_r (x :: T) = foldr (const focusDown) x [1..n] == x
where n = length (index x) where n = length (index x)
-- prop_rotate_all (x :: T) = f (f x) == f x -- prop_rotate_all (x :: T) = f (f x) == f x

View File

@@ -35,7 +35,7 @@ prop_greedyView_local (x :: T) = do
-- greedyView is idempotent -- greedyView is idempotent
prop_greedyView_idem (x :: T) = do prop_greedyView_idem (x :: T) = do
n <- arbitraryTag x n <- arbitraryTag x
return $ greedyView n (greedyView n x) == (greedyView n x) return $ greedyView n (greedyView n x) == greedyView n x
-- greedyView is reversible, though shuffles the order of hidden/visible -- greedyView is reversible, though shuffles the order of hidden/visible
prop_greedyView_reversible (x :: T) = do prop_greedyView_reversible (x :: T) = do

View File

@@ -46,7 +46,7 @@ prop_insert_delete x = do
-- inserting n elements increases current stack size by n -- inserting n elements increases current stack size by n
prop_size_insert is (EmptyStackSet x) = prop_size_insert is (EmptyStackSet x) =
size (foldr insertUp x ws ) == (length ws) size (foldr insertUp x ws) == length ws
where where
ws = nub is ws = nub is
size = length . index size = length . index

View File

@@ -29,6 +29,6 @@ prop_purelayout_full rect = do
-- what happens when we send an IncMaster message to Full --- Nothing -- what happens when we send an IncMaster message to Full --- Nothing
prop_sendmsg_full (NonNegative k) = prop_sendmsg_full (NonNegative k) =
isNothing (Full `pureMessage` (SomeMessage (IncMasterN k))) isNothing (Full `pureMessage` SomeMessage (IncMasterN k))
prop_desc_full = description Full == show Full prop_desc_full = description Full == show Full

View File

@@ -29,12 +29,12 @@ prop_tile_non_overlap rect windows nmaster = noOverlaps (tile pct rect nmaster w
-- splitting horizontally yields sensible results -- splitting horizontally yields sensible results
prop_split_horizontal (NonNegative n) x = prop_split_horizontal (NonNegative n) x =
(noOverflows (+) (rect_x x) (rect_width x)) ==> noOverflows (+) (rect_x x) (rect_width x) ==>
sum (map rect_width xs) == rect_width x sum (map rect_width xs) == rect_width x
&& &&
all (== rect_height x) (map rect_height xs) all (\s -> rect_height s == rect_height x) xs
&& &&
(map rect_x xs) == (sort $ map rect_x xs) map rect_x xs == sort (map rect_x xs)
where where
xs = splitHorizontally n x xs = splitHorizontally n x
@@ -72,7 +72,7 @@ prop_shrink_tall (NonNegative n) (Positive delta) (NonNegative frac) =
-- remaining fraction should shrink -- remaining fraction should shrink
where where
l1 = Tall n delta frac l1 = Tall n delta frac
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Shrink) Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage Shrink
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
@@ -93,7 +93,7 @@ prop_expand_tall (NonNegative n)
where where
frac = min 1 (n1 % d1) frac = min 1 (n1 % d1)
l1 = Tall n delta frac l1 = Tall n delta frac
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Expand) Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage Expand
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
-- what happens when we send an IncMaster message to Tall -- what happens when we send an IncMaster message to Tall
@@ -102,7 +102,7 @@ prop_incmaster_tall (NonNegative n) (Positive delta) (NonNegative frac)
delta == delta' && frac == frac' && n' == n + k delta == delta' && frac == frac' && n' == n + k
where where
l1 = Tall n delta frac l1 = Tall n delta frac
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage (IncMasterN k)) Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage (IncMasterN k)
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)

View File

@@ -53,8 +53,8 @@ prop_aspect_hint_shrink hint (w,h) = case applyAspectHint hint (w,h) of
-- the desired range -- the desired range
prop_aspect_fits = prop_aspect_fits =
forAll ((,,,) <$> pos <*> pos <*> pos <*> pos) $ \ (x,y,a,b) -> forAll ((,,,) <$> pos <*> pos <*> pos <*> pos) $ \ (x,y,a,b) ->
let f v = applyAspectHint ((x, y+a), (x+b, y)) v let f = applyAspectHint ((x, y+a), (x+b, y))
in and [ noOverflows (*) x (y+a), noOverflows (*) (x+b) y ] in noOverflows (*) x (y+a) && noOverflows (*) (x+b) y
==> f (x,y) == (x,y) ==> f (x,y) == (x,y)
where pos = choose (0, 65535) where pos = choose (0, 65535)

View File

@@ -27,7 +27,7 @@ prop_shift_reversible (x :: T) = do
-- shiftMaster -- shiftMaster
-- focus/local/idempotent same as swapMaster: -- focus/local/idempotent same as swapMaster:
prop_shift_master_focus (x :: T) = peek x == (peek $ shiftMaster x) prop_shift_master_focus (x :: T) = peek x == peek (shiftMaster x)
prop_shift_master_local (x :: T) = hidden_spaces x == hidden_spaces (shiftMaster x) prop_shift_master_local (x :: T) = hidden_spaces x == hidden_spaces (shiftMaster x)
prop_shift_master_idempotent (x :: T) = shiftMaster (shiftMaster x) == shiftMaster x prop_shift_master_idempotent (x :: T) = shiftMaster (shiftMaster x) == shiftMaster x
-- ordering is constant modulo the focused window: -- ordering is constant modulo the focused window:
@@ -57,14 +57,14 @@ prop_shift_win_fix_current = do
x <- arbitrary `suchThat` \(x' :: T) -> x <- arbitrary `suchThat` \(x' :: T) ->
-- Invariant, otherWindows are NOT in the current workspace. -- Invariant, otherWindows are NOT in the current workspace.
let otherWindows = allWindows x' L.\\ index x' let otherWindows = allWindows x' L.\\ index x'
in length(tags x') >= 2 && length(otherWindows) >= 1 in length (tags x') >= 2 && not (null otherWindows)
-- Sadly we have to construct `otherWindows` again, for the actual StackSet -- Sadly we have to construct `otherWindows` again, for the actual StackSet
-- that got chosen. -- that got chosen.
let otherWindows = allWindows x L.\\ index x let otherWindows = allWindows x L.\\ index x
-- We know such tag must exists, due to the precondition -- We know such tag must exists, due to the precondition
n <- arbitraryTag x `suchThat` (/= currentTag x) n <- arbitraryTag x `suchThat` (/= currentTag x)
-- we know length is >= 1, from above precondition -- we know length is >= 1, from above precondition
idx <- choose(0, length(otherWindows) - 1) idx <- choose (0, length otherWindows - 1)
let w = otherWindows !! idx let w = otherWindows !! idx
return $ (current $ x) == (current $ shiftWin n w x) return $ current x == current (shiftWin n w x)

View File

@@ -1,6 +1,10 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
#ifdef VERSION_quickcheck_classes
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
#endif
module Properties.Stack where module Properties.Stack where
import Test.QuickCheck import Test.QuickCheck
@@ -24,7 +28,7 @@ import Test.QuickCheck.Classes (
-- windows kept in the zipper -- windows kept in the zipper
prop_index_length (x :: T) = prop_index_length (x :: T) =
case stack . workspace . current $ x of case stack . workspace . current $ x of
Nothing -> length (index x) == 0 Nothing -> null (index x)
Just it -> length (index x) == length (focus it : up it ++ down it) Just it -> length (index x) == length (focus it : up it ++ down it)
@@ -43,7 +47,7 @@ prop_allWindowsMember (NonEmptyWindowsStackSet x) = do
-- which is a key component in this test (together with member). -- which is a key component in this test (together with member).
let ws = allWindows x let ws = allWindows x
-- We know that there are at least 1 window in a NonEmptyWindowsStackSet. -- We know that there are at least 1 window in a NonEmptyWindowsStackSet.
idx <- choose(0, (length ws) - 1) idx <- choose (0, length ws - 1)
return $ member (ws!!idx) x return $ member (ws!!idx) x
@@ -56,8 +60,8 @@ prop_filter_order (x :: T) =
-- differentiate should return Nothing if the list is empty or Just stack, with -- differentiate should return Nothing if the list is empty or Just stack, with
-- the first element of the list is current, and the rest of the list is down. -- the first element of the list is current, and the rest of the list is down.
prop_differentiate xs = prop_differentiate xs =
if null xs then differentiate xs == Nothing if null xs then isNothing (differentiate xs)
else (differentiate xs) == Just (Stack (head xs) [] (tail xs)) else differentiate xs == Just (Stack (head xs) [] (tail xs))
where _ = xs :: [Int] where _ = xs :: [Int]

View File

@@ -58,7 +58,7 @@ invariant (s :: T) = and
-- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ] -- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
monotonic [] = True monotonic [] = True
monotonic (x:[]) = True monotonic [x] = True
monotonic (x:y:zs) | x == y-1 = monotonic (y:zs) monotonic (x:y:zs) | x == y-1 = monotonic (y:zs)
| otherwise = False | otherwise = False
@@ -126,7 +126,7 @@ prop_empty (EmptyStackSet x) =
prop_empty_current (EmptyStackSet x) = currentTag x == head (tags x) prop_empty_current (EmptyStackSet x) = currentTag x == head (tags x)
-- no windows will be a member of an empty workspace -- no windows will be a member of an empty workspace
prop_member_empty i (EmptyStackSet x) = member i x == False prop_member_empty i (EmptyStackSet x) = not (member i x)
-- peek either yields nothing on the Empty workspace, or Just a valid window -- peek either yields nothing on the Empty workspace, or Just a valid window
prop_member_peek (x :: T) = prop_member_peek (x :: T) =

View File

@@ -11,8 +11,8 @@ import XMonad.StackSet hiding (filter)
-- swapUp, swapDown, swapMaster: reordiring windows -- swapUp, swapDown, swapMaster: reordiring windows
-- swap is trivially reversible -- swap is trivially reversible
prop_swap_left (x :: T) = (swapUp (swapDown x)) == x prop_swap_left (x :: T) = swapUp (swapDown x) == x
prop_swap_right (x :: T) = (swapDown (swapUp x)) == x prop_swap_right (x :: T) = swapDown (swapUp x) == x
-- TODO swap is reversible -- TODO swap is reversible
-- swap is reversible, but involves moving focus back the window with -- swap is reversible, but involves moving focus back the window with
-- master on it. easy to do with a mouse... -- master on it. easy to do with a mouse...
@@ -26,12 +26,12 @@ prop_promote_reversible x b = (not . null . fromMaybe [] . flip index x . curren
-} -}
-- swap doesn't change focus -- swap doesn't change focus
prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x) prop_swap_master_focus (x :: T) = peek x == peek (swapMaster x)
-- = case peek x of -- = case peek x of
-- Nothing -> True -- Nothing -> True
-- Just f -> focus (stack (workspace $ current (swap x))) == f -- Just f -> focus (stack (workspace $ current (swap x))) == f
prop_swap_left_focus (x :: T) = peek x == (peek $ swapUp x) prop_swap_left_focus (x :: T) = peek x == peek (swapUp x)
prop_swap_right_focus (x :: T) = peek x == (peek $ swapDown x) prop_swap_right_focus (x :: T) = peek x == peek (swapDown x)
-- swap is local -- swap is local
prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x) prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x)
@@ -39,9 +39,9 @@ prop_swap_left_local (x :: T) = hidden_spaces x == hidden_spaces (swapUp x)
prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x) prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x)
-- rotation through the height of a stack gets us back to the start -- rotation through the height of a stack gets us back to the start
prop_swap_all_l (x :: T) = (foldr (const swapUp) x [1..n]) == x prop_swap_all_l (x :: T) = foldr (const swapUp) x [1..n] == x
where n = length (index x) where n = length (index x)
prop_swap_all_r (x :: T) = (foldr (const swapDown) x [1..n]) == x prop_swap_all_r (x :: T) = foldr (const swapDown) x [1..n] == x
where n = length (index x) where n = length (index x)
prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x

View File

@@ -37,7 +37,7 @@ prop_view_local (x :: T) = do
-- view is idempotent -- view is idempotent
prop_view_idem (x :: T) = do prop_view_idem (x :: T) = do
n <- arbitraryTag x n <- arbitraryTag x
return $ view n (view n x) == (view n x) return $ view n (view n x) == view n x
-- view is reversible, though shuffles the order of hidden/visible -- view is reversible, though shuffles the order of hidden/visible
prop_view_reversible (x :: T) = do prop_view_reversible (x :: T) = do

View File

@@ -12,8 +12,8 @@ hidden_spaces x = map workspace (visible x) ++ hidden x
-- normalise workspace list -- normalise workspace list
normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) } normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) }
where where
f = \a b -> tag (workspace a) `compare` tag (workspace b) f a b = tag (workspace a) `compare` tag (workspace b)
g = \a b -> tag a `compare` tag b g a b = tag a `compare` tag b
noOverlaps [] = True noOverlaps [] = True

View File

@@ -1,5 +1,5 @@
name: xmonad name: xmonad
version: 0.17.0 version: 0.17.1
synopsis: A tiling window manager synopsis: A tiling window manager
description: xmonad is a tiling window manager for X. Windows are arranged description: xmonad is a tiling window manager for X. Windows are arranged
automatically to tile the screen without gaps or overlap, maximising automatically to tile the screen without gaps or overlap, maximising
@@ -25,9 +25,9 @@ author: Spencer Janssen, Don Stewart, Adam Vogt, David Roundy, Jason
Jens Petersen, Joey Hess, Jonne Ransijn, Josh Holland, Khudyakov Alexey, Jens Petersen, Joey Hess, Jonne Ransijn, Josh Holland, Khudyakov Alexey,
Klaus Weidner, Michael G. Sloan, Mikkel Christiansen, Nicolas Dudebout, Klaus Weidner, Michael G. Sloan, Mikkel Christiansen, Nicolas Dudebout,
Ondřej Súkup, Paul Hebble, Shachaf Ben-Kiki, Siim Põder, Tim McIver, Ondřej Súkup, Paul Hebble, Shachaf Ben-Kiki, Siim Põder, Tim McIver,
Trevor Elliott, Wouter Swierstra, Conrad Irwin, Tim Thelion Trevor Elliott, Wouter Swierstra, Conrad Irwin, Tim Thelion, Tony Zorman
maintainer: xmonad@haskell.org maintainer: xmonad@haskell.org
tested-with: GHC == 8.4.4 || == 8.6.5 || == 8.8.4 || == 8.10.4 || == 9.0.1 tested-with: GHC == 8.4.4 || == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.2
category: System category: System
homepage: http://xmonad.org homepage: http://xmonad.org
bug-reports: https://github.com/xmonad/xmonad/issues bug-reports: https://github.com/xmonad/xmonad/issues