diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..005c924 --- /dev/null +++ b/default.nix @@ -0,0 +1,30 @@ +let + # 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. ''. + haskellNix.sources.nixpkgs-2009 + # These arguments passed to nixpkgs, include some patches and also + # the haskell.nix functionality itself as an overlay. + haskellNix.nixpkgsArgs; +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. +} diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 0000000..3538edf --- /dev/null +++ b/nix/sources.json @@ -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///archive/.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///archive/.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///archive/.tar.gz" + } +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 0000000..1938409 --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,174 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + 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`!"; + in + 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: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (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 + else + 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: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + 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: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + 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" + else + 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; + }; + +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/shell.nix b/shell.nix index 0ba64df..61d18d9 100644 --- a/shell.nix +++ b/shell.nix @@ -1,35 +1,38 @@ -{ nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: - +# shell.nix let - - inherit (nixpkgs) pkgs; - - f = { mkDerivation, base, directory, filepath, hakyll, HTTP - , stdenv, time - }: - mkDerivation { - pname = "nek0-blog"; - version = "0.1.0.0"; - 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; in + 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 {}).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; + } diff --git a/site/posts/2021-06-27-Hacking-my-XP-Pen-drawing-tablets.md b/site/posts/2021-06-27-Hacking-my-XP-Pen-drawing-tablets.md new file mode 100644 index 0000000..7fec40a --- /dev/null +++ b/site/posts/2021-06-27-Hacking-my-XP-Pen-drawing-tablets.md @@ -0,0 +1,168 @@ +--- +title: Hacking my XP-Pen drawing tablets +author: nek0 +tags: +- 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: + +```nix +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: + +```nix +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: + +```nix +services.udev.extraHwdb = '' + evdev:input:b0003v28BDp0904e0100-e0* + KEYBOARD_KEY_d0045=0x14c +''; +``` + +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: + +``` +evdev:input:b0003vpe0100-e0* +``` + +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