employ haskell.nix and write new post

nek0 2021-06-27 21:37:59 +02:00
parent 133e847735
commit 958ad779fc
5 changed files with 445 additions and 32 deletions

default.nix Normal file
View File

@ -0,0 +1,30 @@
# Read in the Niv sources
sources = import ./nix/sources.nix {};
# If ./nix/sources.nix file is not found run:
# niv init
# niv add input-output-hk/haskell.nix -n haskellNix
# Fetch the haskell.nix commit we have pinned with Niv
haskellNix = import sources.haskellNix {};
# If haskellNix is not found run:
# niv add input-output-hk/haskell.nix -n haskellNix
# Import nixpkgs and pass the haskell.nix provided nixpkgsArgs
pkgs = import
# haskell.nix provides access to the nixpkgs pins which are used by our CI,
# hence you will be more likely to get cache hits when using these.
# But you can also just use your own, e.g. '<nixpkgs>'.
# These arguments passed to nixpkgs, include some patches and also
# the haskell.nix functionality itself as an overlay.
in pkgs.haskell-nix.project {
# 'cleanGit' cleans a source directory based on the files known by git
src = pkgs.haskell-nix.haskellLib.cleanGit {
name = "haskell-nix-project";
src = ./.;
# Specify the GHC version to use.
compiler-nix-name = "ghc8104"; # Not required for `stack.yaml` based projects.

nix/sources.json Normal file
View File

@ -0,0 +1,38 @@
"haskellNix": {
"branch": "master",
"description": "Alternative Haskell Infrastructure for Nixpkgs",
"homepage": "https://input-output-hk.github.io/haskell.nix",
"owner": "input-output-hk",
"repo": "haskell.nix",
"rev": "3648880358c8d6fda984b18bd4c6c8d6bed98a1a",
"sha256": "1ci0hpw8jff8i6chbwv8834k6righ5nsswx5vl491lnkr6j2b8fp",
"type": "tarball",
"url": "https://github.com/input-output-hk/haskell.nix/archive/3648880358c8d6fda984b18bd4c6c8d6bed98a1a.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070",
"sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
"nixpkgs": {
"branch": "release-20.03",
"description": "Nix Packages collection",
"homepage": "",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "246b75a0d0d7da135a3e62c686b98975c30df42c",
"sha256": "0px8k4lh5nhq6f7pxqw8yawl5494njv22z4rmssywqkanf1xjs32",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/246b75a0d0d7da135a3e62c686b98975c30df42c.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"

nix/sources.nix Normal file
View File

@ -0,0 +1,174 @@
# This file has been generated by Niv.
# The fetchers. fetch_<type> fetches specs of type <type>.
fetch_file = pkgs: name: spec:
name' = sanitizeName name + "-src";
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
fetch_tarball = pkgs: name: spec:
name' = sanitizeName name + "-src";
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = name: spec:
ref =
if spec ? ref then spec.ref else
if spec ? branch then "refs/heads/${spec.branch}" else
if spec ? tag then "refs/tags/${spec.tag}" else
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
fetch_local = spec: spec.path;
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
# Various helpers
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
sanitizeName = name:
concatMapStrings (s: if builtins.isList s then "-" else s)
builtins.split "[^[:alnum:]+._?=-]+"
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources: system:
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> {}
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs name spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git name spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
if ersatz == "" then drv else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
optionalAttrs = cond: as: if cond then as else {};
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
inherit (builtins) lessThan nixVersion fetchTarball;
if lessThan nixVersion "1.12" then
fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
inherit (builtins) lessThan nixVersion fetchurl;
if lessThan nixVersion "1.12" then
fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (
name: spec:
if builtins.hasAttr "outPath" spec
then abort
"The values in sources.json should not have an 'outPath' attribute"
spec // { outPath = replace name (fetch config.pkgs name spec); }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
, system ? builtins.currentSystem
, pkgs ? mkPkgs sources system
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

View File

@ -1,35 +1,38 @@
{ nixpkgs ? import <nixpkgs> {}, compiler ? "default", doBenchmark ? false }:
# shell.nix
inherit (nixpkgs) pkgs;
f = { mkDerivation, base, directory, filepath, hakyll, HTTP
, stdenv, time
mkDerivation {
pname = "nek0-blog";
version = "";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [
base directory filepath hakyll HTTP time
homepage = "https://nek0.eu";
description = "My blog";
license = "unknown";
hydraPlatforms = stdenv.lib.platforms.none;
haskellPackages = if compiler == "default"
then pkgs.haskellPackages
else pkgs.haskell.packages.${compiler};
variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id;
drv = variant (haskellPackages.callPackage f {});
project = import ./default.nix;
project.shellFor {
# ALL of these arguments are optional.
if pkgs.lib.inNixShell then drv.env else drv
# List of packages from the project you want to work on in
# the shell (default is all the projects local packages).
#packages = ps: with ps; [
# pkga
# pkgb
# Builds a Hoogle documentation index of all dependencies,
# and provides a "hoogle" command to search the index.
withHoogle = false;
# Some common tools can be added with the `tools` argument
tools = {
cabal = "latest";
hakyll = "latest";
# See overlays/tools.nix for more details
# Some you may need to get some other way.
buildInputs = [ (import <nixpkgs> {}).git ];
# Sellect cross compilers to include.
#crossPlatforms = ps: with ps; [
# ghcjs # Adds support for `js-unknown-ghcjs-cabal build` in the shell
# # mingwW64 # Adds support for `x86_64-W64-mingw32-cabal build` in the shell
# Prevents cabal from choosing alternate plans, so that
# *all* dependencies are provided by Nix.
exactDeps = true;

View File

@ -0,0 +1,168 @@
title: Hacking my XP-Pen drawing tablets
author: nek0
- hardware
- hacking
- drawing
description: Enabling functions on my XP-Pen tablets without proprietary driver
Hi there,
Long time no see. I am currently quite busy finishing my apprenticeship so my
free time suffers a bit, but my goal is now in reach. Only my oral exam is left
to pass and then I will be done.
So today I want to write about some hardware issues I had with my drawing
tablets and which I solved thanks to
[this blog post by David Revoy](https://www.davidrevoy.com/article842/review-of-the-xp-pen-artist-24-pro-on-linux).
I have been drawing digitally a lot in my free time and thus own at least one
drawing tablet. I stuck with the brand "XP-Pen" as they seem to have a fairly
good quality for a reasonable price. Also they seem to support linux (at least
in their own strange way). They do ship a driver for their tablets, which is
proprietary and needs to run in the background all the time and seems not to
be able to remember its settings (at least not on my [NixOS][nixos]).
So up until recently I was bound to work with what worked out of the box with
Deco Pro M, which was most of the functionality it provided, even the pen tilt
was recognized out of the box. The only things I was missing was making a
right-click with my pen, assigning the hotkeys on my tablet and mapping the
tablet to a specific Display on a multi monitor setup.
This is where David's blog post enters the stage, where he provides a detailed
guide on how to set up a XP-Pen Artist 24 drawing monitor in linux without
using the proprietary driver. I was able to extrapolate from that and adjust my
`/etc/nixos/configuration.nix` to configure my tablet according to my needs.
What I want to try with this blog post is to provide a more general guide on
how to configure most, if not any, XP-Pen drawing tablets or monitors on NixOS.
## The procedure
First of all, You need to find out the identifier for your tablet. with the
tablet plugged into the USB port, run `lsusb` (which is part of the `usbutils`
package). You should get some lines of output with an entry similar to this one:
Bus 001 Device 005: ID 28bd:0904 XP-Pen 11 inch PenTablet
The `28bd:0904` is the ID we are looking for. Note that the part after the
colon varies from device to device, so don't just copy&paste.
Now we enter the realm of the configuration. First of all, we need to enable
the wacom driver and utilities with the following setting:
services.xserver.wacom.enable = true;
Now we move on to having the wacom driver actually manage the XP-Pen devices.
For this we have to add rules to the `Xorg.conf`, which is conveniently handled
like this:
services.xserver.inputClassSections = [
Identifier "XP-Pen 11 inch PenTablet"
MatchUSBID "28bd:0904"
MatchIsTablet "on"
MatchDevicePath "/dev/input/event*"
Driver "wacom"
Identifier "XP-Pen 11 inch PenTablet"
MatchUSBID "28bd:0904"
MatchIsKeyboard "on"
MatchDevicePath "/dev/input/event*"
Driver "libinput"
These two sections match to my tablet, so alter all occurences of the lines
`Identifier` and `MatchUSBID` to match your device, otherwise it will not work.
With this you tell your system to let the wacom driver handle all tablet input
it can handle, everything else will be interpreted as a keyboard by `libinput`,
which includes all the hotkeys on your tablet, if you have any.
Now comes the fun part mapping the upper stylus button to be a right-click. For
this we need to analyse the input of the stylus using `evtest` (which is
contained within the package of the same name). With root permissions we run
`evtest` and see output containing lines similar to this:
/dev/input/event21: 11 inch PenTablet Mouse
/dev/input/event22: 11 inch PenTablet Keyboard
/dev/input/event23: 11 inch PenTablet
Select the device event number [0-23]:
Select the device without `Mouse` or `Keyboard`, so `23` in this example.
You will get an overview of all input events supported by this device and when
you bring your stylus near the pad it should pick up all movements and button
presses you make and print them onto the console. To abort this testing, press
`Ctrl` + `C` on your keyboard.
Depending on your device, the output of the stylus test will produce a lot of
output, because all position and tilt changes will be recorded onto the console.
what we are looking for though is the input of the upper stylus button, so
press it a few times trying to hold the pen as steady as possible and abort
shortly after your button presses.
Now you need to look for specific lines in the console output, which will look
somewhat like this:
Event: time 1624813216.995575, type 4 (EV_MSC), code 4 (MSC_SCAN), value d0045
The most important keyword here is the `code 4 (MSC_SCAN)`, so search for that,
and note the value behind it. In this case it is `d0045`. This value, by the
way, seems to be the standard value for the right-click with XP-Pen tablets,
as it is the same with all three tablets or monitors I have set up with this
method so far.
After we extracted the right-click value of the stylus, let's return to our
configuration file and add the following option:
services.udev.extraHwdb = ''
This rule will do the actual remapping of the button press on the stylus to
right-click. Note, that the USB-ID of your device is present in here too,
so change this line so it matches your device:
The line below contains the value of the upper stylus button (`d0045`) and maps
it to the right-click value (`0x14c`).
Congratulations. You just configured right-click on your stylus.
To map your hotkeys on your tablet, you need to run `evtest` again, but this
time you want to test the device with the keyboard description. Press your
hotkeys and note the values behind the `code 4 (MSC_SCAN)` lines.
Then remap the values to your desired keys. Note that not all hotkeys support
multikey bindings, but only those emitting multiple different keypress codes.
I hope this tutorial is helpful for you in any way. If you feel that I missed
something or need some clarification, just get in touch and I will expand this
article as needed.
Keep calm and wash hands.
[nixos]: https://nixos.org